@builder.io/mitosis 0.9.3 → 0.9.5

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,412 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertJsFunctionToSwift = exports.extractFunctionSignature = exports.getBindingType = exports.camelToSnakeCase = exports.getForEachParams = exports.isForEachBlock = exports.needsScrollView = exports.getEventHandlerName = exports.getStatePropertyTypeAnnotation = exports.cssToSwiftUIModifiers = exports.jsxElementToSwiftUIView = exports.getSwiftType = exports.stripStateAndProps = exports.ensureSwiftStringFormat = exports.convertConsoleLogToPrint = void 0;
4
+ const capitalize_1 = require("../../helpers/capitalize");
5
+ const strip_state_and_props_refs_1 = require("../../helpers/strip-state-and-props-refs");
6
+ // TODO(kyle): use babel here to do ast
7
+ const convertConsoleLogToPrint = (code) => {
8
+ if (!code)
9
+ return code;
10
+ // Match console.log statements with various argument patterns
11
+ return code.replace(/console\.log\s*\(\s*(.*?)\s*\)/g, (match, args) => {
12
+ // Handle empty console.log()
13
+ if (!args.trim()) {
14
+ return 'print()';
15
+ }
16
+ // Simple handling for basic console.log calls
17
+ // For complex cases, we'd need a more sophisticated parser
18
+ return `print(${(0, exports.ensureSwiftStringFormat)(args)})`;
19
+ });
20
+ };
21
+ exports.convertConsoleLogToPrint = convertConsoleLogToPrint;
22
+ // Helper function to ensure Swift strings use double quotes
23
+ const ensureSwiftStringFormat = (code) => {
24
+ if (!code)
25
+ return code;
26
+ // We need a more reliable approach to handle nested quotes
27
+ // This uses a state machine approach to track whether we're inside double quotes
28
+ let result = '';
29
+ let insideDoubleQuotes = false;
30
+ for (let i = 0; i < code.length; i++) {
31
+ const char = code[i];
32
+ const prevChar = i > 0 ? code[i - 1] : '';
33
+ // Handle quote state tracking
34
+ if (char === '"' && prevChar !== '\\') {
35
+ insideDoubleQuotes = !insideDoubleQuotes;
36
+ result += char;
37
+ }
38
+ // Only replace single quotes when not inside double quotes
39
+ else if (char === "'" && prevChar !== '\\' && !insideDoubleQuotes) {
40
+ // Start of a single-quoted string
41
+ result += '"';
42
+ // Find the end of the single-quoted string, accounting for escaped quotes
43
+ let j = i + 1;
44
+ while (j < code.length) {
45
+ if (code[j] === "'" && code[j - 1] !== '\\') {
46
+ break;
47
+ }
48
+ j++;
49
+ }
50
+ // Add the string content
51
+ result += code.substring(i + 1, j);
52
+ // Add closing double quote if we found the end
53
+ if (j < code.length) {
54
+ result += '"';
55
+ i = j; // Skip to the end of the single-quoted string
56
+ }
57
+ else {
58
+ // If no closing quote was found, just add the single quote as is
59
+ result = result.substring(0, result.length - 1) + "'";
60
+ }
61
+ }
62
+ else {
63
+ result += char;
64
+ }
65
+ }
66
+ return result;
67
+ };
68
+ exports.ensureSwiftStringFormat = ensureSwiftStringFormat;
69
+ const stripStateAndProps = ({ json, options, }) => {
70
+ return (code) => {
71
+ // Convert console.log statements to Swift print
72
+ code = (0, exports.convertConsoleLogToPrint)(code);
73
+ // Ensure Swift strings use double quotes
74
+ code = (0, exports.ensureSwiftStringFormat)(code);
75
+ // In Swift, we use self.propertyName for accessing properties
76
+ return (0, strip_state_and_props_refs_1.stripStateAndPropsRefs)(code, {
77
+ includeState: true,
78
+ includeProps: true,
79
+ replaceWith: (name) => {
80
+ // In Swift, we access properties with self.propertyName
81
+ return `self.${name}`;
82
+ },
83
+ });
84
+ };
85
+ };
86
+ exports.stripStateAndProps = stripStateAndProps;
87
+ const getSwiftType = (type) => {
88
+ if (!type)
89
+ return 'Any';
90
+ // Handle array types with proper Swift syntax
91
+ if (type.includes('Array<') || type.includes('[]') || type.toLowerCase().startsWith('array')) {
92
+ // Extract the element type from Array<ElementType>
93
+ let elementType = 'Any';
94
+ // Match different array type patterns
95
+ const arrayMatch = type.match(/Array<([^>]+)>/i) ||
96
+ type.match(/([^[\]]+)\[\]/i) ||
97
+ type.match(/array\s*<([^>]+)>/i);
98
+ if (arrayMatch && arrayMatch[1]) {
99
+ elementType = (0, exports.getSwiftType)(arrayMatch[1].trim());
100
+ }
101
+ // Return Swift array type: [ElementType]
102
+ return `[${elementType}]`;
103
+ }
104
+ // Handle primitive types
105
+ switch (type.toLowerCase()) {
106
+ case 'string':
107
+ return 'String';
108
+ case 'number':
109
+ return 'Double';
110
+ case 'boolean':
111
+ case 'bool':
112
+ return 'Bool';
113
+ case 'any':
114
+ return 'Any';
115
+ case 'void':
116
+ return 'Void';
117
+ case 'object':
118
+ return '[String: Any]';
119
+ case 'null':
120
+ case 'undefined':
121
+ return 'Optional<Any>';
122
+ default:
123
+ // For complex types, return as is with first letter capitalized
124
+ return type.charAt(0).toUpperCase() + type.slice(1);
125
+ }
126
+ };
127
+ exports.getSwiftType = getSwiftType;
128
+ const jsxElementToSwiftUIView = (tagName) => {
129
+ // Map JSX/HTML elements to SwiftUI components
130
+ switch (tagName.toLowerCase()) {
131
+ case 'div':
132
+ return 'VStack';
133
+ case 'span':
134
+ case 'p':
135
+ case 'h1':
136
+ case 'h2':
137
+ case 'h3':
138
+ case 'h4':
139
+ case 'h5':
140
+ case 'h6':
141
+ return 'Text';
142
+ case 'img':
143
+ return 'Image';
144
+ case 'input':
145
+ return 'TextField';
146
+ case 'button':
147
+ return 'Button';
148
+ case 'a':
149
+ return 'Link';
150
+ case 'ul':
151
+ return 'List';
152
+ case 'li':
153
+ return 'Text'; // Will be wrapped in List
154
+ case 'form':
155
+ return 'Form';
156
+ case 'select':
157
+ return 'Picker';
158
+ case 'option':
159
+ return 'Text'; // Options in SwiftUI are part of the Picker content
160
+ default:
161
+ // For custom components or unrecognized tags
162
+ return (0, capitalize_1.capitalize)(tagName);
163
+ }
164
+ };
165
+ exports.jsxElementToSwiftUIView = jsxElementToSwiftUIView;
166
+ const cssToSwiftUIModifiers = (style) => {
167
+ const modifiers = [];
168
+ // Map CSS properties to SwiftUI modifiers
169
+ Object.entries(style).forEach(([key, value]) => {
170
+ switch (key) {
171
+ case 'backgroundColor':
172
+ modifiers.push(`.background(Color("${value}"))`);
173
+ break;
174
+ case 'color':
175
+ modifiers.push(`.foregroundColor(Color("${value}"))`);
176
+ break;
177
+ case 'fontSize':
178
+ const fontSize = parseInt(value);
179
+ if (!isNaN(fontSize)) {
180
+ modifiers.push(`.font(.system(size: ${fontSize}))`);
181
+ }
182
+ break;
183
+ case 'fontWeight':
184
+ modifiers.push(`.fontWeight(.${value})`);
185
+ break;
186
+ case 'padding':
187
+ modifiers.push(`.padding(${value})`);
188
+ break;
189
+ case 'margin':
190
+ // Swift doesn't have direct margin equivalent, we'll use padding
191
+ modifiers.push(`// Note: 'margin' converted to padding: ${value}`);
192
+ modifiers.push(`.padding(${value})`);
193
+ break;
194
+ case 'width':
195
+ modifiers.push(`.frame(width: ${value})`);
196
+ break;
197
+ case 'height':
198
+ modifiers.push(`.frame(height: ${value})`);
199
+ break;
200
+ // Add more CSS to SwiftUI modifier mappings as needed
201
+ default:
202
+ modifiers.push(`// Unmapped style: ${key}: ${value}`);
203
+ }
204
+ });
205
+ return modifiers;
206
+ };
207
+ exports.cssToSwiftUIModifiers = cssToSwiftUIModifiers;
208
+ const getStatePropertyTypeAnnotation = (propertyType, type) => {
209
+ // Use appropriate SwiftUI property wrappers
210
+ switch (propertyType) {
211
+ case 'reactive':
212
+ // For reactive state, use @State for simple values
213
+ // @Observable would be used for classes but requires Swift 5.9+/iOS 17+
214
+ return `@State private var`;
215
+ case 'normal':
216
+ // For normal state, use @State for simple values
217
+ return `@State private var`;
218
+ default:
219
+ // For non-reactive values, use a regular property
220
+ return `var`;
221
+ }
222
+ };
223
+ exports.getStatePropertyTypeAnnotation = getStatePropertyTypeAnnotation;
224
+ const getEventHandlerName = (eventName) => {
225
+ switch (eventName) {
226
+ case 'onClick':
227
+ return 'onTapGesture';
228
+ case 'onChange':
229
+ return 'onChange';
230
+ case 'onInput':
231
+ return 'onEditingChanged';
232
+ case 'onBlur':
233
+ return 'onSubmit';
234
+ case 'onFocus':
235
+ return 'onEditingChanged';
236
+ default:
237
+ return eventName;
238
+ }
239
+ };
240
+ exports.getEventHandlerName = getEventHandlerName;
241
+ const needsScrollView = (json) => {
242
+ // Check if overflow property indicates scrolling
243
+ if (json.properties.style) {
244
+ try {
245
+ const styleObj = JSON.parse(json.properties.style);
246
+ return (styleObj.overflow === 'auto' ||
247
+ styleObj.overflow === 'scroll' ||
248
+ styleObj.overflowY === 'auto' ||
249
+ styleObj.overflowY === 'scroll' ||
250
+ styleObj.overflowX === 'auto' ||
251
+ styleObj.overflowX === 'scroll');
252
+ }
253
+ catch (e) {
254
+ // If style can't be parsed, check for overflow directly in the style string
255
+ const styleStr = json.properties.style;
256
+ return (styleStr.includes('overflow:auto') ||
257
+ styleStr.includes('overflow:scroll') ||
258
+ styleStr.includes('overflow-y:auto') ||
259
+ styleStr.includes('overflow-y:scroll') ||
260
+ styleStr.includes('overflow-x:auto') ||
261
+ styleStr.includes('overflow-x:scroll'));
262
+ }
263
+ }
264
+ return false;
265
+ };
266
+ exports.needsScrollView = needsScrollView;
267
+ const isForEachBlock = (json) => {
268
+ var _a;
269
+ // Check if this is a ForEach binding using the bindings.each pattern
270
+ return !!((_a = json.bindings.each) === null || _a === void 0 ? void 0 : _a.code);
271
+ };
272
+ exports.isForEachBlock = isForEachBlock;
273
+ const getForEachParams = (json, processCode) => {
274
+ var _a, _b;
275
+ if (!((_a = json.bindings.each) === null || _a === void 0 ? void 0 : _a.code)) {
276
+ return { collection: '', itemName: 'item', indexName: null };
277
+ }
278
+ const eachCode = json.bindings.each.code;
279
+ let itemName = 'item';
280
+ let indexName = null;
281
+ // Extract collection, item name, and index name from each binding
282
+ try {
283
+ // Parse expressions like: items.map(item => ...)
284
+ // or items.map((item, index) => ...)
285
+ const match = eachCode.match(/(\w+)\.map\(\s*(?:\()?([^,)]+)(?:,\s*([^)]+))?\)?/);
286
+ if (match) {
287
+ const collection = processCode(match[1]);
288
+ itemName = match[2].trim();
289
+ indexName = ((_b = match[3]) === null || _b === void 0 ? void 0 : _b.trim()) || null;
290
+ return { collection, itemName, indexName };
291
+ }
292
+ // Fallback to the whole code as collection if pattern doesn't match
293
+ return {
294
+ collection: processCode(eachCode),
295
+ itemName,
296
+ indexName,
297
+ };
298
+ }
299
+ catch (e) {
300
+ console.warn('Failed to parse each binding:', eachCode);
301
+ return {
302
+ collection: processCode(eachCode),
303
+ itemName,
304
+ indexName,
305
+ };
306
+ }
307
+ };
308
+ exports.getForEachParams = getForEachParams;
309
+ const camelToSnakeCase = (str) => {
310
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
311
+ };
312
+ exports.camelToSnakeCase = camelToSnakeCase;
313
+ const getBindingType = (key) => {
314
+ if (key.startsWith('bind:')) {
315
+ return key.substring(5);
316
+ }
317
+ return key;
318
+ };
319
+ exports.getBindingType = getBindingType;
320
+ /**
321
+ * Extract function signature information from JavaScript function code
322
+ */
323
+ const extractFunctionSignature = (code) => {
324
+ // Default values
325
+ let name = '';
326
+ let params = [];
327
+ let returnType = 'Void';
328
+ let body = '';
329
+ // Extract function name, parameters, and body
330
+ const funcMatch = code.match(/(?:function\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)?\s*\(([^)]*)\)\s*(?:=>)?\s*(?:{([\s\S]*)}|(.*))/);
331
+ if (funcMatch) {
332
+ name = funcMatch[1] || '';
333
+ // Extract parameters
334
+ const paramsStr = funcMatch[2].trim();
335
+ if (paramsStr) {
336
+ params = paramsStr.split(',').map((param) => {
337
+ // Handle TypeScript-style parameter types if present
338
+ const paramParts = param.trim().split(':');
339
+ const paramName = paramParts[0].trim();
340
+ const paramType = paramParts.length > 1 ? (0, exports.getSwiftType)(paramParts[1].trim()) : 'Any';
341
+ return { name: paramName, type: paramType };
342
+ });
343
+ }
344
+ // Extract function body
345
+ body = funcMatch[3] || funcMatch[4] || '';
346
+ // Try to determine return type from TypeScript annotations or infer from return statements
347
+ const returnTypeMatch = code.match(/\)\s*:\s*([^{]+)/);
348
+ if (returnTypeMatch) {
349
+ returnType = (0, exports.getSwiftType)(returnTypeMatch[1].trim());
350
+ }
351
+ else if (body.includes('return')) {
352
+ // Try to infer from return statements
353
+ const returnValueMatch = body.match(/return\s+(["'].*["']|true|false|\d+|\d+\.\d+|\[.*\])/);
354
+ if (returnValueMatch) {
355
+ const returnValue = returnValueMatch[1];
356
+ if (returnValue.startsWith('"') || returnValue.startsWith("'")) {
357
+ returnType = 'String';
358
+ }
359
+ else if (returnValue === 'true' || returnValue === 'false') {
360
+ returnType = 'Bool';
361
+ }
362
+ else if (returnValue.match(/^\d+$/)) {
363
+ returnType = 'Int';
364
+ }
365
+ else if (returnValue.match(/^\d+\.\d+$/)) {
366
+ returnType = 'Double';
367
+ }
368
+ else if (returnValue.startsWith('[')) {
369
+ returnType = '[Any]';
370
+ }
371
+ }
372
+ }
373
+ }
374
+ return { name, params, returnType, body };
375
+ };
376
+ exports.extractFunctionSignature = extractFunctionSignature;
377
+ /**
378
+ * Convert JavaScript function code to Swift function syntax
379
+ */
380
+ const convertJsFunctionToSwift = (code, functionName) => {
381
+ // Extract the function signature
382
+ const { name, params, returnType, body } = (0, exports.extractFunctionSignature)(code);
383
+ // Use provided name or extracted name
384
+ const finalName = functionName || name || 'function';
385
+ // Convert function body to Swift
386
+ let swiftBody = body
387
+ // Convert variable declarations
388
+ .replace(/\bvar\s+(\w+)/g, 'var $1')
389
+ .replace(/\blet\s+(\w+)/g, 'let $1')
390
+ .replace(/\bconst\s+(\w+)/g, 'let $1')
391
+ // Convert common array methods
392
+ .replace(/\.push\(/g, '.append(')
393
+ .replace(/\.map\(/g, '.map(')
394
+ .replace(/\.filter\(/g, '.filter(')
395
+ .replace(/\.includes\(/g, '.contains(')
396
+ .replace(/\.indexOf\(/g, '.firstIndex(of: ')
397
+ // Convert null/undefined checks
398
+ .replace(/=== null/g, '== nil')
399
+ .replace(/!== null/g, '!= nil')
400
+ .replace(/=== undefined/g, '== nil')
401
+ .replace(/!== undefined/g, '!= nil')
402
+ // Convert console.log
403
+ .replace(/console\.log\((.+?)\)/g, 'print($1)');
404
+ // Create parameter list with Swift types
405
+ const paramList = params.map((p) => `${p.name}: ${p.type}`).join(', ');
406
+ // Build the Swift function signature
407
+ const signature = `func ${finalName}(${paramList}) -> ${returnType}`;
408
+ // Build the complete Swift function
409
+ const swiftCode = `${signature} {\n ${swiftBody}\n}`;
410
+ return { swiftCode, signature };
411
+ };
412
+ exports.convertJsFunctionToSwift = convertJsFunctionToSwift;
@@ -1,4 +1,29 @@
1
1
  import { BaseTranspilerOptions } from '../../types/transpiler';
2
2
  export interface ToSwiftOptions extends BaseTranspilerOptions {
3
+ /**
4
+ * Format generated Swift code
5
+ * @default true
6
+ */
7
+ formatCode?: boolean;
8
+ /**
9
+ * Include type annotations in Swift code
10
+ * @default true
11
+ */
12
+ includeTypes?: boolean;
13
+ /**
14
+ * Type of state management to use
15
+ * @default 'state'
16
+ */
17
+ stateType?: 'state' | 'stateObject' | 'observable';
18
+ /**
19
+ * Prefix for class names
20
+ * @default ''
21
+ */
22
+ classPrefix?: string;
23
+ /**
24
+ * Whether to include SwiftUI preview code
25
+ * @default true
26
+ */
27
+ includePreview?: boolean;
3
28
  }
4
29
  export type SwiftMetadata = {};
package/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "name": "Builder.io",
23
23
  "url": "https://www.builder.io"
24
24
  },
25
- "version": "0.9.3",
25
+ "version": "0.9.5",
26
26
  "homepage": "https://github.com/BuilderIO/mitosis",
27
27
  "main": "./dist/src/index.js",
28
28
  "exports": {