@callstack/brownfield-cli 3.7.0 → 3.8.1

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.
@@ -1,17 +1,30 @@
1
1
  import path from 'node:path';
2
2
  import { createRequire } from 'node:module';
3
- export function generateTurboModuleSpec(methods) {
3
+ function mapTypeForTurboSpec(typeText, options) {
4
+ if (typeText.startsWith('Promise<')) {
5
+ const inner = typeText.slice(8, -1);
6
+ return `Promise<${mapTypeForTurboSpec(inner, options)}>`;
7
+ }
8
+ if (options.modelTypeNames?.includes(typeText)) {
9
+ return 'Object';
10
+ }
11
+ return typeText;
12
+ }
13
+ export function generateTurboModuleSpec(methods, referencedTypeDeclarations = [], options = {}) {
4
14
  const methodSignatures = methods
5
15
  .map((method) => {
6
16
  const params = method.params
7
- .map((param) => `${param.name}${param.optional ? '?' : ''}: ${param.type}`)
17
+ .map((param) => `${param.name}${param.optional ? '?' : ''}: ${mapTypeForTurboSpec(param.type, options)}`)
8
18
  .join(', ');
9
- return ` ${method.name}(${params}): ${method.returnType};`;
19
+ return ` ${method.name}(${params}): ${mapTypeForTurboSpec(method.returnType, options)};`;
10
20
  })
11
21
  .join('\n');
22
+ const typeDeclarationsBlock = referencedTypeDeclarations.length === 0
23
+ ? ''
24
+ : `${referencedTypeDeclarations.map((entry) => entry.declaration).join('\n\n')}\n\n`;
12
25
  return `import { TurboModuleRegistry, type TurboModule } from 'react-native';
13
26
 
14
- export interface Spec extends TurboModule {
27
+ ${typeDeclarationsBlock}export interface Spec extends TurboModule {
15
28
  ${methodSignatures}
16
29
  }
17
30
 
@@ -20,7 +33,15 @@ export default TurboModuleRegistry.getEnforcing<Spec>(
20
33
  );
21
34
  `;
22
35
  }
23
- export function generateIndexTs(methods) {
36
+ function buildTypeImportLine(referencedTypeDeclarations) {
37
+ if (referencedTypeDeclarations.length === 0) {
38
+ return '';
39
+ }
40
+ const names = referencedTypeDeclarations.map((entry) => entry.name).join(', ');
41
+ return `import type { ${names} } from './NativeBrownfieldNavigation';\n`;
42
+ }
43
+ export function generateIndexTs(methods, referencedTypeDeclarations = []) {
44
+ const typeImportLine = buildTypeImportLine(referencedTypeDeclarations);
24
45
  const functionImplementations = methods
25
46
  .map((method) => {
26
47
  const params = method.params
@@ -39,6 +60,7 @@ export function generateIndexTs(methods) {
39
60
  })
40
61
  .join(',\n');
41
62
  return `import NativeBrownfieldNavigation from './NativeBrownfieldNavigation';
63
+ ${typeImportLine}
42
64
 
43
65
  const BrownfieldNavigation = {
44
66
  ${functionImplementations},
@@ -78,7 +100,8 @@ export function transpileWithConsumerBabel(tsCode, projectRoot, packageRoot) {
78
100
  }
79
101
  return transformed.code;
80
102
  }
81
- export function generateIndexDts(methods) {
103
+ export function generateIndexDts(methods, referencedTypeDeclarations = []) {
104
+ const typeImportLine = buildTypeImportLine(referencedTypeDeclarations);
82
105
  const methodSignatures = methods
83
106
  .map((method) => {
84
107
  const params = method.params
@@ -87,7 +110,7 @@ export function generateIndexDts(methods) {
87
110
  return ` ${method.name}: (${params}) => ${method.returnType};`;
88
111
  })
89
112
  .join('\n');
90
- return `declare const BrownfieldNavigation: {
113
+ return `${typeImportLine}declare const BrownfieldNavigation: {
91
114
  ${methodSignatures}
92
115
  };
93
116
 
@@ -1,3 +1,3 @@
1
- import type { MethodSignature } from './types.js';
2
- export declare function parseNavigationSpec(specPath: string): MethodSignature[];
1
+ import type { ParsedNavigationSpec } from './types.js';
2
+ export declare function parseNavigationSpec(specPath: string): ParsedNavigationSpec;
3
3
  //# sourceMappingURL=parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/navigation/parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqCvE"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/navigation/parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkCpB,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CA4G1E"}
@@ -1,5 +1,34 @@
1
1
  import fs from 'node:fs';
2
2
  import { Project } from 'ts-morph';
3
+ import { Node } from 'ts-morph';
4
+ const SKIP_TYPE_TOKENS = new Set([
5
+ 'Array',
6
+ 'Date',
7
+ 'Map',
8
+ 'Object',
9
+ 'Promise',
10
+ 'ReadonlyArray',
11
+ 'Record',
12
+ 'Set',
13
+ 'any',
14
+ 'boolean',
15
+ 'false',
16
+ 'null',
17
+ 'number',
18
+ 'object',
19
+ 'string',
20
+ 'true',
21
+ 'undefined',
22
+ 'unknown',
23
+ 'void',
24
+ ]);
25
+ function collectReferencedTypesFromText(typeText) {
26
+ const matches = typeText.match(/\b[A-Za-z_]\w*\b/g);
27
+ if (!matches) {
28
+ return [];
29
+ }
30
+ return matches.filter((match) => !SKIP_TYPE_TOKENS.has(match));
31
+ }
3
32
  export function parseNavigationSpec(specPath) {
4
33
  if (!fs.existsSync(specPath)) {
5
34
  throw new Error(`Spec file not found: ${specPath}`);
@@ -11,7 +40,7 @@ export function parseNavigationSpec(specPath) {
11
40
  if (!specInterface) {
12
41
  throw new Error('Could not find BrownfieldNavigationSpec or Spec interface in spec file');
13
42
  }
14
- return specInterface.getMethods().map((method) => {
43
+ const methods = specInterface.getMethods().map((method) => {
15
44
  const name = method.getName();
16
45
  const params = method.getParameters().map((param) => {
17
46
  const typeNode = param.getTypeNode();
@@ -30,4 +59,84 @@ export function parseNavigationSpec(specPath) {
30
59
  isAsync: returnType.startsWith('Promise<'),
31
60
  };
32
61
  });
62
+ const declarationNodes = [
63
+ ...sourceFile.getTypeAliases(),
64
+ ...sourceFile.getInterfaces(),
65
+ ].filter((node) => {
66
+ const nodeName = node.getName();
67
+ return nodeName !== 'BrownfieldNavigationSpec' && nodeName !== 'Spec';
68
+ });
69
+ const declarationByName = new Map(declarationNodes.map((node) => {
70
+ const declarationText = node.getText();
71
+ const normalizedDeclaration = node.isExported()
72
+ ? declarationText
73
+ : `export ${declarationText}`;
74
+ return [node.getName(), normalizedDeclaration];
75
+ }));
76
+ const referencedTypeNames = new Set();
77
+ for (const method of methods) {
78
+ for (const typeText of [
79
+ method.returnType,
80
+ ...method.params.map((param) => param.type),
81
+ ]) {
82
+ for (const typeName of collectReferencedTypesFromText(typeText)) {
83
+ referencedTypeNames.add(typeName);
84
+ }
85
+ }
86
+ }
87
+ const referencedTypeDeclarations = [];
88
+ const modelDefinitions = [];
89
+ const addedTypeNames = new Set();
90
+ const queue = [...referencedTypeNames];
91
+ while (queue.length > 0) {
92
+ const typeName = queue.shift();
93
+ if (!typeName || addedTypeNames.has(typeName)) {
94
+ continue;
95
+ }
96
+ const declaration = declarationByName.get(typeName);
97
+ if (!declaration) {
98
+ continue;
99
+ }
100
+ addedTypeNames.add(typeName);
101
+ referencedTypeDeclarations.push({ name: typeName, declaration });
102
+ const declarationNode = declarationNodes.find((node) => node.getName() === typeName);
103
+ if (declarationNode) {
104
+ const fields = extractModelFields(declarationNode);
105
+ if (fields.length > 0) {
106
+ modelDefinitions.push({ name: typeName, fields });
107
+ }
108
+ }
109
+ for (const nestedTypeName of collectReferencedTypesFromText(declaration)) {
110
+ if (!addedTypeNames.has(nestedTypeName) &&
111
+ declarationByName.has(nestedTypeName)) {
112
+ queue.push(nestedTypeName);
113
+ }
114
+ }
115
+ }
116
+ return {
117
+ methods,
118
+ referencedTypeDeclarations,
119
+ modelDefinitions,
120
+ };
121
+ }
122
+ function extractModelFields(node) {
123
+ if (Node.isInterfaceDeclaration(node)) {
124
+ return node.getProperties().map((property) => ({
125
+ name: property.getName(),
126
+ type: property.getTypeNode()?.getText() ?? 'unknown',
127
+ optional: property.hasQuestionToken(),
128
+ }));
129
+ }
130
+ if (Node.isTypeAliasDeclaration(node)) {
131
+ const typeNode = node.getTypeNode();
132
+ if (!typeNode || !Node.isTypeLiteral(typeNode)) {
133
+ return [];
134
+ }
135
+ return typeNode.getProperties().map((property) => ({
136
+ name: property.getName(),
137
+ type: property.getTypeNode()?.getText() ?? 'unknown',
138
+ optional: property.hasQuestionToken(),
139
+ }));
140
+ }
141
+ return [];
33
142
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/navigation/runner.ts"],"names":[],"mappings":"AA2BA,UAAU,2BAA2B;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgLD,wBAAsB,oBAAoB,CAAC,EACzC,QAAQ,EACR,MAAc,EACd,WAA2B,GAC5B,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D7C"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/navigation/runner.ts"],"names":[],"mappings":"AA2BA,UAAU,2BAA2B;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgLD,wBAAsB,oBAAoB,CAAC,EACzC,QAAQ,EACR,MAAc,EACd,WAA2B,GAC5B,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA0E7C"}
@@ -88,28 +88,39 @@ export async function runNavigationCodegen({ specPath, dryRun = false, projectRo
88
88
  }
89
89
  intro(`Running Brownfield Navigation codegen`);
90
90
  logger.info(`Parsing spec file: ${resolvedSpecPath}`);
91
- const methods = parseNavigationSpec(resolvedSpecPath);
91
+ const { methods, referencedTypeDeclarations, modelDefinitions } = parseNavigationSpec(resolvedSpecPath);
92
92
  if (methods.length === 0) {
93
93
  throw new Error('No methods found in spec file');
94
94
  }
95
95
  logger.info(`Found ${methods.length} method${methods.length === 1 ? '' : 's'}: ${methods.map((method) => method.name).join(', ')}`);
96
96
  const packageRoot = getNavigationPackagePath(projectRoot);
97
97
  const androidJavaPackageName = DEFAULT_ANDROID_JAVA_PACKAGE;
98
- const indexTs = generateIndexTs(methods);
98
+ const indexTs = generateIndexTs(methods, referencedTypeDeclarations);
99
99
  const models = await generateNavigationModels({
100
100
  specPath: resolvedSpecPath,
101
101
  methods,
102
102
  kotlinPackageName: androidJavaPackageName,
103
+ modelDefinitions,
103
104
  });
104
105
  const artifacts = {
105
- turboModuleSpec: generateTurboModuleSpec(methods),
106
+ turboModuleSpec: generateTurboModuleSpec(methods, referencedTypeDeclarations, {
107
+ modelTypeNames: models.modelTypeNames,
108
+ }),
106
109
  indexTs,
107
110
  indexJs: transpileWithConsumerBabel(indexTs, projectRoot, packageRoot),
108
- indexDts: generateIndexDts(methods),
109
- swiftDelegate: generateSwiftDelegate(methods),
110
- objcImplementation: generateObjCImplementation(methods),
111
- kotlinDelegate: generateKotlinDelegate(methods, androidJavaPackageName),
112
- kotlinModule: generateKotlinModule(methods, androidJavaPackageName),
111
+ indexDts: generateIndexDts(methods, referencedTypeDeclarations),
112
+ swiftDelegate: generateSwiftDelegate(methods, {
113
+ modelTypeNames: models.modelTypeNames,
114
+ }),
115
+ objcImplementation: generateObjCImplementation(methods, {
116
+ modelTypeNames: models.modelTypeNames,
117
+ }),
118
+ kotlinDelegate: generateKotlinDelegate(methods, androidJavaPackageName, {
119
+ modelTypeNames: models.modelTypeNames,
120
+ }),
121
+ kotlinModule: generateKotlinModule(methods, androidJavaPackageName, {
122
+ modelTypeNames: models.modelTypeNames,
123
+ }),
113
124
  };
114
125
  if (models.modelTypeNames.length > 0) {
115
126
  logger.info(`Generating quicktype models for types: ${models.modelTypeNames.join(', ')}`);
@@ -9,6 +9,24 @@ export interface MethodSignature {
9
9
  returnType: string;
10
10
  isAsync: boolean;
11
11
  }
12
+ export interface TypeDeclaration {
13
+ name: string;
14
+ declaration: string;
15
+ }
16
+ export interface ModelFieldDefinition {
17
+ name: string;
18
+ type: string;
19
+ optional: boolean;
20
+ }
21
+ export interface ModelDefinition {
22
+ name: string;
23
+ fields: ModelFieldDefinition[];
24
+ }
25
+ export interface ParsedNavigationSpec {
26
+ methods: MethodSignature[];
27
+ referencedTypeDeclarations: TypeDeclaration[];
28
+ modelDefinitions: ModelDefinition[];
29
+ }
12
30
  export interface GeneratedNavigationArtifacts {
13
31
  turboModuleSpec: string;
14
32
  indexTs: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigation/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigation/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,0BAA0B,EAAE,eAAe,EAAE,CAAC;IAC9C,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callstack/brownfield-cli",
3
- "version": "3.7.0",
3
+ "version": "3.8.1",
4
4
  "license": "MIT",
5
5
  "author": "Artur Morys-Magiera <artus9033@gmail.com>",
6
6
  "bin": {
@@ -8,10 +8,19 @@ const TS_TO_KOTLIN_TYPE: Record<string, string> = {
8
8
  Object: 'ReadableMap',
9
9
  };
10
10
 
11
- function mapTsTypeToKotlin(tsType: string, optional: boolean = false): string {
11
+ interface KotlinTypeMappingOptions {
12
+ modelTypeNames?: string[];
13
+ }
14
+
15
+ function mapTsTypeToKotlin(
16
+ tsType: string,
17
+ optional: boolean = false,
18
+ options: KotlinTypeMappingOptions = {},
19
+ layer: 'delegate' | 'module' = 'delegate'
20
+ ): string {
12
21
  if (tsType.startsWith('Promise<')) {
13
22
  const inner = tsType.slice(8, -1);
14
- return mapTsTypeToKotlin(inner, optional);
23
+ return mapTsTypeToKotlin(inner, optional, options, layer);
15
24
  }
16
25
 
17
26
  const mapped = TS_TO_KOTLIN_TYPE[tsType];
@@ -19,25 +28,33 @@ function mapTsTypeToKotlin(tsType: string, optional: boolean = false): string {
19
28
  return optional ? `${mapped}?` : mapped;
20
29
  }
21
30
 
31
+ if (options.modelTypeNames?.includes(tsType)) {
32
+ if (layer === 'module') {
33
+ return optional ? 'ReadableMap?' : 'ReadableMap';
34
+ }
35
+ return optional ? `${tsType}?` : tsType;
36
+ }
37
+
22
38
  return optional ? 'Any?' : 'Any';
23
39
  }
24
40
 
25
41
  export function generateKotlinDelegate(
26
42
  methods: MethodSignature[],
27
- kotlinPackageName: string
43
+ kotlinPackageName: string,
44
+ options: KotlinTypeMappingOptions = {}
28
45
  ): string {
29
46
  const methodSignatures = methods
30
47
  .map((method) => {
31
48
  const params = method.params
32
49
  .map(
33
50
  (param) =>
34
- `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`
51
+ `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options)}`
35
52
  )
36
53
  .join(', ');
37
54
  const returnType =
38
55
  method.returnType === 'void'
39
56
  ? ''
40
- : `: ${mapTsTypeToKotlin(method.returnType, false)}`;
57
+ : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'delegate')}`;
41
58
  return ` fun ${method.name}(${params})${returnType}`;
42
59
  })
43
60
  .join('\n');
@@ -52,20 +69,24 @@ ${methodSignatures}
52
69
 
53
70
  export function generateKotlinModule(
54
71
  methods: MethodSignature[],
55
- kotlinPackageName: string
72
+ kotlinPackageName: string,
73
+ options: KotlinTypeMappingOptions = {}
56
74
  ): string {
57
75
  const hasAsyncMethod = methods.some((method) => method.isAsync);
58
76
  const hasObjectType = methods.some(
59
77
  (method) =>
60
78
  method.returnType.includes('Object') ||
61
- method.params.some((param) => param.type === 'Object')
79
+ method.params.some(
80
+ (param) =>
81
+ param.type === 'Object' || options.modelTypeNames?.includes(param.type)
82
+ )
62
83
  );
63
84
 
64
85
  const methodImplementations = methods
65
86
  .map((method) =>
66
87
  method.isAsync
67
- ? generateAsyncKotlinMethod(method)
68
- : generateSyncKotlinMethod(method)
88
+ ? generateAsyncKotlinMethod(method, options)
89
+ : generateSyncKotlinMethod(method, options)
69
90
  )
70
91
  .join('\n\n');
71
92
 
@@ -88,32 +109,61 @@ ${methodImplementations}
88
109
  `;
89
110
  }
90
111
 
91
- function generateSyncKotlinMethod(method: MethodSignature): string {
112
+ function generateSyncKotlinMethod(
113
+ method: MethodSignature,
114
+ options: KotlinTypeMappingOptions = {}
115
+ ): string {
92
116
  const params = method.params
93
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
117
+ .map(
118
+ (param) =>
119
+ `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`
120
+ )
121
+ .join(', ');
122
+ const preparedParams: string[] = [];
123
+ const args = method.params
124
+ .map((param) => {
125
+ if (options.modelTypeNames?.includes(param.type)) {
126
+ const convertedName = `${param.name}Model`;
127
+ preparedParams.push(
128
+ ` val ${convertedName} = ${param.name}${
129
+ param.optional
130
+ ? `?.let(::to${param.type})`
131
+ : `.let(::to${param.type})`
132
+ }`
133
+ );
134
+ return convertedName;
135
+ }
136
+ return param.name;
137
+ })
94
138
  .join(', ');
95
- const args = method.params.map((param) => param.name).join(', ');
96
139
 
97
140
  const signature = ` @ReactMethod\n override fun ${method.name}(${params})${
98
141
  method.returnType === 'void'
99
142
  ? ''
100
- : `: ${mapTsTypeToKotlin(method.returnType, false)}`
143
+ : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'module')}`
101
144
  }`;
145
+ const preparedPrefix = preparedParams.length > 0 ? `${preparedParams.join('\n')}\n` : '';
102
146
 
103
147
  if (method.returnType === 'void') {
104
148
  return `${signature} {
105
- BrownfieldNavigationManager.getDelegate().${method.name}(${args})
149
+ ${preparedPrefix} BrownfieldNavigationManager.getDelegate().${method.name}(${args})
106
150
  }`;
107
151
  }
108
152
 
109
153
  return `${signature} {
110
- return BrownfieldNavigationManager.getDelegate().${method.name}(${args})
154
+ ${preparedPrefix} return BrownfieldNavigationManager.getDelegate().${method.name}(${args})
111
155
  }`;
112
156
  }
113
157
 
114
- function generateAsyncKotlinMethod(method: MethodSignature): string {
158
+ function generateAsyncKotlinMethod(
159
+ method: MethodSignature,
160
+ options: KotlinTypeMappingOptions = {}
161
+ ): string {
115
162
  const paramsWithTypes = method.params
116
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
163
+ .map(
164
+ (param) =>
165
+ `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`
166
+ )
117
167
  .join(', ');
118
168
  const params =
119
169
  paramsWithTypes.length > 0
@@ -16,11 +16,23 @@ const TS_TO_SWIFT_TYPE: Record<string, string> = {
16
16
  Object: '[String: Any]',
17
17
  };
18
18
 
19
- function mapTsTypeToObjC(tsType: string, nullable: boolean = false): string {
19
+ interface ObjCTypeMappingOptions {
20
+ modelTypeNames?: string[];
21
+ }
22
+
23
+ function mapTsTypeToObjC(
24
+ tsType: string,
25
+ nullable: boolean = false,
26
+ options: ObjCTypeMappingOptions = {}
27
+ ): string {
20
28
  if (tsType.startsWith('Promise<')) {
21
29
  return 'void';
22
30
  }
23
31
 
32
+ if (options.modelTypeNames?.includes(tsType)) {
33
+ return nullable ? 'NSDictionary * _Nullable' : 'NSDictionary *';
34
+ }
35
+
24
36
  const mapped = TS_TO_OBJC_TYPE[tsType];
25
37
  if (mapped) {
26
38
  if (nullable && mapped.includes('*')) {
@@ -32,10 +44,20 @@ function mapTsTypeToObjC(tsType: string, nullable: boolean = false): string {
32
44
  return nullable ? 'id _Nullable' : 'id';
33
45
  }
34
46
 
35
- function mapTsTypeToSwift(tsType: string, optional: boolean = false): string {
47
+ interface SwiftTypeMappingOptions {
48
+ modelTypeNames?: string[];
49
+ }
50
+
51
+ interface ObjCGenerationOptions extends ObjCTypeMappingOptions {}
52
+
53
+ function mapTsTypeToSwift(
54
+ tsType: string,
55
+ optional: boolean = false,
56
+ options: SwiftTypeMappingOptions = {}
57
+ ): string {
36
58
  if (tsType.startsWith('Promise<')) {
37
59
  const inner = tsType.slice(8, -1);
38
- return mapTsTypeToSwift(inner, optional);
60
+ return mapTsTypeToSwift(inner, optional, options);
39
61
  }
40
62
 
41
63
  const mapped = TS_TO_SWIFT_TYPE[tsType];
@@ -43,15 +65,22 @@ function mapTsTypeToSwift(tsType: string, optional: boolean = false): string {
43
65
  return optional ? `${mapped}?` : mapped;
44
66
  }
45
67
 
68
+ if (options.modelTypeNames?.includes(tsType)) {
69
+ return optional ? `${tsType}?` : tsType;
70
+ }
71
+
46
72
  return optional ? 'Any?' : 'Any';
47
73
  }
48
74
 
49
- export function generateSwiftDelegate(methods: MethodSignature[]): string {
75
+ export function generateSwiftDelegate(
76
+ methods: MethodSignature[],
77
+ options: SwiftTypeMappingOptions = {}
78
+ ): string {
50
79
  const protocolMethods = methods
51
80
  .map((method) => {
52
81
  const params = method.params
53
82
  .map((param, index) => {
54
- const swiftType = mapTsTypeToSwift(param.type, param.optional);
83
+ const swiftType = mapTsTypeToSwift(param.type, param.optional, options);
55
84
  const label = index === 0 ? '_' : param.name;
56
85
  return `${label} ${param.name}: ${swiftType}`;
57
86
  })
@@ -60,7 +89,7 @@ export function generateSwiftDelegate(methods: MethodSignature[]): string {
60
89
  const returnType =
61
90
  method.returnType === 'void'
62
91
  ? ''
63
- : ` -> ${mapTsTypeToSwift(method.returnType, false)}`;
92
+ : ` -> ${mapTsTypeToSwift(method.returnType, false, options)}`;
64
93
 
65
94
  return ` @objc func ${method.name}(${params})${returnType}`;
66
95
  })
@@ -74,10 +103,15 @@ ${protocolMethods}
74
103
  `;
75
104
  }
76
105
 
77
- export function generateObjCImplementation(methods: MethodSignature[]): string {
106
+ export function generateObjCImplementation(
107
+ methods: MethodSignature[],
108
+ options: ObjCGenerationOptions = {}
109
+ ): string {
78
110
  const methodImplementations = methods
79
111
  .map((method) =>
80
- method.isAsync ? generateAsyncObjCMethod(method) : generateSyncObjCMethod(method)
112
+ method.isAsync
113
+ ? generateAsyncObjCMethod(method, options)
114
+ : generateSyncObjCMethod(method, options)
81
115
  )
82
116
  .join('\n\n');
83
117
 
@@ -108,38 +142,61 @@ ${methodImplementations}
108
142
  `;
109
143
  }
110
144
 
111
- function generateSyncObjCMethod(method: MethodSignature): string {
145
+ function generateSyncObjCMethod(
146
+ method: MethodSignature,
147
+ options: ObjCGenerationOptions
148
+ ): string {
112
149
  const { name, params, returnType } = method;
113
150
 
114
- let signature = `- (${mapTsTypeToObjC(returnType)})${name}`;
151
+ let signature = `- (${mapTsTypeToObjC(returnType, false, options)})${name}`;
115
152
  if (params.length > 0) {
116
153
  signature += params
117
154
  .map((param, index) => {
118
155
  const prefix = index === 0 ? ':' : ` ${param.name}:`;
119
- return `${prefix}(${mapTsTypeToObjC(param.type, param.optional)})${param.name}`;
156
+ return `${prefix}(${mapTsTypeToObjC(param.type, param.optional, options)})${param.name}`;
120
157
  })
121
158
  .join('');
122
159
  }
123
160
 
161
+ const preparedParams: string[] = [];
162
+ const delegateArgs: string[] = [];
163
+ for (const param of params) {
164
+ if (options.modelTypeNames?.includes(param.type)) {
165
+ const convertedParamName = `${param.name}Model`;
166
+ preparedParams.push(
167
+ `${param.type} *${convertedParamName} = ${param.name} == nil ? nil : [${param.type} fromDictionary:${param.name}];`
168
+ );
169
+ delegateArgs.push(convertedParamName);
170
+ } else {
171
+ delegateArgs.push(param.name);
172
+ }
173
+ }
174
+
124
175
  let delegateCall = `[[[BrownfieldNavigationManager shared] getDelegate] ${name}`;
125
- if (params.length > 0) {
126
- delegateCall += params
127
- .map((param, index) => {
176
+ if (delegateArgs.length > 0) {
177
+ delegateCall += delegateArgs
178
+ .map((argName, index) => {
179
+ const param = params[index];
128
180
  const label = index === 0 ? '' : param.name;
129
- return `${label}:${param.name}`;
181
+ return `${label}:${argName}`;
130
182
  })
131
183
  .join(' ');
132
184
  }
133
185
  delegateCall += ']';
134
186
 
135
187
  const returnPrefix = returnType === 'void' ? '' : 'return ';
188
+ const preparedLines = preparedParams.map((line) => ` ${line}`).join('\n');
189
+ const bodyPrefix = preparedLines.length > 0 ? `${preparedLines}\n` : '';
136
190
 
137
191
  return `${signature} {
138
- ${returnPrefix}${delegateCall};
192
+ ${bodyPrefix} ${returnPrefix}${delegateCall};
139
193
  }`;
140
194
  }
141
195
 
142
- function generateAsyncObjCMethod(method: MethodSignature): string {
196
+ function generateAsyncObjCMethod(
197
+ method: MethodSignature,
198
+ options: ObjCGenerationOptions
199
+ ): string {
143
200
  const { name, params } = method;
144
201
 
145
202
  let signature = `- (void)${name}`;
@@ -158,7 +215,7 @@ function generateAsyncObjCMethod(method: MethodSignature): string {
158
215
  ? 'RCTPromiseResolveBlock'
159
216
  : param.type === 'RCTPromiseRejectBlock'
160
217
  ? 'RCTPromiseRejectBlock'
161
- : mapTsTypeToObjC(param.type, param.optional);
218
+ : mapTsTypeToObjC(param.type, param.optional, options);
162
219
  return `${prefix}(${type})${param.name}`;
163
220
  })
164
221
  .join(' ');