@ethlete/cdk 4.70.0 → 5.0.0-next.0

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,868 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { formatFiles, getProjects, logger, visitNotIgnoredFiles } from '@nx/devkit';
3
+ import * as ts from 'typescript';
4
+ const STRATEGY_MAP = {
5
+ dialog: 'dialogOverlayStrategy',
6
+ bottomSheet: 'bottomSheetOverlayStrategy',
7
+ leftSheet: 'leftSheetOverlayStrategy',
8
+ rightSheet: 'rightSheetOverlayStrategy',
9
+ topSheet: 'topSheetOverlayStrategy',
10
+ fullScreenDialog: 'fullScreenDialogOverlayStrategy',
11
+ anchoredDialog: 'anchoredDialogOverlayStrategy',
12
+ };
13
+ const STRATEGY_INJECT_MAP = {
14
+ dialog: 'injectDialogStrategy',
15
+ bottomSheet: 'injectBottomSheetStrategy',
16
+ leftSheet: 'injectLeftSheetStrategy',
17
+ rightSheet: 'injectRightSheetStrategy',
18
+ topSheet: 'injectTopSheetStrategy',
19
+ fullScreenDialog: 'injectFullscreenDialogStrategy',
20
+ anchoredDialog: 'injectAnchoredDialogStrategy',
21
+ };
22
+ const TRANSFORMING_PRESET_STRATEGIES = {
23
+ transformingBottomSheetToDialog: 'transformingBottomSheetToDialogOverlayStrategy',
24
+ transformingFullScreenDialogToRightSheet: 'transformingFullScreenDialogToRightSheetOverlayStrategy',
25
+ transformingFullScreenDialogToDialog: 'transformingFullScreenDialogToDialogOverlayStrategy',
26
+ };
27
+ export default async function migrateOverlayPositions(tree) {
28
+ logger.log('Starting overlay positions migration...');
29
+ const projects = getProjects(tree);
30
+ let filesChanged = 0;
31
+ for (const [, project] of projects) {
32
+ visitNotIgnoredFiles(tree, project.root, (filePath) => {
33
+ if (!shouldProcessFile(filePath, tree)) {
34
+ return;
35
+ }
36
+ const content = tree.read(filePath, 'utf-8');
37
+ const newContent = processFile(filePath, content);
38
+ if (newContent !== content) {
39
+ tree.write(filePath, newContent);
40
+ filesChanged++;
41
+ logger.log(`✓ Migrated ${filePath}`);
42
+ }
43
+ });
44
+ }
45
+ if (filesChanged > 0) {
46
+ logger.log(`✓ Successfully migrated ${filesChanged} file(s)`);
47
+ await formatFiles(tree);
48
+ }
49
+ else {
50
+ logger.log('No files needed migration');
51
+ }
52
+ }
53
+ function shouldProcessFile(filePath, tree) {
54
+ if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts')) {
55
+ return false;
56
+ }
57
+ const content = tree.read(filePath, 'utf-8');
58
+ if (!content)
59
+ return false;
60
+ // Quick check if file might need migration
61
+ return (content.includes('OverlayService') ||
62
+ content.includes('positions') ||
63
+ content.includes('OverlayBreakpointConfigEntry'));
64
+ }
65
+ function processFile(filePath, content) {
66
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
67
+ const changes = [];
68
+ const importsToAdd = new Set();
69
+ const importsToRemove = new Set();
70
+ // Track which OverlayService imports are from @ethlete/cdk
71
+ const ethleteOverlayServiceNames = new Set();
72
+ // First pass: identify OverlayService imports from @ethlete/cdk
73
+ function identifyEthleteOverlayService(node) {
74
+ if (ts.isImportDeclaration(node)) {
75
+ const moduleSpecifier = node.moduleSpecifier;
76
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/cdk') {
77
+ if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
78
+ node.importClause.namedBindings.elements.forEach((element) => {
79
+ if (element.name.text === 'OverlayService' || element.propertyName?.text === 'OverlayService') {
80
+ // Store the local name (could be aliased)
81
+ ethleteOverlayServiceNames.add(element.name.text);
82
+ }
83
+ });
84
+ }
85
+ }
86
+ }
87
+ ts.forEachChild(node, identifyEthleteOverlayService);
88
+ }
89
+ identifyEthleteOverlayService(sourceFile);
90
+ // Helper to check if a type reference is for ethlete OverlayService
91
+ function isEthleteOverlayServiceType(node) {
92
+ if (ts.isIdentifier(node.typeName)) {
93
+ return ethleteOverlayServiceNames.has(node.typeName.text);
94
+ }
95
+ return false;
96
+ }
97
+ // Helper to check if inject() call is for ethlete OverlayService
98
+ function isEthleteOverlayServiceInject(node) {
99
+ if (ts.isIdentifier(node.expression) && node.expression.text === 'inject' && node.arguments.length === 1) {
100
+ const arg = node.arguments[0];
101
+ if (arg && ts.isIdentifier(arg)) {
102
+ return ethleteOverlayServiceNames.has(arg.text);
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+ // Visit the AST to find what needs to change
108
+ function visit(node) {
109
+ // Handle constructor injection of OverlayService - MUST BE FIRST
110
+ if (ts.isConstructorDeclaration(node)) {
111
+ const overlayServiceParams = [];
112
+ // Find all OverlayService parameters from @ethlete/cdk
113
+ node.parameters.forEach((param) => {
114
+ if (param.type && ts.isTypeReferenceNode(param.type) && isEthleteOverlayServiceType(param.type)) {
115
+ overlayServiceParams.push(param);
116
+ }
117
+ });
118
+ if (overlayServiceParams.length > 0) {
119
+ // Find the class declaration
120
+ let classDecl;
121
+ let current = node.parent;
122
+ while (current) {
123
+ if (ts.isClassDeclaration(current)) {
124
+ classDecl = current;
125
+ break;
126
+ }
127
+ current = current.parent;
128
+ }
129
+ if (classDecl) {
130
+ const hasOnlyOverlayServiceParams = node.parameters.length === overlayServiceParams.length;
131
+ const hasNoBody = !node.body || node.body.statements.length === 0;
132
+ if (hasOnlyOverlayServiceParams && hasNoBody) {
133
+ // We're going to replace the entire constructor with field declarations
134
+ // Get the full range of the constructor including leading whitespace
135
+ const constructorFullStart = node.getFullStart();
136
+ const constructorEnd = node.getEnd();
137
+ // Find the end including trailing newline
138
+ const fullText = sourceFile.getFullText();
139
+ let endPos = constructorEnd;
140
+ while (endPos < fullText.length && (fullText[endPos] === ' ' || fullText[endPos] === '\t')) {
141
+ endPos++;
142
+ }
143
+ if (endPos < fullText.length && fullText[endPos] === '\n') {
144
+ endPos++;
145
+ }
146
+ // Get the indentation by looking at the constructor line
147
+ const constructorLineStart = fullText.lastIndexOf('\n', constructorFullStart) + 1;
148
+ const indent = fullText.substring(constructorLineStart, constructorFullStart);
149
+ // Build replacement with field declarations
150
+ const fieldDeclarations = overlayServiceParams
151
+ .map((param) => {
152
+ const paramName = param.name.getText(sourceFile);
153
+ const modifiers = param.modifiers?.map((m) => m.getText(sourceFile)).join(' ') || 'private';
154
+ return `${indent}${modifiers} ${paramName} = injectOverlayManager();`;
155
+ })
156
+ .join('\n');
157
+ // Replace the entire constructor with the field declarations
158
+ changes.push({
159
+ start: constructorFullStart,
160
+ end: endPos,
161
+ replacement: fieldDeclarations + '\n',
162
+ });
163
+ overlayServiceParams.forEach(() => {
164
+ importsToAdd.add('injectOverlayManager');
165
+ importsToRemove.add('OverlayService');
166
+ });
167
+ }
168
+ }
169
+ }
170
+ return true;
171
+ }
172
+ // Handle inject(OverlayService) - only if from @ethlete/cdk
173
+ if (ts.isCallExpression(node) && isEthleteOverlayServiceInject(node)) {
174
+ changes.push({
175
+ start: node.getStart(sourceFile),
176
+ end: node.getEnd(),
177
+ replacement: 'injectOverlayManager()',
178
+ });
179
+ importsToAdd.add('injectOverlayManager');
180
+ return true;
181
+ }
182
+ // Handle positions: property FIRST (before checking for method calls)
183
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'positions') {
184
+ // Check if this is a builder pattern: (builder) => ...
185
+ if (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) {
186
+ const func = node.initializer;
187
+ // Check if it has a parameter (the builder parameter)
188
+ if (func.parameters.length === 1) {
189
+ const builderParam = func.parameters[0];
190
+ const builderParamName = builderParam.name.getText(sourceFile);
191
+ // Check if the function body uses DEFAULTS
192
+ if (func.body && nodeContainsDefaults(func.body)) {
193
+ // Get the function body (array or block)
194
+ let bodyCode;
195
+ if (ts.isBlock(func.body)) {
196
+ // Function with block: (builder) => { return [...] }
197
+ bodyCode = func.body.getText(sourceFile);
198
+ }
199
+ else {
200
+ // Arrow function with expression: (builder) => [...]
201
+ bodyCode = func.body.getText(sourceFile);
202
+ }
203
+ // Transform the body - replace builder.DEFAULTS.X with strategy calls
204
+ const { code: transformedBody, usesMergeConfigs, strategiesUsed, } = transformBuilderBodyForDefaults(bodyCode, builderParamName, sourceFile);
205
+ if (usesMergeConfigs) {
206
+ importsToAdd.add('mergeOverlayBreakpointConfigs');
207
+ }
208
+ // Build the factory function with inject statements
209
+ const injectStatements = Array.from(strategiesUsed)
210
+ .map((inject) => {
211
+ const varName = getStrategyVariableName(inject);
212
+ return `const ${varName} = ${inject}();`;
213
+ })
214
+ .join('\n ');
215
+ const factoryCode = `() => {
216
+ ${injectStatements}
217
+ return ${transformedBody};
218
+ }`;
219
+ changes.push({
220
+ start: node.name.getStart(sourceFile),
221
+ end: node.initializer.getEnd(),
222
+ replacement: `strategies: ${factoryCode}`,
223
+ });
224
+ // Add all strategy inject functions to imports
225
+ strategiesUsed.forEach((inject) => importsToAdd.add(inject));
226
+ return false; // Don't visit children
227
+ }
228
+ // Not using DEFAULTS, just transform the builder pattern normally
229
+ const transformedValue = transformBuilderPattern(node.initializer.getText(sourceFile), importsToAdd);
230
+ if (transformedValue !== node.initializer.getText(sourceFile)) {
231
+ changes.push({
232
+ start: node.name.getStart(sourceFile),
233
+ end: node.initializer.getEnd(),
234
+ replacement: `strategies: ${transformedValue}`,
235
+ });
236
+ return false;
237
+ }
238
+ }
239
+ }
240
+ // Handle DEFAULTS case (not in builder pattern)
241
+ if (nodeContainsDefaults(node.initializer)) {
242
+ const strategyInjects = new Set();
243
+ trackDefaultsUsageInNode(node.initializer, strategyInjects);
244
+ const factoryCode = generateFactoryFunctionCode(node.initializer, sourceFile, strategyInjects, importsToAdd);
245
+ changes.push({
246
+ start: node.name.getStart(sourceFile),
247
+ end: node.initializer.getEnd(),
248
+ replacement: `strategies: ${factoryCode}`,
249
+ });
250
+ strategyInjects.forEach((inject) => importsToAdd.add(inject));
251
+ return false;
252
+ }
253
+ // Check if value directly uses overlay positions API
254
+ const directlyUsesPositionsApi = node.initializer.getText(sourceFile).includes('.positions.') ||
255
+ node.initializer.getText(sourceFile).includes('builder.');
256
+ if (directlyUsesPositionsApi) {
257
+ const originalValue = node.initializer.getText(sourceFile);
258
+ const transformedValue = transformPositionCalls(originalValue, importsToAdd);
259
+ if (transformedValue !== originalValue) {
260
+ changes.push({
261
+ start: node.name.getStart(sourceFile),
262
+ end: node.initializer.getEnd(),
263
+ replacement: `strategies: ${transformedValue}`,
264
+ });
265
+ return false;
266
+ }
267
+ }
268
+ // For any other positions: property inside overlay calls, just rename it
269
+ if (isInsideOverlayHandlerCall(node) || isInsideOverlayOpenCall(node)) {
270
+ changes.push({
271
+ start: node.name.getStart(sourceFile),
272
+ end: node.name.getEnd(),
273
+ replacement: 'strategies',
274
+ });
275
+ return true;
276
+ }
277
+ }
278
+ // Handle class property declarations with DEFAULTS
279
+ if (ts.isPropertyDeclaration(node) && node.initializer) {
280
+ // Check if the type is OverlayBreakpointConfigEntry[]
281
+ if (node.type && isOverlayBreakpointConfigEntryArrayType(node.type) && nodeContainsDefaults(node.initializer)) {
282
+ const strategyInjects = new Set();
283
+ trackDefaultsUsageInNode(node.initializer, strategyInjects);
284
+ // Update the type to () => OverlayStrategyBreakpoint[]
285
+ if (ts.isArrayTypeNode(node.type)) {
286
+ changes.push({
287
+ start: node.type.getStart(sourceFile),
288
+ end: node.type.getEnd(),
289
+ replacement: '() => OverlayStrategyBreakpoint[]',
290
+ });
291
+ importsToAdd.add('OverlayStrategyBreakpoint');
292
+ }
293
+ // Wrap the initializer in a factory function
294
+ const factoryCode = generateFactoryFunctionCode(node.initializer, sourceFile, strategyInjects, importsToAdd);
295
+ changes.push({
296
+ start: node.initializer.getStart(sourceFile),
297
+ end: node.initializer.getEnd(),
298
+ replacement: factoryCode,
299
+ });
300
+ strategyInjects.forEach((inject) => importsToAdd.add(inject));
301
+ return false; // Don't visit children
302
+ }
303
+ }
304
+ // Handle overlayService.positions.method() calls (only if not already handled above)
305
+ if (isPositionMethodCall(node)) {
306
+ // Check if this node is inside a return statement that contains DEFAULTS
307
+ let current = node.parent;
308
+ let insideDefaultsReturn = false;
309
+ while (current) {
310
+ if (ts.isReturnStatement(current) && current.expression) {
311
+ if (nodeContainsDefaults(current.expression)) {
312
+ insideDefaultsReturn = true;
313
+ break;
314
+ }
315
+ }
316
+ current = current.parent;
317
+ }
318
+ // Skip processing if we're inside a DEFAULTS return statement
319
+ // (it will be handled by the factory function transformation)
320
+ if (insideDefaultsReturn) {
321
+ return false; // Don't process this node or its children
322
+ }
323
+ const callExpr = node;
324
+ const propAccess = callExpr.expression;
325
+ const methodName = propAccess.name.text;
326
+ let replacement;
327
+ if (methodName in TRANSFORMING_PRESET_STRATEGIES) {
328
+ const strategyFn = TRANSFORMING_PRESET_STRATEGIES[methodName];
329
+ replacement = `${strategyFn}(${callExpr.arguments.map((arg) => arg.getText(sourceFile)).join(', ')})`;
330
+ importsToAdd.add(strategyFn);
331
+ }
332
+ else if (methodName === 'mergeConfigs') {
333
+ replacement = `mergeOverlayBreakpointConfigs(${callExpr.arguments.map((arg) => arg.getText(sourceFile)).join(', ')})`;
334
+ importsToAdd.add('mergeOverlayBreakpointConfigs');
335
+ }
336
+ else {
337
+ const strategyFn = STRATEGY_MAP[methodName];
338
+ if (strategyFn) {
339
+ replacement = `${strategyFn}(${callExpr.arguments.map((arg) => arg.getText(sourceFile)).join(', ')})`;
340
+ importsToAdd.add(strategyFn);
341
+ }
342
+ else {
343
+ return true;
344
+ }
345
+ }
346
+ changes.push({
347
+ start: callExpr.getStart(sourceFile),
348
+ end: callExpr.getEnd(),
349
+ replacement,
350
+ });
351
+ return true;
352
+ }
353
+ // Handle OverlayBreakpointConfigEntry type
354
+ if (isOverlayBreakpointConfigEntryType(node)) {
355
+ const typeNode = node;
356
+ let parentFunc;
357
+ let current = node.parent;
358
+ while (current) {
359
+ if (ts.isMethodDeclaration(current) || ts.isFunctionDeclaration(current)) {
360
+ parentFunc = current;
361
+ break;
362
+ }
363
+ current = current.parent;
364
+ }
365
+ if (parentFunc?.body && nodeContainsDefaults(parentFunc.body)) {
366
+ // Skip - will be handled by function return type logic
367
+ }
368
+ else {
369
+ changes.push({
370
+ start: typeNode.getStart(sourceFile),
371
+ end: typeNode.getEnd(),
372
+ replacement: 'OverlayStrategyBreakpoint',
373
+ });
374
+ importsToAdd.add('OverlayStrategyBreakpoint');
375
+ }
376
+ return true;
377
+ }
378
+ // Handle function return types
379
+ // In the visit function, find this section and update it:
380
+ if ((ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) && node.body && node.type) {
381
+ if (nodeContainsDefaults(node.body) && isOverlayBreakpointConfigEntryArrayType(node.type)) {
382
+ const strategyInjects = new Set();
383
+ trackDefaultsUsageInNode(node.body, strategyInjects);
384
+ const returnStatements = [];
385
+ function findReturns(n) {
386
+ if (ts.isReturnStatement(n) && n.expression) {
387
+ returnStatements.push(n);
388
+ }
389
+ ts.forEachChild(n, findReturns);
390
+ }
391
+ findReturns(node.body);
392
+ if (returnStatements.length > 0) {
393
+ if (ts.isArrayTypeNode(node.type)) {
394
+ changes.push({
395
+ start: node.type.getStart(sourceFile),
396
+ end: node.type.getEnd(),
397
+ replacement: '() => OverlayStrategyBreakpoint[]',
398
+ });
399
+ importsToAdd.add('OverlayStrategyBreakpoint');
400
+ }
401
+ returnStatements.forEach((ret) => {
402
+ if (ret.expression) {
403
+ const factoryCode = generateFactoryFunctionCode(ret.expression, sourceFile, strategyInjects, importsToAdd);
404
+ // We need to be very precise about what we're replacing
405
+ // Replace ONLY from "return" to the end of the expression (not including semicolon)
406
+ const returnKeywordStart = ret.getStart(sourceFile); // Start of 'return' keyword
407
+ const expressionEnd = ret.expression.getEnd(); // End of the array expression
408
+ changes.push({
409
+ start: returnKeywordStart,
410
+ end: expressionEnd,
411
+ replacement: `return ${factoryCode}`,
412
+ });
413
+ }
414
+ });
415
+ strategyInjects.forEach((inject) => importsToAdd.add(inject));
416
+ }
417
+ }
418
+ return true;
419
+ }
420
+ // Handle imports
421
+ if (ts.isImportDeclaration(node)) {
422
+ const moduleSpecifier = node.moduleSpecifier;
423
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/cdk') {
424
+ if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
425
+ node.importClause.namedBindings.elements.forEach((element) => {
426
+ if (element.name.text === 'OverlayService') {
427
+ importsToRemove.add('OverlayService');
428
+ importsToAdd.add('injectOverlayManager');
429
+ }
430
+ if (element.name.text === 'OverlayBreakpointConfigEntry') {
431
+ importsToRemove.add('OverlayBreakpointConfigEntry');
432
+ }
433
+ });
434
+ }
435
+ }
436
+ return true;
437
+ }
438
+ return true;
439
+ }
440
+ function visitNode(node) {
441
+ const shouldContinue = visit(node);
442
+ if (shouldContinue) {
443
+ ts.forEachChild(node, visitNode);
444
+ }
445
+ }
446
+ visitNode(sourceFile);
447
+ // Apply changes in reverse order (from end to start) to maintain positions
448
+ changes.sort((a, b) => b.start - a.start);
449
+ for (let i = 0; i < changes.length - 1; i++) {
450
+ const current = changes[i];
451
+ const next = changes[i + 1];
452
+ if (current.start < next.end) {
453
+ logger.error(`Overlapping changes detected!`);
454
+ logger.error(`Change 1: ${current.start}-${current.end} = "${current.replacement}"`);
455
+ logger.error(`Change 2: ${next.start}-${next.end} = "${next.replacement}"`);
456
+ throw new Error('Overlapping changes detected - this will corrupt the file');
457
+ }
458
+ }
459
+ let result = content;
460
+ for (const change of changes) {
461
+ result = result.substring(0, change.start) + change.replacement + result.substring(change.end);
462
+ }
463
+ // Update imports
464
+ result = updateImportsInContent(result, importsToAdd, importsToRemove);
465
+ return result;
466
+ }
467
+ function transformNodeTextForDefaults(node, sourceFile) {
468
+ let text = node.getText(sourceFile);
469
+ let usesMergeConfigs = false;
470
+ // Check if mergeConfigs exists BEFORE any transformations
471
+ if (text.includes('.positions.mergeConfigs(')) {
472
+ usesMergeConfigs = true;
473
+ }
474
+ // Replace config: with strategy:
475
+ text = text.replace(/\bconfig:/g, 'strategy:');
476
+ // Replace DEFAULTS references with strategy.build() calls
477
+ Object.entries(STRATEGY_INJECT_MAP).forEach(([strategyName, injectFn]) => {
478
+ const varName = getStrategyVariableName(injectFn);
479
+ // Replace: overlayService.positions.DEFAULTS.dialog
480
+ // With: dialogStrategy.build()
481
+ const defaultsPattern = new RegExp(`(?:this\\.)?\\w+\\.positions\\.DEFAULTS\\.${strategyName}`, 'g');
482
+ text = text.replace(defaultsPattern, `${varName}.build()`);
483
+ });
484
+ // Now handle mergeConfigs - do this AFTER replacing DEFAULTS
485
+ Object.entries(STRATEGY_INJECT_MAP).forEach(([strategyName, injectFn]) => {
486
+ const varName = getStrategyVariableName(injectFn);
487
+ // Pattern: overlayService.positions.mergeConfigs(dialogStrategy.build(), ...args)
488
+ // We want to extract just the args after the first argument and comma
489
+ const pattern = new RegExp(`(?:this\\.)?\\w+\\.positions\\.mergeConfigs\\(\\s*${varName}\\.build\\(\\)\\s*,\\s*`, 'g');
490
+ let match;
491
+ const replacements = [];
492
+ while ((match = pattern.exec(text)) !== null) {
493
+ const matchStart = match.index;
494
+ const afterComma = match.index + match[0].length;
495
+ // Find the closing paren of mergeConfigs by counting parentheses
496
+ let parenCount = 1;
497
+ let pos = afterComma;
498
+ while (pos < text.length && parenCount > 0) {
499
+ if (text[pos] === '(')
500
+ parenCount++;
501
+ if (text[pos] === ')')
502
+ parenCount--;
503
+ pos++;
504
+ }
505
+ // Extract the arguments (everything between afterComma and the closing paren)
506
+ const closingParenPos = pos - 1;
507
+ const args = text.substring(afterComma, closingParenPos).trim();
508
+ // Remove trailing comma if present
509
+ const cleanedArgs = args.replace(/,\s*$/, '');
510
+ // Build replacement
511
+ const replacement = `${varName}.build(mergeOverlayBreakpointConfigs(${cleanedArgs}))`;
512
+ replacements.push({
513
+ start: matchStart,
514
+ end: pos,
515
+ replacement,
516
+ });
517
+ }
518
+ // Apply replacements in reverse order to maintain positions
519
+ replacements.reverse().forEach(({ start, end, replacement }) => {
520
+ text = text.substring(0, start) + replacement + text.substring(end);
521
+ });
522
+ });
523
+ // Replace any remaining overlayService.positions.mergeConfigs calls
524
+ text = text.replace(/(?:this\.)?\w+\.positions\.mergeConfigs\(/g, 'mergeOverlayBreakpointConfigs(');
525
+ return { code: text, usesMergeConfigs };
526
+ }
527
+ function isInsideOverlayOpenCall(node) {
528
+ let current = node;
529
+ while (current) {
530
+ if (ts.isCallExpression(current)) {
531
+ const expr = current.expression;
532
+ // Match any .open call (not just overlayService)
533
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === 'open') {
534
+ return true;
535
+ }
536
+ }
537
+ current = current.parent;
538
+ }
539
+ return false;
540
+ }
541
+ function generateFactoryFunctionCode(node, sourceFile, strategyInjects, importsToAdd) {
542
+ const { code: transformedCode, usesMergeConfigs } = transformNodeTextForDefaults(node, sourceFile);
543
+ if (usesMergeConfigs) {
544
+ importsToAdd.add('mergeOverlayBreakpointConfigs');
545
+ }
546
+ const injectStatements = Array.from(strategyInjects)
547
+ .map((inject) => {
548
+ const varName = getStrategyVariableName(inject);
549
+ return `const ${varName} = ${inject}();`;
550
+ })
551
+ .join('\n ');
552
+ // Build the factory function with proper indentation and closing braces
553
+ return `() => {
554
+ ${injectStatements}
555
+ return ${transformedCode};
556
+ }`;
557
+ }
558
+ function trackDefaultsUsageInNode(node, strategyInjects) {
559
+ function visit(n) {
560
+ if (ts.isPropertyAccessExpression(n) &&
561
+ ts.isPropertyAccessExpression(n.expression) &&
562
+ ts.isPropertyAccessExpression(n.expression.expression) &&
563
+ n.expression.expression.name.text === 'positions' &&
564
+ n.expression.name.text === 'DEFAULTS') {
565
+ const strategyName = n.name.text;
566
+ const injectFn = STRATEGY_INJECT_MAP[strategyName];
567
+ if (injectFn) {
568
+ strategyInjects.add(injectFn);
569
+ }
570
+ }
571
+ ts.forEachChild(n, visit);
572
+ }
573
+ visit(node);
574
+ }
575
+ function updateImportsInContent(content, importsToAdd, importsToRemove) {
576
+ const importRegex = /import\s*{\s*([^}]+)\s*}\s*from\s*['"]@ethlete\/cdk['"]/;
577
+ const match = content.match(importRegex);
578
+ if (!match) {
579
+ if (importsToAdd.size > 0) {
580
+ const newImport = `import { ${Array.from(importsToAdd).sort().join(', ')} } from '@ethlete/cdk';\n`;
581
+ return newImport + content;
582
+ }
583
+ return content;
584
+ }
585
+ const existingImports = match[1]
586
+ .split(',')
587
+ .map((i) => i.trim())
588
+ .filter((i) => {
589
+ if (!i)
590
+ return false;
591
+ // Remove old imports
592
+ if (importsToRemove.has(i))
593
+ return false;
594
+ // Remove transforming presets if we're adding the OverlayStrategy versions
595
+ if (i in TRANSFORMING_PRESET_STRATEGIES) {
596
+ const strategyVersion = TRANSFORMING_PRESET_STRATEGIES[i];
597
+ if (importsToAdd.has(strategyVersion)) {
598
+ return false; // Remove the old version
599
+ }
600
+ }
601
+ return true;
602
+ });
603
+ const allImports = [...new Set([...existingImports, ...Array.from(importsToAdd)])].sort();
604
+ if (allImports.length === 0) {
605
+ return content.replace(importRegex, '');
606
+ }
607
+ const newImportStatement = `import { ${allImports.join(', ')} } from '@ethlete/cdk'`;
608
+ return content.replace(importRegex, newImportStatement);
609
+ }
610
+ // Type guards
611
+ function isInjectOverlayServiceCall(node) {
612
+ return (ts.isCallExpression(node) &&
613
+ ts.isIdentifier(node.expression) &&
614
+ node.expression.text === 'inject' &&
615
+ node.arguments.length === 1 &&
616
+ ts.isIdentifier(node.arguments[0]) &&
617
+ node.arguments[0].text === 'OverlayService');
618
+ }
619
+ function isPositionMethodCall(node) {
620
+ return (ts.isCallExpression(node) &&
621
+ ts.isPropertyAccessExpression(node.expression) &&
622
+ ts.isPropertyAccessExpression(node.expression.expression) &&
623
+ node.expression.expression.name.text === 'positions');
624
+ }
625
+ function isOverlayBreakpointConfigEntryType(node) {
626
+ return (ts.isTypeReferenceNode(node) &&
627
+ ts.isIdentifier(node.typeName) &&
628
+ node.typeName.text === 'OverlayBreakpointConfigEntry');
629
+ }
630
+ // Helper functions
631
+ function checkIfOverlayRelated(node) {
632
+ // First check: Are we inside createOverlayHandler call?
633
+ if (isInsideOverlayHandlerCall(node)) {
634
+ return true;
635
+ }
636
+ // Check for overlay-related patterns that need migration using AST
637
+ let isOverlayRelated = false;
638
+ function visit(n) {
639
+ if (isOverlayRelated)
640
+ return;
641
+ if (isPositionMethodCall(n)) {
642
+ isOverlayRelated = true;
643
+ return;
644
+ }
645
+ if (isDefaultsUsage(n)) {
646
+ isOverlayRelated = true;
647
+ return;
648
+ }
649
+ if (ts.isCallExpression(n)) {
650
+ const callText = n.getText();
651
+ if (callText.includes('getConfig') || callText.includes('getPositions') || callText.includes('getStrategies')) {
652
+ isOverlayRelated = true;
653
+ return;
654
+ }
655
+ }
656
+ ts.forEachChild(n, visit);
657
+ }
658
+ visit(node);
659
+ return isOverlayRelated;
660
+ }
661
+ function isDefaultsUsage(node) {
662
+ return (ts.isPropertyAccessExpression(node) &&
663
+ ts.isPropertyAccessExpression(node.expression) &&
664
+ node.expression.name.text === 'DEFAULTS');
665
+ }
666
+ // Remove TRANSFORMING_PRESET_STRATEGIES constant - we don't need it
667
+ // The transforming presets keep their original names
668
+ // Update the transformPositionCalls function
669
+ // The TRANSFORMING_PRESET_STRATEGIES constant is correct:
670
+ // But in transformPositionCalls, we're using the wrong approach for builder patterns
671
+ // The builder pattern should ALSO use the strategy name with suffix
672
+ function transformPositionCalls(text, importsToAdd) {
673
+ let result = text;
674
+ // Handle arrow functions with builder pattern: (paramName) => paramName.method()
675
+ const builderArrowMatch = result.match(/\((\w+)\)\s*=>\s*\1\.(\w+)\(/);
676
+ if (builderArrowMatch) {
677
+ const paramName = builderArrowMatch[1];
678
+ const methodName = builderArrowMatch[2];
679
+ let strategyFn;
680
+ // Check if it's a transforming preset
681
+ if (methodName in TRANSFORMING_PRESET_STRATEGIES) {
682
+ strategyFn = TRANSFORMING_PRESET_STRATEGIES[methodName];
683
+ importsToAdd.add(strategyFn);
684
+ }
685
+ else {
686
+ // Regular strategy methods get mapped to their strategy functions
687
+ strategyFn = STRATEGY_MAP[methodName];
688
+ if (strategyFn) {
689
+ importsToAdd.add(strategyFn);
690
+ }
691
+ }
692
+ if (strategyFn) {
693
+ const regex = new RegExp(`\\(${paramName}\\)\\s*=>\\s*${paramName}\\.\\w+\\(`, 'g');
694
+ result = result.replace(regex, `${strategyFn}(`);
695
+ return result;
696
+ }
697
+ }
698
+ // Handle regular method calls: overlayService.positions.method()
699
+ Object.entries(STRATEGY_MAP).forEach(([method, strategy]) => {
700
+ const pattern = new RegExp(`(?:this\\.)?\\w+\\.positions\\.${method}\\(`, 'g');
701
+ result = result.replace(pattern, `${strategy}(`);
702
+ if (result.includes(strategy)) {
703
+ importsToAdd.add(strategy);
704
+ }
705
+ });
706
+ // Handle transforming presets - use the OverlayStrategy suffix
707
+ Object.entries(TRANSFORMING_PRESET_STRATEGIES).forEach(([preset, strategy]) => {
708
+ const pattern = new RegExp(`(?:this\\.)?\\w+\\.positions\\.${preset}\\(`, 'g');
709
+ result = result.replace(pattern, `${strategy}(`);
710
+ if (result.includes(strategy)) {
711
+ importsToAdd.add(strategy);
712
+ }
713
+ });
714
+ // Handle mergeConfigs
715
+ result = result.replace(/(?:this\.)?\w+\.positions\.mergeConfigs\(/g, 'mergeOverlayBreakpointConfigs(');
716
+ if (result.includes('mergeOverlayBreakpointConfigs')) {
717
+ importsToAdd.add('mergeOverlayBreakpointConfigs');
718
+ }
719
+ return result;
720
+ }
721
+ function nodeContainsDefaults(node) {
722
+ let found = false;
723
+ function visit(n) {
724
+ if (found)
725
+ return;
726
+ if (isDefaultsUsage(n)) {
727
+ found = true;
728
+ return;
729
+ }
730
+ ts.forEachChild(n, visit);
731
+ }
732
+ visit(node);
733
+ return found;
734
+ }
735
+ function getStrategyVariableName(injectFn) {
736
+ const strategyName = injectFn.replace('inject', '').replace('Strategy', 'Strategy');
737
+ return strategyName.charAt(0).toLowerCase() + strategyName.slice(1);
738
+ }
739
+ function isOverlayBreakpointConfigEntryArrayType(type) {
740
+ return (ts.isArrayTypeNode(type) &&
741
+ ts.isTypeReferenceNode(type.elementType) &&
742
+ ts.isIdentifier(type.elementType.typeName) &&
743
+ type.elementType.typeName.text === 'OverlayBreakpointConfigEntry');
744
+ }
745
+ function isInsideOverlayHandlerCall(node) {
746
+ let current = node;
747
+ while (current) {
748
+ // Check if we're inside a call expression
749
+ if (ts.isCallExpression(current)) {
750
+ const expr = current.expression;
751
+ // Check if it's createOverlayHandler or createOverlayHandlerWithQueryParamLifecycle
752
+ if (ts.isIdentifier(expr)) {
753
+ const name = expr.text;
754
+ if (name === 'createOverlayHandler' || name === 'createOverlayHandlerWithQueryParamLifecycle') {
755
+ return true;
756
+ }
757
+ }
758
+ }
759
+ current = current.parent;
760
+ }
761
+ return false;
762
+ }
763
+ function transformBuilderBodyForDefaults(bodyCode, builderParamName, sourceFile) {
764
+ let code = bodyCode;
765
+ let usesMergeConfigs = false;
766
+ const strategiesUsed = new Set();
767
+ // Check if mergeConfigs is used
768
+ if (code.includes(`${builderParamName}.mergeConfigs(`)) {
769
+ usesMergeConfigs = true;
770
+ }
771
+ // Replace config: with strategy:
772
+ code = code.replace(/\bconfig:/g, 'strategy:');
773
+ // Replace builder.DEFAULTS.X with Xstrategy.build()
774
+ Object.entries(STRATEGY_INJECT_MAP).forEach(([strategyName, injectFn]) => {
775
+ const pattern = new RegExp(`${builderParamName}\\.DEFAULTS\\.${strategyName}`, 'g');
776
+ if (pattern.test(code)) {
777
+ const varName = getStrategyVariableName(injectFn);
778
+ code = code.replace(pattern, `${varName}.build()`);
779
+ strategiesUsed.add(injectFn);
780
+ }
781
+ });
782
+ // Replace builder.mergeConfigs(Xstrategy.build(), ...args) with Xstrategy.build(mergeOverlayBreakpointConfigs(...args))
783
+ Object.entries(STRATEGY_INJECT_MAP).forEach(([strategyName, injectFn]) => {
784
+ const varName = getStrategyVariableName(injectFn);
785
+ // Pattern: builder.mergeConfigs(dialogStrategy.build(), {...})
786
+ const pattern = new RegExp(`${builderParamName}\\.mergeConfigs\\(\\s*${varName}\\.build\\(\\)\\s*,\\s*`, 'g');
787
+ let match;
788
+ const replacements = [];
789
+ while ((match = pattern.exec(code)) !== null) {
790
+ const matchStart = match.index;
791
+ const afterComma = match.index + match[0].length;
792
+ // Find the closing paren by counting parentheses
793
+ let parenCount = 1;
794
+ let pos = afterComma;
795
+ while (pos < code.length && parenCount > 0) {
796
+ if (code[pos] === '(')
797
+ parenCount++;
798
+ if (code[pos] === ')')
799
+ parenCount--;
800
+ pos++;
801
+ }
802
+ const closingParenPos = pos - 1;
803
+ const args = code.substring(afterComma, closingParenPos).trim();
804
+ const cleanedArgs = args.replace(/,\s*$/, '');
805
+ const replacement = `${varName}.build(mergeOverlayBreakpointConfigs(${cleanedArgs}))`;
806
+ replacements.push({
807
+ start: matchStart,
808
+ end: pos,
809
+ replacement,
810
+ });
811
+ }
812
+ // Apply replacements in reverse order
813
+ replacements.reverse().forEach(({ start, end, replacement }) => {
814
+ code = code.substring(0, start) + replacement + code.substring(end);
815
+ });
816
+ });
817
+ return { code, usesMergeConfigs, strategiesUsed };
818
+ }
819
+ function transformBuilderPattern(text, importsToAdd) {
820
+ // This handles builder patterns that DON'T use DEFAULTS
821
+ // Example: (builder) => builder.dialog() or (builder) => [builder.bottomSheet(), ...]
822
+ // Extract the builder parameter name
823
+ const builderParamMatch = text.match(/\((\w+)\)\s*=>/);
824
+ if (!builderParamMatch) {
825
+ return text; // Not a builder pattern
826
+ }
827
+ const builderParamName = builderParamMatch[1];
828
+ // Get the function body (after =>)
829
+ const arrowIndex = text.indexOf('=>');
830
+ const body = text.substring(arrowIndex + 2).trim();
831
+ // Replace builder.method() calls with strategy functions
832
+ let transformedBody = body;
833
+ // Replace config: with strategy:
834
+ transformedBody = transformedBody.replace(/\bconfig:/g, 'strategy:');
835
+ // Handle regular strategy methods: builder.dialog() -> dialogOverlayStrategy()
836
+ Object.entries(STRATEGY_MAP).forEach(([method, strategy]) => {
837
+ const pattern = new RegExp(`${builderParamName}\\.${method}\\(`, 'g');
838
+ transformedBody = transformedBody.replace(pattern, `${strategy}(`);
839
+ if (transformedBody.includes(strategy)) {
840
+ importsToAdd.add(strategy);
841
+ }
842
+ });
843
+ // Handle transforming presets
844
+ Object.entries(TRANSFORMING_PRESET_STRATEGIES).forEach(([preset, strategy]) => {
845
+ const pattern = new RegExp(`${builderParamName}\\.${preset}\\(`, 'g');
846
+ transformedBody = transformedBody.replace(pattern, `${strategy}(`);
847
+ if (transformedBody.includes(strategy)) {
848
+ importsToAdd.add(strategy);
849
+ }
850
+ });
851
+ // Handle mergeConfigs
852
+ const mergePattern = new RegExp(`${builderParamName}\\.mergeConfigs\\(`, 'g');
853
+ transformedBody = transformedBody.replace(mergePattern, 'mergeOverlayBreakpointConfigs(');
854
+ if (transformedBody.includes('mergeOverlayBreakpointConfigs')) {
855
+ importsToAdd.add('mergeOverlayBreakpointConfigs');
856
+ }
857
+ // If the body was an array or object literal, keep it as is
858
+ // If it was a block with return, extract just the returned value
859
+ if (transformedBody.startsWith('{')) {
860
+ // Block body: { return [...] }
861
+ const returnMatch = transformedBody.match(/return\s+([\s\S]+?);?\s*}$/);
862
+ if (returnMatch) {
863
+ transformedBody = returnMatch[1].trim();
864
+ }
865
+ }
866
+ return transformedBody;
867
+ }
868
+ //# sourceMappingURL=overlay-positions.js.map