lwc-convert 1.0.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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +719 -0
  3. package/dist/cli/commands/aura.d.ts +6 -0
  4. package/dist/cli/commands/aura.d.ts.map +1 -0
  5. package/dist/cli/commands/aura.js +225 -0
  6. package/dist/cli/commands/aura.js.map +1 -0
  7. package/dist/cli/commands/vf.d.ts +6 -0
  8. package/dist/cli/commands/vf.d.ts.map +1 -0
  9. package/dist/cli/commands/vf.js +218 -0
  10. package/dist/cli/commands/vf.js.map +1 -0
  11. package/dist/cli/interactive.d.ts +20 -0
  12. package/dist/cli/interactive.d.ts.map +1 -0
  13. package/dist/cli/interactive.js +577 -0
  14. package/dist/cli/interactive.js.map +1 -0
  15. package/dist/cli/options.d.ts +21 -0
  16. package/dist/cli/options.d.ts.map +1 -0
  17. package/dist/cli/options.js +24 -0
  18. package/dist/cli/options.js.map +1 -0
  19. package/dist/generators/full-conversion.d.ts +41 -0
  20. package/dist/generators/full-conversion.d.ts.map +1 -0
  21. package/dist/generators/full-conversion.js +538 -0
  22. package/dist/generators/full-conversion.js.map +1 -0
  23. package/dist/generators/scaffolding.d.ts +40 -0
  24. package/dist/generators/scaffolding.d.ts.map +1 -0
  25. package/dist/generators/scaffolding.js +716 -0
  26. package/dist/generators/scaffolding.js.map +1 -0
  27. package/dist/generators/test-comparison.d.ts +47 -0
  28. package/dist/generators/test-comparison.d.ts.map +1 -0
  29. package/dist/generators/test-comparison.js +855 -0
  30. package/dist/generators/test-comparison.js.map +1 -0
  31. package/dist/generators/test-generator.d.ts +27 -0
  32. package/dist/generators/test-generator.d.ts.map +1 -0
  33. package/dist/generators/test-generator.js +385 -0
  34. package/dist/generators/test-generator.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +226 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mappings/aura-to-lwc.json +321 -0
  40. package/dist/mappings/vf-to-lwc.json +354 -0
  41. package/dist/parsers/aura/controller-parser.d.ts +36 -0
  42. package/dist/parsers/aura/controller-parser.d.ts.map +1 -0
  43. package/dist/parsers/aura/controller-parser.js +269 -0
  44. package/dist/parsers/aura/controller-parser.js.map +1 -0
  45. package/dist/parsers/aura/helper-parser.d.ts +21 -0
  46. package/dist/parsers/aura/helper-parser.d.ts.map +1 -0
  47. package/dist/parsers/aura/helper-parser.js +173 -0
  48. package/dist/parsers/aura/helper-parser.js.map +1 -0
  49. package/dist/parsers/aura/markup-parser.d.ts +59 -0
  50. package/dist/parsers/aura/markup-parser.d.ts.map +1 -0
  51. package/dist/parsers/aura/markup-parser.js +279 -0
  52. package/dist/parsers/aura/markup-parser.js.map +1 -0
  53. package/dist/parsers/aura/style-parser.d.ts +37 -0
  54. package/dist/parsers/aura/style-parser.d.ts.map +1 -0
  55. package/dist/parsers/aura/style-parser.js +151 -0
  56. package/dist/parsers/aura/style-parser.js.map +1 -0
  57. package/dist/parsers/vf/apex-parser.d.ts +51 -0
  58. package/dist/parsers/vf/apex-parser.d.ts.map +1 -0
  59. package/dist/parsers/vf/apex-parser.js +251 -0
  60. package/dist/parsers/vf/apex-parser.js.map +1 -0
  61. package/dist/parsers/vf/page-parser.d.ts +61 -0
  62. package/dist/parsers/vf/page-parser.d.ts.map +1 -0
  63. package/dist/parsers/vf/page-parser.js +403 -0
  64. package/dist/parsers/vf/page-parser.js.map +1 -0
  65. package/dist/transformers/aura-to-lwc/controller.d.ts +36 -0
  66. package/dist/transformers/aura-to-lwc/controller.d.ts.map +1 -0
  67. package/dist/transformers/aura-to-lwc/controller.js +372 -0
  68. package/dist/transformers/aura-to-lwc/controller.js.map +1 -0
  69. package/dist/transformers/aura-to-lwc/events.d.ts +47 -0
  70. package/dist/transformers/aura-to-lwc/events.d.ts.map +1 -0
  71. package/dist/transformers/aura-to-lwc/events.js +262 -0
  72. package/dist/transformers/aura-to-lwc/events.js.map +1 -0
  73. package/dist/transformers/aura-to-lwc/markup.d.ts +51 -0
  74. package/dist/transformers/aura-to-lwc/markup.d.ts.map +1 -0
  75. package/dist/transformers/aura-to-lwc/markup.js +465 -0
  76. package/dist/transformers/aura-to-lwc/markup.js.map +1 -0
  77. package/dist/transformers/vf-to-lwc/components.d.ts +40 -0
  78. package/dist/transformers/vf-to-lwc/components.d.ts.map +1 -0
  79. package/dist/transformers/vf-to-lwc/components.js +374 -0
  80. package/dist/transformers/vf-to-lwc/components.js.map +1 -0
  81. package/dist/transformers/vf-to-lwc/data-binding.d.ts +53 -0
  82. package/dist/transformers/vf-to-lwc/data-binding.d.ts.map +1 -0
  83. package/dist/transformers/vf-to-lwc/data-binding.js +660 -0
  84. package/dist/transformers/vf-to-lwc/data-binding.js.map +1 -0
  85. package/dist/transformers/vf-to-lwc/markup.d.ts +44 -0
  86. package/dist/transformers/vf-to-lwc/markup.d.ts.map +1 -0
  87. package/dist/transformers/vf-to-lwc/markup.js +816 -0
  88. package/dist/transformers/vf-to-lwc/markup.js.map +1 -0
  89. package/dist/utils/confidence-scorer.d.ts +100 -0
  90. package/dist/utils/confidence-scorer.d.ts.map +1 -0
  91. package/dist/utils/confidence-scorer.js +358 -0
  92. package/dist/utils/confidence-scorer.js.map +1 -0
  93. package/dist/utils/file-io.d.ts +62 -0
  94. package/dist/utils/file-io.d.ts.map +1 -0
  95. package/dist/utils/file-io.js +248 -0
  96. package/dist/utils/file-io.js.map +1 -0
  97. package/dist/utils/logger.d.ts +34 -0
  98. package/dist/utils/logger.d.ts.map +1 -0
  99. package/dist/utils/logger.js +130 -0
  100. package/dist/utils/logger.js.map +1 -0
  101. package/dist/utils/open-folder.d.ts +9 -0
  102. package/dist/utils/open-folder.d.ts.map +1 -0
  103. package/dist/utils/open-folder.js +76 -0
  104. package/dist/utils/open-folder.js.map +1 -0
  105. package/dist/utils/path-resolver.d.ts +29 -0
  106. package/dist/utils/path-resolver.d.ts.map +1 -0
  107. package/dist/utils/path-resolver.js +240 -0
  108. package/dist/utils/path-resolver.js.map +1 -0
  109. package/dist/utils/session-store.d.ts +158 -0
  110. package/dist/utils/session-store.d.ts.map +1 -0
  111. package/dist/utils/session-store.js +518 -0
  112. package/dist/utils/session-store.js.map +1 -0
  113. package/dist/utils/vf-controller-resolver.d.ts +36 -0
  114. package/dist/utils/vf-controller-resolver.d.ts.map +1 -0
  115. package/dist/utils/vf-controller-resolver.js +162 -0
  116. package/dist/utils/vf-controller-resolver.js.map +1 -0
  117. package/package.json +81 -0
@@ -0,0 +1,660 @@
1
+ "use strict";
2
+ /**
3
+ * Transform VF data binding patterns to LWC patterns
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.convertGlobalVariable = convertGlobalVariable;
7
+ exports.convertControllerProperty = convertControllerProperty;
8
+ exports.convertControllerMethod = convertControllerMethod;
9
+ exports.generateDataAccessLayer = generateDataAccessLayer;
10
+ /**
11
+ * Analyze VF page JavaScript for sforce.one.subscribe patterns that call actionFunctions
12
+ * Pattern 1: sforce.one.subscribe(channel, function(message) { actionFunctionName(message.property); })
13
+ * Pattern 2: sforce.one.subscribe(channel, (message) => actionFunc(message.prop))
14
+ * Pattern 3: Config-based: { actionFunction: refreshContactsFunction, lmsSubscribe: sforce.one.subscribe }
15
+ */
16
+ function analyzeLmsToActionFunctionBindings(customJavaScript, actionFunctions) {
17
+ const bindings = [];
18
+ const actionFunctionNames = new Set(actionFunctions.map(af => af.name));
19
+ for (const js of customJavaScript) {
20
+ // Pattern 1 & 2: Direct sforce.one.subscribe(channel, function(message) { actionFunc(message.prop); })
21
+ const subscribePattern = /sforce\.one\.subscribe\s*\(\s*([^,]+),\s*(?:function\s*\(\s*(\w+)\s*\)|(?:\(\s*(\w+)\s*\)\s*=>))/gi;
22
+ let match;
23
+ while ((match = subscribePattern.exec(js)) !== null) {
24
+ const channelRef = match[1].trim();
25
+ const messageParam = match[2] || match[3];
26
+ // Find the function body after this match
27
+ const afterMatch = js.substring(match.index + match[0].length);
28
+ // Look for actionFunction calls in the body
29
+ for (const afName of actionFunctionNames) {
30
+ // Pattern: actionFunctionName(message.propertyName) or actionFunctionName(message['propertyName'])
31
+ const afCallPattern = new RegExp(`${afName}\\s*\\(\\s*${messageParam}\\.(\\w+)|${afName}\\s*\\(\\s*${messageParam}\\[['"]([\\w]+)['"]\\]`, 'i');
32
+ const afMatch = afterMatch.match(afCallPattern);
33
+ if (afMatch) {
34
+ const messageProperty = afMatch[1] || afMatch[2];
35
+ bindings.push({
36
+ channelName: channelRef,
37
+ actionFunctionName: afName,
38
+ messageProperty: messageProperty || 'recordId', // Default to recordId
39
+ });
40
+ break; // Found a binding for this subscribe, move to next
41
+ }
42
+ // Also check for simpler pattern: just calling the actionFunction with message param
43
+ const simplePattern = new RegExp(`${afName}\\s*\\(\\s*${messageParam}\\s*\\)`, 'i');
44
+ if (simplePattern.test(afterMatch)) {
45
+ bindings.push({
46
+ channelName: channelRef,
47
+ actionFunctionName: afName,
48
+ messageProperty: 'recordId', // Default
49
+ });
50
+ break;
51
+ }
52
+ }
53
+ }
54
+ // Pattern 3: Config-based pattern where actionFunction and lmsSubscribe are passed to a config object
55
+ // Example: setPageConfigs({ messageChannel: '...', actionFunction: refreshContactsFunction, lmsSubscribe: sforce.one.subscribe })
56
+ // This indicates the external JS will wire them together, typically with message.recordId
57
+ if (js.includes('lmsSubscribe') && js.includes('sforce.one.subscribe')) {
58
+ // Look for actionFunction: functionName pattern in config objects
59
+ const configActionFunctionPattern = /actionFunction\s*:\s*(\w+)/gi;
60
+ let configMatch;
61
+ while ((configMatch = configActionFunctionPattern.exec(js)) !== null) {
62
+ const afName = configMatch[1];
63
+ if (actionFunctionNames.has(afName)) {
64
+ // Check if there's already a binding for this actionFunction
65
+ const existingBinding = bindings.find(b => b.actionFunctionName === afName);
66
+ if (!existingBinding) {
67
+ // This is a config-based pattern - the external JS typically uses message.recordId
68
+ bindings.push({
69
+ channelName: 'config-based',
70
+ actionFunctionName: afName,
71
+ messageProperty: 'recordId', // Standard convention
72
+ });
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return bindings;
79
+ }
80
+ /**
81
+ * Determine if a property should use @wire
82
+ */
83
+ function shouldUseWire(property) {
84
+ // Getters that just return data are good candidates for wire
85
+ // Setters or properties with side effects should use imperative
86
+ return property.hasGetter && !property.hasSetter && property.isPublic;
87
+ }
88
+ /**
89
+ * Determine if a method should use @wire vs imperative
90
+ */
91
+ function shouldMethodUseWire(method) {
92
+ // Wire is best for:
93
+ // - Methods that take no params or only recordId
94
+ // - Read-only operations
95
+ // - Cacheable methods
96
+ if (method.isCacheable)
97
+ return true;
98
+ // Methods with many parameters are better suited for imperative
99
+ if (method.parameters.length > 2)
100
+ return false;
101
+ // Methods that likely modify data (based on name) should be imperative
102
+ const modifyingPrefixes = ['save', 'update', 'delete', 'insert', 'create', 'remove', 'upsert'];
103
+ const nameLower = method.name.toLowerCase();
104
+ if (modifyingPrefixes.some((p) => nameLower.startsWith(p)))
105
+ return false;
106
+ // Default to wire for simple getters
107
+ return method.parameters.length <= 1 && method.returnType !== 'void';
108
+ }
109
+ /**
110
+ * Convert VF global variable expression to LWC
111
+ */
112
+ function convertGlobalVariable(expression, lmsBinding) {
113
+ const imports = [];
114
+ const warnings = [];
115
+ let code = '';
116
+ let pattern = 'property';
117
+ const expr = expression.reference;
118
+ // $CurrentPage.parameters.x or $CurrentPage.Name
119
+ if (expr.includes('$CurrentPage')) {
120
+ pattern = 'wire';
121
+ imports.push("import { CurrentPageReference } from 'lightning/navigation';");
122
+ const paramMatch = expr.match(/\$CurrentPage\.parameters\.(\w+)/);
123
+ const nameMatch = expr.match(/\$CurrentPage\.Name/i);
124
+ if (paramMatch) {
125
+ const paramName = paramMatch[1];
126
+ code = `// Access URL parameter: ${paramName}
127
+ @wire(CurrentPageReference)
128
+ pageRef;
129
+
130
+ get ${paramName}() {
131
+ return this.pageRef?.state?.${paramName};
132
+ }`;
133
+ }
134
+ else if (nameMatch) {
135
+ code = `// Access current page name
136
+ @wire(CurrentPageReference)
137
+ pageRef;
138
+
139
+ get pageName() {
140
+ return this.pageRef?.attributes?.name || this.pageRef?.state?.c__pageName || '';
141
+ }`;
142
+ }
143
+ else {
144
+ code = `@wire(CurrentPageReference)
145
+ pageRef;`;
146
+ }
147
+ }
148
+ // $MessageChannel.Channel_Name__c - Lightning Message Service
149
+ else if (expr.includes('$MessageChannel')) {
150
+ pattern = 'wire';
151
+ const channelMatch = expr.match(/\$MessageChannel\.([^}'"]+)/);
152
+ const channelName = channelMatch ? channelMatch[1] : 'Unknown_Channel__c';
153
+ const channelVar = channelName.replace(/__c$/i, '').toUpperCase() + '_CHANNEL';
154
+ imports.push("import { publish, subscribe, unsubscribe, MessageContext } from 'lightning/messageService';");
155
+ imports.push(`import ${channelVar} from '@salesforce/messageChannel/${channelName}';`);
156
+ // Generate handleMessage body based on LMS binding analysis
157
+ let handleMessageBody;
158
+ if (lmsBinding) {
159
+ handleMessageBody = `// Auto-detected from VF: calls ${lmsBinding.actionFunctionName} with message.${lmsBinding.messageProperty}
160
+ if (message.${lmsBinding.messageProperty}) {
161
+ this.${lmsBinding.actionFunctionName}(message.${lmsBinding.messageProperty})
162
+ .then(result => {
163
+ // Update component state with result
164
+ // Example: this.contactRecord = result;
165
+ })
166
+ .catch(error => {
167
+ this.handleError(error);
168
+ });
169
+ }`;
170
+ warnings.push(`LMS handleMessage auto-wired to ${lmsBinding.actionFunctionName}(message.${lmsBinding.messageProperty})`);
171
+ }
172
+ else {
173
+ handleMessageBody = `// TODO: Handle message payload
174
+ // Example: this.recordId = message.recordId;`;
175
+ }
176
+ code = `// Lightning Message Service: ${channelName}
177
+ @wire(MessageContext)
178
+ messageContext;
179
+
180
+ // Subscription reference for cleanup
181
+ _subscription = null;
182
+
183
+ // Lifecycle: Subscribe to message channel
184
+ connectedCallback() {
185
+ this.subscribeToMessageChannel();
186
+ }
187
+
188
+ // Lifecycle: Cleanup subscription
189
+ disconnectedCallback() {
190
+ this.unsubscribeFromMessageChannel();
191
+ }
192
+
193
+ // Subscribe to messages
194
+ subscribeToMessageChannel() {
195
+ if (!this._subscription) {
196
+ this._subscription = subscribe(
197
+ this.messageContext,
198
+ ${channelVar},
199
+ (message) => this.handleMessage(message)
200
+ );
201
+ }
202
+ }
203
+
204
+ // Unsubscribe from messages
205
+ unsubscribeFromMessageChannel() {
206
+ unsubscribe(this._subscription);
207
+ this._subscription = null;
208
+ }
209
+
210
+ // Handle incoming messages
211
+ handleMessage(message) {
212
+ ${handleMessageBody}
213
+ }
214
+
215
+ // Publish a message
216
+ publishMessage(payload) {
217
+ publish(this.messageContext, ${channelVar}, payload);
218
+ }`;
219
+ warnings.push(`Lightning Message Service detected: ${channelName} - connectedCallback/disconnectedCallback auto-generated`);
220
+ }
221
+ // $User.x
222
+ else if (expr.includes('$User')) {
223
+ pattern = 'import';
224
+ const fieldMatch = expr.match(/\$User\.(\w+)/);
225
+ if (fieldMatch) {
226
+ const field = fieldMatch[1];
227
+ if (field === 'Id') {
228
+ imports.push("import userId from '@salesforce/user/Id';");
229
+ code = `// Current user ID
230
+ userId = userId;`;
231
+ }
232
+ else {
233
+ imports.push("import { getRecord } from 'lightning/uiRecordApi';");
234
+ imports.push("import userId from '@salesforce/user/Id';");
235
+ imports.push(`import USER_${field.toUpperCase()}_FIELD from '@salesforce/schema/User.${field}';`);
236
+ code = `// Current user ${field}
237
+ @wire(getRecord, { recordId: userId, fields: [USER_${field.toUpperCase()}_FIELD] })
238
+ user;
239
+
240
+ get user${field}() {
241
+ return this.user?.data?.fields?.${field}?.value;
242
+ }`;
243
+ pattern = 'wire';
244
+ }
245
+ }
246
+ }
247
+ // $Label.namespace.labelName
248
+ else if (expr.includes('$Label')) {
249
+ pattern = 'import';
250
+ const labelMatch = expr.match(/\$Label\.(\w+)\.(\w+)/);
251
+ if (labelMatch) {
252
+ const [, namespace, labelName] = labelMatch;
253
+ imports.push(`import ${labelName} from '@salesforce/label/${namespace}.${labelName}';`);
254
+ code = `// Label: ${namespace}.${labelName}
255
+ label = {
256
+ ${labelName}
257
+ };`;
258
+ }
259
+ }
260
+ // $Resource.resourceName
261
+ else if (expr.includes('$Resource')) {
262
+ pattern = 'import';
263
+ const resourceMatch = expr.match(/\$Resource\.(\w+)/);
264
+ if (resourceMatch) {
265
+ const resourceName = resourceMatch[1];
266
+ imports.push(`import ${resourceName} from '@salesforce/resourceUrl/${resourceName}';`);
267
+ code = `// Static resource: ${resourceName}
268
+ ${resourceName}Url = ${resourceName};`;
269
+ }
270
+ }
271
+ // $Action
272
+ else if (expr.includes('$Action')) {
273
+ pattern = 'imperative';
274
+ warnings.push('$Action expressions need NavigationMixin for navigation');
275
+ imports.push("import { NavigationMixin } from 'lightning/navigation';");
276
+ code = `// Navigation action - use NavigationMixin
277
+ // Example: this[NavigationMixin.Navigate]({
278
+ // type: 'standard__recordPage',
279
+ // attributes: { recordId: id, actionName: 'view' }
280
+ // });`;
281
+ }
282
+ // $ObjectType
283
+ else if (expr.includes('$ObjectType')) {
284
+ pattern = 'import';
285
+ const objectMatch = expr.match(/\$ObjectType\.(\w+)\.fields\.(\w+)/);
286
+ if (objectMatch) {
287
+ const [, objectName, fieldName] = objectMatch;
288
+ imports.push(`import ${fieldName.toUpperCase()}_FIELD from '@salesforce/schema/${objectName}.${fieldName}';`);
289
+ code = `// Field reference: ${objectName}.${fieldName}
290
+ // Use in wire adapter or getFieldValue`;
291
+ }
292
+ }
293
+ else {
294
+ warnings.push(`Unknown global variable pattern: ${expr}`);
295
+ code = `// TODO: Convert ${expr}`;
296
+ }
297
+ return {
298
+ vfExpression: expression.original,
299
+ lwcPattern: pattern,
300
+ code,
301
+ imports,
302
+ warnings,
303
+ };
304
+ }
305
+ /**
306
+ * Convert Apex controller property to LWC
307
+ */
308
+ function convertControllerProperty(property, controllerName) {
309
+ const warnings = [];
310
+ const isWireCandidate = shouldUseWire(property);
311
+ let code = '';
312
+ if (isWireCandidate) {
313
+ // Use wire adapter
314
+ code = `// Property: ${property.name} (from ${controllerName})
315
+ // Option 1: Wire to Apex getter
316
+ @wire(get${capitalize(property.name)})
317
+ ${property.name};
318
+
319
+ // Option 2: Simple reactive property
320
+ ${property.name};
321
+
322
+ // If using wire, add this to imports:
323
+ // import get${capitalize(property.name)} from '@salesforce/apex/${controllerName}.get${capitalize(property.name)}';`;
324
+ }
325
+ else {
326
+ // Use imperative or reactive property
327
+ code = `// Property: ${property.name}
328
+ ${property.name}${property.initialValue ? ` = ${property.initialValue}` : ''};`;
329
+ if (property.hasSetter) {
330
+ code += `
331
+
332
+ // This property had a setter in VF - update via method call
333
+ async update${capitalize(property.name)}(value) {
334
+ this.${property.name} = value;
335
+ // TODO: If this needs to persist, call Apex method
336
+ }`;
337
+ }
338
+ }
339
+ return {
340
+ property,
341
+ lwcCode: code,
342
+ isWireCandidate,
343
+ warnings,
344
+ };
345
+ }
346
+ /**
347
+ * Convert Apex controller method to LWC
348
+ */
349
+ function convertControllerMethod(method, controllerName) {
350
+ const warnings = [];
351
+ const isWireCandidate = shouldMethodUseWire(method);
352
+ let importStatement = '';
353
+ let code = '';
354
+ // Check if method already has @AuraEnabled
355
+ if (!method.isAuraEnabled && !method.isRemoteAction) {
356
+ warnings.push(`Method "${method.name}" needs @AuraEnabled annotation to be called from LWC`);
357
+ }
358
+ importStatement = `import ${method.name} from '@salesforce/apex/${controllerName}.${method.name}';`;
359
+ if (isWireCandidate) {
360
+ // Generate wire-based code
361
+ if (method.parameters.length === 0) {
362
+ code = `// Wired Apex method: ${method.name}
363
+ @wire(${method.name})
364
+ ${method.name}Result;
365
+
366
+ get ${method.name}Data() {
367
+ return this.${method.name}Result?.data;
368
+ }
369
+
370
+ get ${method.name}Error() {
371
+ return this.${method.name}Result?.error;
372
+ }`;
373
+ }
374
+ else {
375
+ // Wire with parameters
376
+ const params = method.parameters
377
+ .map((p) => `${p.name}: this.${p.name}`)
378
+ .join(', ');
379
+ code = `// Wired Apex method: ${method.name}
380
+ @wire(${method.name}, { ${params} })
381
+ ${method.name}Result;`;
382
+ }
383
+ if (method.isCacheable) {
384
+ code += `
385
+
386
+ // Note: This method is cacheable. Use refreshApex() to refresh:
387
+ // import { refreshApex } from '@salesforce/apex';
388
+ // await refreshApex(this.${method.name}Result);`;
389
+ }
390
+ }
391
+ else {
392
+ // Generate imperative call code
393
+ const params = method.parameters.map((p) => p.name).join(', ');
394
+ const paramObj = method.parameters.length > 0
395
+ ? `{ ${method.parameters.map((p) => p.name).join(', ')} }`
396
+ : '';
397
+ code = `// Imperative Apex call: ${method.name}
398
+ async call${capitalize(method.name)}(${params}) {
399
+ try {
400
+ const result = await ${method.name}(${paramObj});
401
+ return result;
402
+ } catch (error) {
403
+ console.error('Error calling ${method.name}:', error);
404
+ throw error;
405
+ }
406
+ }`;
407
+ }
408
+ return {
409
+ method,
410
+ lwcCode: code,
411
+ importStatement,
412
+ isWireCandidate,
413
+ warnings,
414
+ };
415
+ }
416
+ /**
417
+ * Generate complete data access layer for VF page
418
+ */
419
+ function generateDataAccessLayer(vfPage, apexController) {
420
+ const imports = [];
421
+ const properties = [];
422
+ const methods = [];
423
+ const wireDeclarations = [];
424
+ const warnings = [];
425
+ // Analyze VF page JavaScript for LMS-to-actionFunction bindings
426
+ const lmsBindings = analyzeLmsToActionFunctionBindings(vfPage.customJavaScript, vfPage.actionFunctions);
427
+ // Process global variable expressions
428
+ const processedExpressions = new Set();
429
+ for (const expr of vfPage.expressions) {
430
+ // Avoid processing the same expression multiple times
431
+ if (processedExpressions.has(expr.reference))
432
+ continue;
433
+ processedExpressions.add(expr.reference);
434
+ if (expr.type === 'global' || expr.reference.includes('$MessageChannel')) {
435
+ // Find matching LMS binding for this MessageChannel
436
+ let matchingBinding;
437
+ if (expr.reference.includes('$MessageChannel')) {
438
+ matchingBinding = lmsBindings.find(_b => {
439
+ // The binding channelName might be a variable reference or the actual channel name
440
+ return lmsBindings.length > 0; // Use first binding if any exist
441
+ });
442
+ if (lmsBindings.length > 0 && !matchingBinding) {
443
+ matchingBinding = lmsBindings[0]; // Default to first binding
444
+ }
445
+ }
446
+ const conversion = convertGlobalVariable(expr, matchingBinding);
447
+ imports.push(...conversion.imports);
448
+ if (conversion.lwcPattern === 'wire') {
449
+ wireDeclarations.push(conversion.code);
450
+ }
451
+ else {
452
+ properties.push(conversion.code);
453
+ }
454
+ warnings.push(...conversion.warnings);
455
+ }
456
+ }
457
+ // Process Remote Objects from VF components
458
+ const remoteObjects = findRemoteObjects(vfPage.components);
459
+ if (remoteObjects.length > 0) {
460
+ for (const ro of remoteObjects) {
461
+ const objectNameLower = ro.objectName.toLowerCase();
462
+ // Generate wire adapter scaffolding
463
+ wireDeclarations.push(`// Wire adapter for ${ro.objectName} (converted from apex:remoteObjectModel)
464
+ // TODO: Create Apex controller method to replace Remote Objects
465
+ //
466
+ // Example Apex controller:
467
+ // @AuraEnabled(cacheable=true)
468
+ // public static List<${ro.objectName}> get${ro.objectName}s() {
469
+ // return [SELECT ${ro.fields} FROM ${ro.objectName} LIMIT 10];
470
+ // }
471
+ //
472
+ // @wire(get${ro.objectName}s)
473
+ // ${objectNameLower}s;
474
+ //
475
+ // Or use lightning/uiRecordApi for standard objects:
476
+ // import { getRecords } from 'lightning/uiRecordApi';
477
+ `);
478
+ imports.push(`// import get${ro.objectName}s from '@salesforce/apex/YourController.get${ro.objectName}s';`);
479
+ warnings.push(`Remote Object "${ro.objectName}" detected - create Apex controller method to replace`);
480
+ }
481
+ }
482
+ // Process Apex controller if available
483
+ if (apexController) {
484
+ // Process properties
485
+ for (const prop of apexController.properties) {
486
+ const conversion = convertControllerProperty(prop, apexController.className);
487
+ if (conversion.isWireCandidate) {
488
+ wireDeclarations.push(conversion.lwcCode);
489
+ }
490
+ else {
491
+ properties.push(conversion.lwcCode);
492
+ }
493
+ warnings.push(...conversion.warnings);
494
+ }
495
+ // Process methods
496
+ for (const method of apexController.methods) {
497
+ if (method.isPublic || method.isAuraEnabled || method.isRemoteAction) {
498
+ const conversion = convertControllerMethod(method, apexController.className);
499
+ imports.push(conversion.importStatement);
500
+ if (conversion.isWireCandidate) {
501
+ wireDeclarations.push(conversion.lwcCode);
502
+ }
503
+ else {
504
+ methods.push(conversion.lwcCode);
505
+ }
506
+ warnings.push(...conversion.warnings);
507
+ }
508
+ }
509
+ // Note about SOQL queries
510
+ if (apexController.soqlQueries.length > 0) {
511
+ warnings.push(`Controller has ${apexController.soqlQueries.length} SOQL queries - ensure FLS/CRUD checks are in place`);
512
+ }
513
+ // Note about DML operations
514
+ if (apexController.dmlOperations.length > 0) {
515
+ warnings.push(`Controller has DML operations (${apexController.dmlOperations.join(', ')}) - these should use imperative calls`);
516
+ }
517
+ }
518
+ // Process remote actions from VF page
519
+ for (const ra of vfPage.remoteActions) {
520
+ warnings.push(`RemoteAction "${ra.controller}.${ra.method}" - add @AuraEnabled annotation and import`);
521
+ imports.push(`// import ${ra.method} from '@salesforce/apex/${ra.controller}.${ra.method}';`);
522
+ }
523
+ // Process action functions
524
+ // Action functions with parameters should use imperative Apex pattern instead of wire
525
+ for (const af of vfPage.actionFunctions) {
526
+ // Check if this action function has parameters (apex:param children)
527
+ const hasParams = findActionFunctionParams(vfPage.components, af.name);
528
+ if (hasParams.length > 0) {
529
+ // Use imperative pattern for actionFunction with params
530
+ const paramNames = hasParams.map(p => p.name);
531
+ const paramDeclaration = paramNames.join(', ');
532
+ const paramObject = paramNames.length > 0
533
+ ? `{ ${paramNames.join(', ')} }`
534
+ : '';
535
+ // Extract the method name from the action (e.g., "{!doSomething}" -> "doSomething")
536
+ const methodName = extractMethodFromAction(af.action);
537
+ methods.push(`// Converted from apex:actionFunction "${af.name}" (imperative - has parameters)
538
+ // Parameters: ${paramNames.join(', ')}
539
+ async ${af.name}(${paramDeclaration}) {
540
+ this.isLoading = true;
541
+ try {
542
+ const result = await ${methodName || af.name}(${paramObject});
543
+ ${af.rerender ? `// Originally rerendered: ${af.rerender} - update reactive properties to trigger re-render` : ''}
544
+ ${af.oncomplete ? `// Original oncomplete callback: ${af.oncomplete}` : ''}
545
+ return result;
546
+ } catch (error) {
547
+ console.error('Error calling ${af.name}:', error);
548
+ throw error;
549
+ } finally {
550
+ this.isLoading = false;
551
+ }
552
+ }`);
553
+ // Add import for the Apex method if we can determine the controller
554
+ if (methodName && apexController) {
555
+ imports.push(`import ${methodName} from '@salesforce/apex/${apexController.className}.${methodName}';`);
556
+ }
557
+ else if (methodName) {
558
+ imports.push(`// import ${methodName} from '@salesforce/apex/YourController.${methodName}';`);
559
+ }
560
+ warnings.push(`actionFunction "${af.name}" has ${hasParams.length} parameters - using imperative Apex pattern`);
561
+ }
562
+ else {
563
+ // Simple action function without params can potentially use wire (but still default to imperative for consistency)
564
+ methods.push(`// Converted from apex:actionFunction "${af.name}"
565
+ async ${af.name}() {
566
+ this.isLoading = true;
567
+ try {
568
+ // TODO: Implement - original action: ${af.action}
569
+ ${af.rerender ? `// Originally rerendered: ${af.rerender}` : ''}
570
+ ${af.oncomplete ? `// Had oncomplete: ${af.oncomplete}` : ''}
571
+ } catch (error) {
572
+ console.error('Error calling ${af.name}:', error);
573
+ throw error;
574
+ } finally {
575
+ this.isLoading = false;
576
+ }
577
+ }`);
578
+ }
579
+ }
580
+ // Deduplicate imports
581
+ const uniqueImports = [...new Set(imports)];
582
+ return {
583
+ imports: uniqueImports,
584
+ properties,
585
+ methods,
586
+ wireDeclarations,
587
+ warnings,
588
+ };
589
+ }
590
+ /**
591
+ * Helper: capitalize first letter
592
+ */
593
+ function capitalize(str) {
594
+ return str.charAt(0).toUpperCase() + str.slice(1);
595
+ }
596
+ function findRemoteObjects(components) {
597
+ const remoteObjects = [];
598
+ function traverse(comp) {
599
+ if (comp.name.toLowerCase() === 'apex:remoteobjectmodel') {
600
+ const objectName = comp.attributes.name || 'Unknown';
601
+ const fields = comp.attributes.fields || 'Id';
602
+ remoteObjects.push({ objectName, fields });
603
+ }
604
+ // Check children
605
+ for (const child of comp.children) {
606
+ traverse(child);
607
+ }
608
+ }
609
+ for (const comp of components) {
610
+ traverse(comp);
611
+ }
612
+ return remoteObjects;
613
+ }
614
+ function findActionFunctionParams(components, actionFunctionName) {
615
+ const params = [];
616
+ function traverse(comp) {
617
+ const lowerName = comp.name.toLowerCase();
618
+ // Found the action function
619
+ if (lowerName === 'apex:actionfunction' && comp.attributes.name === actionFunctionName) {
620
+ // Look for apex:param children
621
+ for (const child of comp.children) {
622
+ if (child.name.toLowerCase() === 'apex:param') {
623
+ const param = {
624
+ name: child.attributes.name || child.attributes.assignto?.replace(/\{!|\}/g, '') || 'param',
625
+ value: child.attributes.value,
626
+ assignTo: child.attributes.assignto,
627
+ };
628
+ params.push(param);
629
+ }
630
+ }
631
+ return;
632
+ }
633
+ // Keep searching in children
634
+ for (const child of comp.children) {
635
+ traverse(child);
636
+ }
637
+ }
638
+ for (const comp of components) {
639
+ traverse(comp);
640
+ }
641
+ return params;
642
+ }
643
+ /**
644
+ * Extract method name from VF action expression
645
+ * e.g., "{!doSomething}" -> "doSomething"
646
+ * "{!controller.myMethod}" -> "myMethod"
647
+ */
648
+ function extractMethodFromAction(action) {
649
+ if (!action)
650
+ return null;
651
+ // Remove {! and } delimiters
652
+ const cleaned = action.replace(/\{!|\}/g, '').trim();
653
+ // Handle controller.method pattern
654
+ if (cleaned.includes('.')) {
655
+ const parts = cleaned.split('.');
656
+ return parts[parts.length - 1];
657
+ }
658
+ return cleaned;
659
+ }
660
+ //# sourceMappingURL=data-binding.js.map