@idealyst/theme 1.1.7 → 1.1.9

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,883 @@
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
+ let processedBody = processNode(node.body, depth + 1);
218
+
219
+ // Convert block body with single return to arrow notation for Unistyles compatibility
220
+ // This transforms: (props) => { return { ... } }
221
+ // Into: (props) => ({ ... })
222
+ if (t.isBlockStatement(processedBody) && processedBody.body.length === 1) {
223
+ const singleStmt = processedBody.body[0];
224
+ if (t.isReturnStatement(singleStmt) && singleStmt.argument) {
225
+ // Use the return argument directly as the arrow function body
226
+ processedBody = singleStmt.argument;
227
+ }
228
+ }
229
+
230
+ return t.arrowFunctionExpression(
231
+ node.params,
232
+ processedBody
233
+ );
234
+ }
235
+
236
+ // Handle TSAsExpression (e.g., { ... } as const)
237
+ if (t.isTSAsExpression(node)) {
238
+ const processedExpr = processNode(node.expression, depth + 1);
239
+ return t.tsAsExpression(processedExpr, node.typeAnnotation);
240
+ }
241
+
242
+ // Handle ParenthesizedExpression
243
+ if (t.isParenthesizedExpression(node)) {
244
+ const processedExpr = processNode(node.expression, depth + 1);
245
+ return t.parenthesizedExpression(processedExpr);
246
+ }
247
+
248
+ // Handle BlockStatement (for nested arrow functions with block bodies)
249
+ if (t.isBlockStatement(node)) {
250
+ const newBody = node.body.map(stmt => {
251
+ if (t.isReturnStatement(stmt) && stmt.argument) {
252
+ return t.returnStatement(processNode(stmt.argument, depth + 1));
253
+ }
254
+ // Handle variable declarations that might contain style objects
255
+ if (t.isVariableDeclaration(stmt)) {
256
+ return t.variableDeclaration(
257
+ stmt.kind,
258
+ stmt.declarations.map(decl => {
259
+ if (decl.init) {
260
+ return t.variableDeclarator(decl.id, processNode(decl.init, depth + 1));
261
+ }
262
+ return decl;
263
+ })
264
+ );
265
+ }
266
+ return stmt;
267
+ });
268
+ return t.blockStatement(newBody);
269
+ }
270
+
271
+ return node;
272
+ }
273
+
274
+ // Handle the callback body - may be ObjectExpression, TSAsExpression, ParenthesizedExpression, or BlockStatement
275
+ let bodyToProcess = cloned.body;
276
+
277
+ // Unwrap ParenthesizedExpression
278
+ if (t.isParenthesizedExpression(bodyToProcess)) {
279
+ bodyToProcess = bodyToProcess.expression;
280
+ }
281
+
282
+ // Unwrap TSAsExpression
283
+ if (t.isTSAsExpression(bodyToProcess)) {
284
+ const processedExpr = processNode(bodyToProcess.expression);
285
+ cloned.body = t.tsAsExpression(processedExpr, bodyToProcess.typeAnnotation);
286
+ } else if (t.isObjectExpression(bodyToProcess)) {
287
+ cloned.body = processNode(bodyToProcess);
288
+ } else if (t.isBlockStatement(cloned.body)) {
289
+ cloned.body.body = cloned.body.body.map(stmt => {
290
+ if (t.isReturnStatement(stmt) && stmt.argument) {
291
+ return t.returnStatement(processNode(stmt.argument));
292
+ }
293
+ return stmt;
294
+ });
295
+ }
296
+
297
+ return cloned;
298
+ }
299
+
300
+ function expandVariantsObject(t, variantsObj, themeParam, keys, verbose, expandedVariants) {
301
+ const newProperties = [];
302
+
303
+ verbose(` expandVariantsObject: processing ${variantsObj.properties?.length || 0} properties`);
304
+
305
+ for (const prop of variantsObj.properties) {
306
+ if (!t.isObjectProperty(prop)) {
307
+ newProperties.push(prop);
308
+ continue;
309
+ }
310
+
311
+ let variantName;
312
+ if (t.isIdentifier(prop.key)) {
313
+ variantName = prop.key.name;
314
+ } else if (t.isStringLiteral(prop.key)) {
315
+ variantName = prop.key.value;
316
+ } else {
317
+ newProperties.push(prop);
318
+ continue;
319
+ }
320
+
321
+ verbose(` Checking variant: ${variantName}`);
322
+ const iteratorInfo = findIteratorPattern(t, prop.value, themeParam);
323
+ verbose(` Iterator info for ${variantName}: ${JSON.stringify(iteratorInfo)}`);
324
+
325
+ if (iteratorInfo) {
326
+ verbose(` Expanding ${variantName} variant with ${iteratorInfo.type} iterator`);
327
+ verbose(` Keys available: ${JSON.stringify(keys?.sizes?.[iteratorInfo.componentName] || keys?.intents || [])}`);
328
+ const expanded = expandVariantWithIterator(t, prop.value, themeParam, keys, iteratorInfo, verbose);
329
+ newProperties.push(t.objectProperty(prop.key, expanded));
330
+
331
+ const iteratorKey = iteratorInfo.componentName
332
+ ? `${iteratorInfo.type}.${iteratorInfo.componentName}`
333
+ : iteratorInfo.type;
334
+ expandedVariants.push({ variant: variantName, iterator: iteratorKey });
335
+ } else {
336
+ newProperties.push(prop);
337
+ }
338
+ }
339
+
340
+ return t.objectExpression(newProperties);
341
+ }
342
+
343
+ function findIteratorPattern(t, node, themeParam, debugLog = () => {}) {
344
+ let result = null;
345
+
346
+ function walk(n, path = '') {
347
+ if (!n || typeof n !== 'object' || result) return;
348
+
349
+ if (t.isMemberExpression(n)) {
350
+ const chain = getMemberChain(t, n, themeParam);
351
+ if (chain) {
352
+ for (let i = 0; i < chain.length; i++) {
353
+ const part = chain[i];
354
+ if (part.startsWith('$')) {
355
+ const iteratorName = part.slice(1);
356
+
357
+ if (iteratorName === 'intents') {
358
+ result = {
359
+ type: 'intents',
360
+ accessPath: chain.slice(i + 1),
361
+ };
362
+ return;
363
+ }
364
+
365
+ if (i > 0 && chain[i - 1] === 'sizes') {
366
+ result = {
367
+ type: 'sizes',
368
+ componentName: iteratorName,
369
+ accessPath: chain.slice(i + 1),
370
+ };
371
+ return;
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ for (const key of Object.keys(n)) {
379
+ if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;
380
+ if (n[key] && typeof n[key] === 'object') {
381
+ walk(n[key]);
382
+ }
383
+ }
384
+ }
385
+
386
+ walk(node);
387
+ return result;
388
+ }
389
+
390
+ function getMemberChain(t, node, themeParam) {
391
+ const parts = [];
392
+
393
+ function walk(n) {
394
+ if (t.isIdentifier(n)) {
395
+ if (n.name === themeParam) {
396
+ return true;
397
+ }
398
+ return false;
399
+ }
400
+
401
+ if (t.isMemberExpression(n)) {
402
+ if (!walk(n.object)) return false;
403
+
404
+ if (t.isIdentifier(n.property)) {
405
+ parts.push(n.property.name);
406
+ } else if (t.isStringLiteral(n.property)) {
407
+ parts.push(n.property.value);
408
+ }
409
+ return true;
410
+ }
411
+
412
+ return false;
413
+ }
414
+
415
+ return walk(node) ? parts : null;
416
+ }
417
+
418
+ function expandVariantWithIterator(t, valueNode, themeParam, keys, iteratorInfo, verbose) {
419
+ let keysToExpand = [];
420
+
421
+ if (iteratorInfo.type === 'intents') {
422
+ keysToExpand = keys?.intents || [];
423
+ } else if (iteratorInfo.type === 'sizes' && iteratorInfo.componentName) {
424
+ keysToExpand = keys?.sizes?.[iteratorInfo.componentName] || [];
425
+ }
426
+
427
+ if (keysToExpand.length === 0) {
428
+ return valueNode;
429
+ }
430
+
431
+ verbose(` Expanding to keys: ${keysToExpand.join(', ')}`);
432
+
433
+ const expandedProps = [];
434
+
435
+ for (const key of keysToExpand) {
436
+ const expandedValue = replaceIteratorRefs(t, valueNode, themeParam, iteratorInfo, key);
437
+ expandedProps.push(
438
+ t.objectProperty(
439
+ t.identifier(key),
440
+ expandedValue
441
+ )
442
+ );
443
+ }
444
+
445
+ return t.objectExpression(expandedProps);
446
+ }
447
+
448
+ function replaceIteratorRefs(t, node, themeParam, iteratorInfo, key) {
449
+ if (!node || typeof node !== 'object') return node;
450
+
451
+ const cloned = t.cloneDeep(node);
452
+
453
+ function walk(n) {
454
+ if (!n || typeof n !== 'object') return n;
455
+
456
+ if (Array.isArray(n)) {
457
+ return n.map(walk);
458
+ }
459
+
460
+ if (t.isMemberExpression(n)) {
461
+ const chain = getMemberChain(t, n, themeParam);
462
+ if (chain) {
463
+ let hasIterator = false;
464
+ let dollarIndex = -1;
465
+
466
+ for (let i = 0; i < chain.length; i++) {
467
+ if (chain[i].startsWith('$')) {
468
+ const iterName = chain[i].slice(1);
469
+ if (iteratorInfo.type === 'intents' && iterName === 'intents') {
470
+ hasIterator = true;
471
+ dollarIndex = i;
472
+ break;
473
+ }
474
+ if (iteratorInfo.type === 'sizes' && iterName === iteratorInfo.componentName && i > 0 && chain[i - 1] === 'sizes') {
475
+ hasIterator = true;
476
+ dollarIndex = i;
477
+ break;
478
+ }
479
+ }
480
+ }
481
+
482
+ if (hasIterator) {
483
+ const newChain = [];
484
+ for (let i = 0; i < chain.length; i++) {
485
+ if (i === dollarIndex) {
486
+ const iterName = chain[i].slice(1);
487
+ newChain.push(iterName);
488
+ newChain.push(key);
489
+ } else {
490
+ newChain.push(chain[i]);
491
+ }
492
+ }
493
+ return buildMemberExpression(t, themeParam, newChain);
494
+ }
495
+ }
496
+ }
497
+
498
+ if (t.isObjectExpression(n)) {
499
+ return t.objectExpression(
500
+ n.properties.map(prop => walk(prop))
501
+ );
502
+ }
503
+
504
+ if (t.isObjectProperty(n)) {
505
+ return t.objectProperty(
506
+ n.key,
507
+ walk(n.value),
508
+ n.computed,
509
+ n.shorthand
510
+ );
511
+ }
512
+
513
+ return n;
514
+ }
515
+
516
+ return walk(cloned);
517
+ }
518
+
519
+ function buildMemberExpression(t, base, chain) {
520
+ let expr = t.identifier(base);
521
+ for (const part of chain) {
522
+ expr = t.memberExpression(expr, t.identifier(part));
523
+ }
524
+ return expr;
525
+ }
526
+
527
+ // ============================================================================
528
+ // Babel Plugin
529
+ // ============================================================================
530
+
531
+ module.exports = function idealystStylesPlugin({ types: t }) {
532
+ // Store babel types for use in extractThemeKeysFromAST
533
+ babelTypes = t;
534
+
535
+ let debugMode = false;
536
+ let verboseMode = false;
537
+
538
+ function debug(...args) {
539
+ if (debugMode || verboseMode) {
540
+ console.log('[idealyst-plugin]', ...args);
541
+ }
542
+ }
543
+
544
+ function verbose(...args) {
545
+ if (verboseMode) {
546
+ console.log('[idealyst-plugin]', ...args);
547
+ }
548
+ }
549
+
550
+ // Log plugin initialization
551
+ console.log('[idealyst-plugin] Plugin loaded (AST-based theme analysis)');
552
+
553
+ return {
554
+ name: 'idealyst-styles',
555
+
556
+ visitor: {
557
+ Program: {
558
+ enter(path, state) {
559
+ const filename = state.filename || '';
560
+ const opts = state.opts || {};
561
+
562
+ // Check if this file should be processed
563
+ const shouldProcess =
564
+ opts.processAll ||
565
+ (opts.autoProcessPaths?.some(p => filename.includes(p)));
566
+
567
+ state.needsStyleSheetImport = false;
568
+ state.hasStyleSheetImport = false;
569
+
570
+ path.traverse({
571
+ ImportDeclaration(importPath) {
572
+ if (importPath.node.source.value === 'react-native-unistyles') {
573
+ for (const spec of importPath.node.specifiers) {
574
+ if (t.isImportSpecifier(spec) &&
575
+ t.isIdentifier(spec.imported, { name: 'StyleSheet' })) {
576
+ state.hasStyleSheetImport = true;
577
+ }
578
+ }
579
+ state.unistylesImportPath = importPath;
580
+ }
581
+ }
582
+ });
583
+ },
584
+ exit(path, state) {
585
+ // Always ensure StyleSheet import and marker when needed
586
+ // (existing import might have been removed by tree-shaking)
587
+ if (state.needsStyleSheetImport) {
588
+ // Check if StyleSheet is currently in the AST
589
+ let hasStyleSheet = false;
590
+ let hasVoidMarker = false;
591
+ let unistylesImport = null;
592
+
593
+ path.traverse({
594
+ ImportDeclaration(importPath) {
595
+ if (importPath.node.source.value === 'react-native-unistyles') {
596
+ unistylesImport = importPath;
597
+ for (const spec of importPath.node.specifiers) {
598
+ if (t.isImportSpecifier(spec) &&
599
+ t.isIdentifier(spec.imported, { name: 'StyleSheet' })) {
600
+ hasStyleSheet = true;
601
+ }
602
+ }
603
+ }
604
+ },
605
+ // Check for existing void StyleSheet; marker
606
+ ExpressionStatement(exprPath) {
607
+ if (t.isUnaryExpression(exprPath.node.expression, { operator: 'void' }) &&
608
+ t.isIdentifier(exprPath.node.expression.argument, { name: 'StyleSheet' })) {
609
+ hasVoidMarker = true;
610
+ }
611
+ }
612
+ });
613
+
614
+ if (!hasStyleSheet) {
615
+ if (unistylesImport) {
616
+ // Add StyleSheet to existing import
617
+ unistylesImport.node.specifiers.push(
618
+ t.importSpecifier(t.identifier('StyleSheet'), t.identifier('StyleSheet'))
619
+ );
620
+ } else {
621
+ // Create new import
622
+ const importDecl = t.importDeclaration(
623
+ [t.importSpecifier(t.identifier('StyleSheet'), t.identifier('StyleSheet'))],
624
+ t.stringLiteral('react-native-unistyles')
625
+ );
626
+ path.unshiftContainer('body', importDecl);
627
+ }
628
+ }
629
+
630
+ // Add void StyleSheet; marker so Unistyles detects the file
631
+ // This must be present for Unistyles to process the file
632
+ if (!hasVoidMarker) {
633
+ const voidMarker = t.expressionStatement(
634
+ t.unaryExpression('void', t.identifier('StyleSheet'))
635
+ );
636
+ // Insert after imports (find first non-import statement)
637
+ const body = path.node.body;
638
+ let insertIndex = 0;
639
+ for (let i = 0; i < body.length; i++) {
640
+ if (!t.isImportDeclaration(body[i])) {
641
+ insertIndex = i;
642
+ break;
643
+ }
644
+ insertIndex = i + 1;
645
+ }
646
+ body.splice(insertIndex, 0, voidMarker);
647
+ }
648
+ }
649
+ }
650
+ },
651
+
652
+ CallExpression(path, state) {
653
+ const { node } = path;
654
+ const opts = state.opts || {};
655
+ debugMode = opts.debug === true;
656
+ verboseMode = opts.verbose === true;
657
+ const filename = state.filename || '';
658
+
659
+ const shouldProcess =
660
+ opts.processAll ||
661
+ (opts.autoProcessPaths?.some(p => filename.includes(p)));
662
+
663
+ if (!shouldProcess) return;
664
+
665
+ // ============================================================
666
+ // Handle extendStyle - Store extension AST for later merging
667
+ // ============================================================
668
+ if (t.isIdentifier(node.callee, { name: 'extendStyle' })) {
669
+ debug(`FOUND extendStyle in: ${filename}`);
670
+
671
+ const [componentNameArg, stylesCallback] = node.arguments;
672
+
673
+ if (!t.isStringLiteral(componentNameArg)) {
674
+ debug(` SKIP - componentName is not a string literal`);
675
+ return;
676
+ }
677
+
678
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
679
+ !t.isFunctionExpression(stylesCallback)) {
680
+ debug(` SKIP - callback is not a function`);
681
+ return;
682
+ }
683
+
684
+ const componentName = componentNameArg.value;
685
+ debug(` Processing extendStyle('${componentName}')`);
686
+
687
+ // Get theme parameter name
688
+ let themeParam = 'theme';
689
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
690
+ themeParam = stylesCallback.params[0].name;
691
+ }
692
+
693
+ // Load theme keys and expand iterators
694
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
695
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
696
+ const expandedVariants = [];
697
+ const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
698
+
699
+ // Store in registry for later merging
700
+ const entry = getOrCreateEntry(componentName);
701
+ entry.extensions.push(expandedCallback);
702
+ entry.themeParam = themeParam;
703
+
704
+ debug(` -> Stored extension for '${componentName}' (${entry.extensions.length} total)`);
705
+
706
+ // Remove the extendStyle call (it's been captured)
707
+ path.remove();
708
+ return;
709
+ }
710
+
711
+ // ============================================================
712
+ // Handle overrideStyle - Store as override (replaces base)
713
+ // ============================================================
714
+ if (t.isIdentifier(node.callee, { name: 'overrideStyle' })) {
715
+ debug(`FOUND overrideStyle in: ${filename}`);
716
+
717
+ const [componentNameArg, stylesCallback] = node.arguments;
718
+
719
+ if (!t.isStringLiteral(componentNameArg)) {
720
+ debug(` SKIP - componentName is not a string literal`);
721
+ return;
722
+ }
723
+
724
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
725
+ !t.isFunctionExpression(stylesCallback)) {
726
+ debug(` SKIP - callback is not a function`);
727
+ return;
728
+ }
729
+
730
+ const componentName = componentNameArg.value;
731
+ debug(` Processing overrideStyle('${componentName}')`);
732
+
733
+ let themeParam = 'theme';
734
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
735
+ themeParam = stylesCallback.params[0].name;
736
+ }
737
+
738
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
739
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
740
+ const expandedVariants = [];
741
+ const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
742
+
743
+ // Store as override (replaces base entirely)
744
+ const entry = getOrCreateEntry(componentName);
745
+ entry.override = expandedCallback;
746
+ entry.themeParam = themeParam;
747
+
748
+ debug(` -> Stored override for '${componentName}'`);
749
+
750
+ // Remove the overrideStyle call
751
+ path.remove();
752
+ return;
753
+ }
754
+
755
+ // ============================================================
756
+ // Handle defineStyle - Merge with extensions and output StyleSheet.create
757
+ // ============================================================
758
+ if (t.isIdentifier(node.callee, { name: 'defineStyle' })) {
759
+ debug(`FOUND defineStyle in: ${filename}`);
760
+
761
+ const [componentNameArg, stylesCallback] = node.arguments;
762
+
763
+ if (!t.isStringLiteral(componentNameArg)) {
764
+ debug(` SKIP - componentName is not a string literal`);
765
+ return;
766
+ }
767
+
768
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
769
+ !t.isFunctionExpression(stylesCallback)) {
770
+ debug(` SKIP - callback is not a function`);
771
+ return;
772
+ }
773
+
774
+ const componentName = componentNameArg.value;
775
+ debug(` Processing defineStyle('${componentName}')`);
776
+
777
+ let themeParam = 'theme';
778
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
779
+ themeParam = stylesCallback.params[0].name;
780
+ }
781
+
782
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
783
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
784
+ const expandedVariants = [];
785
+
786
+ // Debug for View only
787
+ if (componentName === 'View') {
788
+ console.log(`\n========== VIEW KEYS DEBUG ==========`);
789
+ console.log(`rootDir: ${rootDir}`);
790
+ console.log(`keys.sizes.view: ${JSON.stringify(keys?.sizes?.view)}`);
791
+ console.log(`keys.intents: ${JSON.stringify(keys?.intents)}`);
792
+ console.log(`======================================\n`);
793
+ }
794
+
795
+ try {
796
+ // Expand iterators in the base callback
797
+ let expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
798
+
799
+ // Check for registered override or extensions
800
+ const entry = getOrCreateEntry(componentName);
801
+
802
+ if (entry.override) {
803
+ // Override completely replaces base
804
+ debug(` -> Using override for '${componentName}'`);
805
+ expandedCallback = entry.override;
806
+ } else if (entry.extensions.length > 0) {
807
+ // Merge extensions into base
808
+ debug(` -> Merging ${entry.extensions.length} extensions for '${componentName}'`);
809
+ for (const ext of entry.extensions) {
810
+ expandedCallback = mergeCallbackBodies(t, expandedCallback, ext);
811
+ }
812
+ }
813
+
814
+ if (!expandedCallback) {
815
+ console.error(`[idealyst-plugin] ERROR: expandedCallback is null/undefined for ${componentName}`);
816
+ return;
817
+ }
818
+
819
+ // Transform to StyleSheet.create
820
+ state.needsStyleSheetImport = true;
821
+
822
+ const newCall = t.callExpression(
823
+ t.memberExpression(
824
+ t.identifier('StyleSheet'),
825
+ t.identifier('create')
826
+ ),
827
+ [expandedCallback]
828
+ );
829
+
830
+ path.replaceWith(newCall);
831
+
832
+ debug(` -> Transformed to StyleSheet.create`);
833
+ if (expandedVariants.length > 0) {
834
+ debug(` Expanded: ${expandedVariants.map(v => `${v.variant}(${v.iterator})`).join(', ')}`);
835
+ }
836
+
837
+ // Output generated code when verbose
838
+ if (verboseMode) {
839
+ const generate = require('@babel/generator').default;
840
+ const output = generate(newCall);
841
+ console.log(`[idealyst-plugin] Generated code for ${componentName}:`);
842
+ console.log(output.code.substring(0, 2000) + (output.code.length > 2000 ? '...' : ''));
843
+ }
844
+ } catch (err) {
845
+ console.error(`[idealyst-plugin] ERROR transforming defineStyle('${componentName}'):`, err);
846
+ }
847
+ return;
848
+ }
849
+
850
+ // ============================================================
851
+ // Handle StyleSheet.create - Just expand $iterator patterns
852
+ // ============================================================
853
+ if (t.isMemberExpression(node.callee) &&
854
+ t.isIdentifier(node.callee.object, { name: 'StyleSheet' }) &&
855
+ t.isIdentifier(node.callee.property, { name: 'create' })) {
856
+
857
+ const [stylesCallback] = node.arguments;
858
+
859
+ if (!t.isArrowFunctionExpression(stylesCallback) &&
860
+ !t.isFunctionExpression(stylesCallback)) {
861
+ return;
862
+ }
863
+
864
+ let themeParam = 'theme';
865
+ if (stylesCallback.params?.[0] && t.isIdentifier(stylesCallback.params[0])) {
866
+ themeParam = stylesCallback.params[0].name;
867
+ }
868
+
869
+ const rootDir = opts.rootDir || state.cwd || process.cwd();
870
+ const keys = loadThemeKeys(opts, rootDir, t, verboseMode);
871
+ const expandedVariants = [];
872
+
873
+ const expandedCallback = expandIterators(t, stylesCallback, themeParam, keys, verbose, expandedVariants);
874
+ node.arguments[0] = expandedCallback;
875
+
876
+ if (expandedVariants.length > 0) {
877
+ debug(` -> Expanded $iterator patterns in StyleSheet.create: ${expandedVariants.map(v => `${v.variant}(${v.iterator})`).join(', ')}`);
878
+ }
879
+ }
880
+ },
881
+ },
882
+ };
883
+ };