nitrogen 0.30.1 → 0.31.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.
@@ -28,6 +28,7 @@ import { NitroConfig } from '../config/NitroConfig.js';
28
28
  import { CustomType } from './types/CustomType.js';
29
29
  import { isSyncFunction, isArrayBuffer, isCustomType, isDate, isError, isMap, isPromise, isRecord, } from './isCoreType.js';
30
30
  import { getCustomTypeConfig } from './getCustomTypeConfig.js';
31
+ import { compareLooselyness } from './helpers.js';
31
32
  function getHybridObjectName(type) {
32
33
  const symbol = isHybridView(type) ? type.getAliasSymbol() : type.getSymbol();
33
34
  if (symbol == null) {
@@ -129,8 +130,15 @@ export function createType(language, type, isOptional) {
129
130
  }
130
131
  else if (type.isNumber() || type.isNumberLiteral()) {
131
132
  if (type.isEnumLiteral()) {
132
- // enum literals are technically just numbers - but we treat them differently in C++.
133
- return createType(language, type.getBaseTypeOfLiteralType(), isOptional);
133
+ // An enum is just a number, that's why it's a number literal.
134
+ // Get the base of the literal, which would be our enum's definition.
135
+ const baseType = type.getBaseTypeOfLiteralType();
136
+ if (!baseType.isEnum()) {
137
+ // The base of the literal is not the enum definition, we need to throw.
138
+ throw new Error(`The enum "${type.getLiteralValue()}" (${type.getText()}) is either a single-value-enum, or an enum-literal. Use a separately defined enum with at least 2 cases instead!`);
139
+ }
140
+ // Call createType(...) now with the enum type.
141
+ return createType(language, baseType, isOptional);
134
142
  }
135
143
  return new NumberType();
136
144
  }
@@ -234,7 +242,8 @@ export function createType(language, type, isOptional) {
234
242
  .getUnionTypes()
235
243
  // Filter out any nulls or undefineds, as those are already treated as `isOptional`.
236
244
  .filter((t) => !t.isNull() && !t.isUndefined() && !t.isVoid())
237
- .map((t) => createType(language, t, false));
245
+ .map((t) => createType(language, t, false))
246
+ .toSorted(compareLooselyness);
238
247
  variants = removeDuplicates(variants);
239
248
  if (variants.length === 1) {
240
249
  // It's just one type with undefined/null variant(s) - so we treat it like a simple optional.
@@ -5,6 +5,12 @@ export declare function createFileMetadataString(filename: string, comment?: Com
5
5
  export declare function isFunction(type: Type): boolean;
6
6
  export declare function toReferenceType(type: string): `const ${typeof type}&`;
7
7
  export declare function escapeCppName(string: string): string;
8
+ /**
9
+ * Compares the "looselyness" of the types.
10
+ * Returns a positive number if {@linkcode a} is more loose than {@linkcode b},
11
+ * and a negative number if otherwise.
12
+ */
13
+ export declare function compareLooselyness(a: Type, b: Type): number;
8
14
  export declare function isBooleanPropertyPrefix(name: string): boolean;
9
15
  export declare function isNotDuplicate<T>(item: T, index: number, array: T[]): boolean;
10
16
  export declare function isCppFile(file: SourceFile): boolean;
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { getTypeAs } from './types/getTypeAs.js';
3
3
  import { OptionalType } from './types/OptionalType.js';
4
+ import { ArrayType } from './types/ArrayType.js';
4
5
  export function createFileMetadataString(filename, comment = '///') {
5
6
  const now = new Date();
6
7
  return `
@@ -36,6 +37,103 @@ export function escapeCppName(string) {
36
37
  }
37
38
  return escapedStr;
38
39
  }
40
+ /**
41
+ * Get a type's "looselyness".
42
+ * The concept is as follows:
43
+ * - If a `type` is easy to runtime-check in JSI (e.g. a `number` is just `.isNumber()`),
44
+ * it should have a very low looselyness value returned here.
45
+ * - If a `type` is hard to runtime-check in JSI (e.g. `AnyMap` can have any key),
46
+ * it should have a very high looselyness value returned here.
47
+ * - Type's looselyness values are compared against each other to determine in which
48
+ * order they should be runtime type-checked - e.g. `AnyMap` should be last (as it can
49
+ * always be `true` if it's just an `object`), and `Promise` should be sooner (as it can
50
+ * be `instanceof` checked)
51
+ * - As a performance optimization, rank faster checks earlier (e.g. `isNumber()` before
52
+ * `instanceof`, as `instanceof` requires global constructor lookup)
53
+ */
54
+ function getTypeLooselyness(type) {
55
+ switch (type.kind) {
56
+ case 'array-buffer':
57
+ // We have `.isArrayBuffer()`
58
+ return 0;
59
+ case 'bigint':
60
+ // We have `.isBigInt()`
61
+ return 0;
62
+ case 'boolean':
63
+ // We have `.isBool()`
64
+ return 0;
65
+ case 'null':
66
+ // We have `isNull()`
67
+ return 0;
68
+ case 'number':
69
+ // We have `isNumber()` (but it can also be an enum)
70
+ return 1;
71
+ case 'optional':
72
+ // We have `isUndefined()`
73
+ return 0;
74
+ case 'function':
75
+ // We have `.isFunction()`
76
+ return 0;
77
+ case 'string':
78
+ // We have `.isString()` (but it can also be a union)
79
+ return 1;
80
+ case 'enum':
81
+ // We have `.isNumber()` or `.isString()` and is within range, only
82
+ // if it's not within range, it can fallback to `string` or `number`
83
+ return 0;
84
+ case 'array':
85
+ // We have `.isArray()` - but it should extend the item type's looselyness
86
+ const arrayType = getTypeAs(type, ArrayType);
87
+ return 0.5 * getTypeLooselyness(arrayType.itemType);
88
+ case 'hybrid-object':
89
+ // We have `getNativeState<T>()`
90
+ return 0;
91
+ case 'hybrid-object-base':
92
+ // We have `getNativeState<HybridObject>()`
93
+ return 1;
94
+ case 'tuple':
95
+ // We have `.isArray()` and `canConvert<T>()`
96
+ return 1;
97
+ case 'date':
98
+ // We have `instanceof Date`
99
+ return 2;
100
+ case 'error':
101
+ // We have `instanceof Error`
102
+ return 2;
103
+ case 'promise':
104
+ // We have `instanceof Promise`
105
+ return 2;
106
+ case 'custom-type':
107
+ // Up to the user
108
+ return 1;
109
+ case 'record':
110
+ // Super loose, only type counts
111
+ return 3;
112
+ case 'map':
113
+ // This is just super loose
114
+ return 4;
115
+ case 'struct':
116
+ // We check each property individually
117
+ return 1;
118
+ case 'variant':
119
+ // Pretty loose
120
+ return 2;
121
+ case 'result-wrapper':
122
+ // Not loose at all
123
+ return 0;
124
+ case 'void':
125
+ // Not a type
126
+ return 0;
127
+ }
128
+ }
129
+ /**
130
+ * Compares the "looselyness" of the types.
131
+ * Returns a positive number if {@linkcode a} is more loose than {@linkcode b},
132
+ * and a negative number if otherwise.
133
+ */
134
+ export function compareLooselyness(a, b) {
135
+ return getTypeLooselyness(a) - getTypeLooselyness(b);
136
+ }
39
137
  export function isBooleanPropertyPrefix(name) {
40
138
  return name.startsWith('is') || name.startsWith('has');
41
139
  }
@@ -14,7 +14,8 @@ export function createKotlinFunction(functionType) {
14
14
  const lambdaSignature = `(${kotlinParamTypes.join(', ')}) -> ${kotlinReturnType}`;
15
15
  const extraImports = functionType
16
16
  .getRequiredImports('kotlin')
17
- .map((i) => `import ${i.name}`);
17
+ .map((i) => `import ${i.name}`)
18
+ .filter(isNotDuplicate);
18
19
  const kotlinCode = `
19
20
  ${createFileMetadataString(`${name}.kt`)}
20
21
 
@@ -23,7 +24,6 @@ package ${packageName}
23
24
  import androidx.annotation.Keep
24
25
  import com.facebook.jni.HybridData
25
26
  import com.facebook.proguard.annotations.DoNotStrip
26
- import com.margelo.nitro.core.*
27
27
  import dalvik.annotation.optimization.FastNative
28
28
  ${extraImports.join('\n')}
29
29
 
@@ -21,6 +21,20 @@ export function createKotlinHybridObject(spec) {
21
21
  ...spec.methods.flatMap((m) => m.getRequiredImports('kotlin')),
22
22
  ...spec.baseTypes.flatMap((b) => new HybridObjectType(b).getRequiredImports('kotlin')),
23
23
  ];
24
+ if (spec.isHybridView) {
25
+ extraImports.push({
26
+ name: 'com.margelo.nitro.views.HybridView',
27
+ space: 'system',
28
+ language: 'kotlin',
29
+ });
30
+ }
31
+ else {
32
+ extraImports.push({
33
+ name: 'com.margelo.nitro.core.HybridObject',
34
+ space: 'system',
35
+ language: 'kotlin',
36
+ });
37
+ }
24
38
  let kotlinBase = spec.isHybridView ? 'HybridView' : 'HybridObject';
25
39
  if (spec.baseTypes.length > 0) {
26
40
  if (spec.baseTypes.length > 1) {
@@ -30,12 +44,9 @@ export function createKotlinHybridObject(spec) {
30
44
  const baseHybrid = new HybridObjectType(base);
31
45
  kotlinBase = baseHybrid.getCode('kotlin');
32
46
  }
33
- const imports = [];
34
- imports.push('import com.margelo.nitro.core.*');
35
- if (spec.isHybridView) {
36
- imports.push('import com.margelo.nitro.views.*');
37
- }
38
- imports.push(...extraImports.map((i) => `import ${i.name}`).filter(isNotDuplicate));
47
+ const imports = extraImports
48
+ .map((i) => `import ${i.name}`)
49
+ .filter(isNotDuplicate);
39
50
  const javaPackage = spec.config.getAndroidPackage('java/kotlin');
40
51
  // 1. Create Kotlin abstract class definition
41
52
  const abstractClassCode = `
@@ -27,7 +27,8 @@ val ${p.escapedName}: ${p.getCode('kotlin')}
27
27
  .join(', ');
28
28
  const extraImports = structType.properties
29
29
  .flatMap((t) => t.getRequiredImports('kotlin'))
30
- .map((i) => `import ${i.name}`);
30
+ .map((i) => `import ${i.name}`)
31
+ .filter(isNotDuplicate);
31
32
  const code = `
32
33
  ${createFileMetadataString(`${structType.structName}.kt`)}
33
34
 
@@ -35,7 +36,6 @@ package ${packageName}
35
36
 
36
37
  import androidx.annotation.Keep
37
38
  import com.facebook.proguard.annotations.DoNotStrip
38
- import com.margelo.nitro.core.*
39
39
  ${extraImports.join('\n')}
40
40
 
41
41
  /**
@@ -7,6 +7,10 @@ import {} from '../types/VariantType.js';
7
7
  import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js';
8
8
  export function createKotlinVariant(variant) {
9
9
  const jsName = variant.variants.map((v) => v.getCode('kotlin')).join(' | ');
10
+ const cxxName = variant
11
+ .getCode('c++')
12
+ .replaceAll('/* ', '')
13
+ .replaceAll(' */', '');
10
14
  const kotlinName = variant.getAliasName('kotlin');
11
15
  const namespace = `J${kotlinName}_impl`;
12
16
  const innerClasses = variant.cases.map(([label, v]) => {
@@ -61,7 +65,8 @@ fun create(value: ${bridge.getTypeCode('kotlin')}): ${kotlinName} = ${innerName}
61
65
  });
62
66
  const extraImports = variant.variants
63
67
  .flatMap((t) => t.getRequiredImports('kotlin'))
64
- .map((i) => `import ${i.name}`);
68
+ .map((i) => `import ${i.name}`)
69
+ .filter(isNotDuplicate);
65
70
  const code = `
66
71
  ${createFileMetadataString(`${kotlinName}.kt`)}
67
72
 
@@ -190,7 +195,7 @@ ${createFileMetadataString(`J${kotlinName}.cpp`)}
190
195
 
191
196
  namespace ${cxxNamespace} {
192
197
  /**
193
- * Converts J${kotlinName} to ${variant.getCode('c++')}
198
+ * Converts J${kotlinName} to ${cxxName}
194
199
  */
195
200
  ${variant.getCode('c++')} J${kotlinName}::toCpp() const {
196
201
  ${indent(cppGetIfs.join(' else '), ' ')}
@@ -1,6 +1,6 @@
1
1
  import { NitroConfig } from '../../config/NitroConfig.js';
2
2
  import { indent } from '../../utils.js';
3
- import { createFileMetadataString } from '../helpers.js';
3
+ import { createFileMetadataString, isNotDuplicate } from '../helpers.js';
4
4
  import { SwiftCxxBridgedType } from './SwiftCxxBridgedType.js';
5
5
  export function createSwiftFunctionBridge(functionType) {
6
6
  const swiftClassName = functionType.specializationName;
@@ -28,7 +28,8 @@ return ${returnType.parseFromSwiftToCpp('__result', 'swift')}
28
28
  }
29
29
  const extraImports = functionType
30
30
  .getRequiredImports('swift')
31
- .map((i) => `import ${i.name}`);
31
+ .map((i) => `import ${i.name}`)
32
+ .filter(isNotDuplicate);
32
33
  const code = `
33
34
  ${createFileMetadataString(`${swiftClassName}.swift`)}
34
35
 
@@ -1,5 +1,5 @@
1
1
  import { indent } from '../../utils.js';
2
- import { createFileMetadataString } from '../helpers.js';
2
+ import { createFileMetadataString, isNotDuplicate } from '../helpers.js';
3
3
  import { getTypeAs } from '../types/getTypeAs.js';
4
4
  import { OptionalType } from '../types/OptionalType.js';
5
5
  function isPrimitive(type) {
@@ -33,7 +33,8 @@ export function createSwiftVariant(variant) {
33
33
  const enumDeclaration = allPrimitives ? 'enum' : 'indirect enum';
34
34
  const extraImports = variant.variants
35
35
  .flatMap((t) => t.getRequiredImports('swift'))
36
- .map((i) => `import ${i.name}`);
36
+ .map((i) => `import ${i.name}`)
37
+ .filter(isNotDuplicate);
37
38
  const code = `
38
39
  ${createFileMetadataString(`${typename}.swift`)}
39
40
 
@@ -1,4 +1,3 @@
1
- import { getForwardDeclaration } from '../c++/getForwardDeclaration.js';
2
1
  export class ArrayBufferType {
3
2
  get canBePassedByReference() {
4
3
  // It's a shared_ptr.
@@ -24,13 +23,28 @@ export class ArrayBufferType {
24
23
  }
25
24
  getRequiredImports(language) {
26
25
  const imports = [];
27
- if (language === 'c++') {
28
- imports.push({
29
- name: 'NitroModules/ArrayBuffer.hpp',
30
- forwardDeclaration: getForwardDeclaration('class', 'ArrayBuffer', 'NitroModules'),
31
- language: 'c++',
32
- space: 'system',
33
- });
26
+ switch (language) {
27
+ case 'c++':
28
+ imports.push({
29
+ language: 'c++',
30
+ name: 'NitroModules/ArrayBuffer.hpp',
31
+ space: 'system',
32
+ });
33
+ break;
34
+ case 'swift':
35
+ imports.push({
36
+ name: 'NitroModules',
37
+ language: 'swift',
38
+ space: 'system',
39
+ });
40
+ break;
41
+ case 'kotlin':
42
+ imports.push({
43
+ name: 'com.margelo.nitro.core.ArrayBuffer',
44
+ language: 'kotlin',
45
+ space: 'system',
46
+ });
47
+ break;
34
48
  }
35
49
  return imports;
36
50
  }
@@ -21,17 +21,26 @@ export class HybridObjectBaseType {
21
21
  }
22
22
  getRequiredImports(language) {
23
23
  const imports = [];
24
- if (language === 'c++') {
25
- imports.push({
26
- language: 'c++',
27
- name: 'memory',
28
- space: 'system',
29
- }, {
30
- name: `NitroModules/HybridObject.hpp`,
31
- forwardDeclaration: getForwardDeclaration('class', 'HybridObject', 'margelo::nitro'),
32
- language: 'c++',
33
- space: 'system',
34
- });
24
+ switch (language) {
25
+ case 'c++':
26
+ imports.push({
27
+ language: 'c++',
28
+ name: 'memory',
29
+ space: 'system',
30
+ }, {
31
+ name: `NitroModules/HybridObject.hpp`,
32
+ forwardDeclaration: getForwardDeclaration('class', 'HybridObject', 'margelo::nitro'),
33
+ language: 'c++',
34
+ space: 'system',
35
+ });
36
+ break;
37
+ case 'kotlin':
38
+ imports.push({
39
+ name: 'com.margelo.nitro.core.HybridObject',
40
+ space: 'system',
41
+ language: 'kotlin',
42
+ });
43
+ break;
35
44
  }
36
45
  return imports;
37
46
  }
@@ -1,4 +1,3 @@
1
- import { getForwardDeclaration } from '../c++/getForwardDeclaration.js';
2
1
  export class MapType {
3
2
  get canBePassedByReference() {
4
3
  // It's a shared_ptr<..>, no ref.
@@ -24,13 +23,28 @@ export class MapType {
24
23
  }
25
24
  getRequiredImports(language) {
26
25
  const imports = [];
27
- if (language === 'c++') {
28
- imports.push({
29
- name: 'NitroModules/AnyMap.hpp',
30
- forwardDeclaration: getForwardDeclaration('class', 'AnyMap', 'NitroModules'),
31
- language: 'c++',
32
- space: 'system',
33
- });
26
+ switch (language) {
27
+ case 'c++':
28
+ imports.push({
29
+ name: 'NitroModules/AnyMap.hpp',
30
+ language: 'c++',
31
+ space: 'system',
32
+ });
33
+ break;
34
+ case 'swift':
35
+ imports.push({
36
+ name: 'NitroModules',
37
+ language: 'swift',
38
+ space: 'system',
39
+ });
40
+ break;
41
+ case 'kotlin':
42
+ imports.push({
43
+ name: 'com.margelo.nitro.core.AnyMap',
44
+ language: 'kotlin',
45
+ space: 'system',
46
+ });
47
+ break;
34
48
  }
35
49
  return imports;
36
50
  }
@@ -50,12 +50,28 @@ export class PromiseType {
50
50
  }
51
51
  getRequiredImports(language) {
52
52
  const imports = this.resultingType.getRequiredImports(language);
53
- if (language === 'c++') {
54
- imports.push({
55
- language: 'c++',
56
- name: 'NitroModules/Promise.hpp',
57
- space: 'system',
58
- });
53
+ switch (language) {
54
+ case 'c++':
55
+ imports.push({
56
+ language: 'c++',
57
+ name: 'NitroModules/Promise.hpp',
58
+ space: 'system',
59
+ });
60
+ break;
61
+ case 'swift':
62
+ imports.push({
63
+ name: 'NitroModules',
64
+ language: 'swift',
65
+ space: 'system',
66
+ });
67
+ break;
68
+ case 'kotlin':
69
+ imports.push({
70
+ name: 'com.margelo.nitro.core.Promise',
71
+ language: 'kotlin',
72
+ space: 'system',
73
+ });
74
+ break;
59
75
  }
60
76
  return imports;
61
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrogen",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "description": "The code-generator for react-native-nitro-modules.",
5
5
  "main": "lib/index",
6
6
  "types": "lib/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "chalk": "^5.3.0",
39
- "react-native-nitro-modules": "^0.30.1",
39
+ "react-native-nitro-modules": "^0.31.0",
40
40
  "ts-morph": "^27.0.0",
41
41
  "yargs": "^18.0.0",
42
42
  "zod": "^4.0.5"
@@ -43,6 +43,7 @@ import {
43
43
  isRecord,
44
44
  } from './isCoreType.js'
45
45
  import { getCustomTypeConfig } from './getCustomTypeConfig.js'
46
+ import { compareLooselyness } from './helpers.js'
46
47
 
47
48
  function getHybridObjectName(type: TSMorphType): string {
48
49
  const symbol = isHybridView(type) ? type.getAliasSymbol() : type.getSymbol()
@@ -189,8 +190,17 @@ export function createType(
189
190
  return new BooleanType()
190
191
  } else if (type.isNumber() || type.isNumberLiteral()) {
191
192
  if (type.isEnumLiteral()) {
192
- // enum literals are technically just numbers - but we treat them differently in C++.
193
- return createType(language, type.getBaseTypeOfLiteralType(), isOptional)
193
+ // An enum is just a number, that's why it's a number literal.
194
+ // Get the base of the literal, which would be our enum's definition.
195
+ const baseType = type.getBaseTypeOfLiteralType()
196
+ if (!baseType.isEnum()) {
197
+ // The base of the literal is not the enum definition, we need to throw.
198
+ throw new Error(
199
+ `The enum "${type.getLiteralValue()}" (${type.getText()}) is either a single-value-enum, or an enum-literal. Use a separately defined enum with at least 2 cases instead!`
200
+ )
201
+ }
202
+ // Call createType(...) now with the enum type.
203
+ return createType(language, baseType, isOptional)
194
204
  }
195
205
  return new NumberType()
196
206
  } else if (type.isString()) {
@@ -293,6 +303,7 @@ export function createType(
293
303
  // Filter out any nulls or undefineds, as those are already treated as `isOptional`.
294
304
  .filter((t) => !t.isNull() && !t.isUndefined() && !t.isVoid())
295
305
  .map((t) => createType(language, t, false))
306
+ .toSorted(compareLooselyness)
296
307
  variants = removeDuplicates(variants)
297
308
 
298
309
  if (variants.length === 1) {
@@ -3,6 +3,7 @@ import type { SourceFile } from './SourceFile.js'
3
3
  import type { Type } from './types/Type.js'
4
4
  import { getTypeAs } from './types/getTypeAs.js'
5
5
  import { OptionalType } from './types/OptionalType.js'
6
+ import { ArrayType } from './types/ArrayType.js'
6
7
 
7
8
  type Comment = '///' | '#'
8
9
 
@@ -50,6 +51,105 @@ export function escapeCppName(string: string): string {
50
51
  return escapedStr
51
52
  }
52
53
 
54
+ /**
55
+ * Get a type's "looselyness".
56
+ * The concept is as follows:
57
+ * - If a `type` is easy to runtime-check in JSI (e.g. a `number` is just `.isNumber()`),
58
+ * it should have a very low looselyness value returned here.
59
+ * - If a `type` is hard to runtime-check in JSI (e.g. `AnyMap` can have any key),
60
+ * it should have a very high looselyness value returned here.
61
+ * - Type's looselyness values are compared against each other to determine in which
62
+ * order they should be runtime type-checked - e.g. `AnyMap` should be last (as it can
63
+ * always be `true` if it's just an `object`), and `Promise` should be sooner (as it can
64
+ * be `instanceof` checked)
65
+ * - As a performance optimization, rank faster checks earlier (e.g. `isNumber()` before
66
+ * `instanceof`, as `instanceof` requires global constructor lookup)
67
+ */
68
+ function getTypeLooselyness(type: Type): number {
69
+ switch (type.kind) {
70
+ case 'array-buffer':
71
+ // We have `.isArrayBuffer()`
72
+ return 0
73
+ case 'bigint':
74
+ // We have `.isBigInt()`
75
+ return 0
76
+ case 'boolean':
77
+ // We have `.isBool()`
78
+ return 0
79
+ case 'null':
80
+ // We have `isNull()`
81
+ return 0
82
+ case 'number':
83
+ // We have `isNumber()` (but it can also be an enum)
84
+ return 1
85
+ case 'optional':
86
+ // We have `isUndefined()`
87
+ return 0
88
+ case 'function':
89
+ // We have `.isFunction()`
90
+ return 0
91
+ case 'string':
92
+ // We have `.isString()` (but it can also be a union)
93
+ return 1
94
+ case 'enum':
95
+ // We have `.isNumber()` or `.isString()` and is within range, only
96
+ // if it's not within range, it can fallback to `string` or `number`
97
+ return 0
98
+ case 'array':
99
+ // We have `.isArray()` - but it should extend the item type's looselyness
100
+ const arrayType = getTypeAs(type, ArrayType)
101
+ return 0.5 * getTypeLooselyness(arrayType.itemType)
102
+ case 'hybrid-object':
103
+ // We have `getNativeState<T>()`
104
+ return 0
105
+ case 'hybrid-object-base':
106
+ // We have `getNativeState<HybridObject>()`
107
+ return 1
108
+ case 'tuple':
109
+ // We have `.isArray()` and `canConvert<T>()`
110
+ return 1
111
+ case 'date':
112
+ // We have `instanceof Date`
113
+ return 2
114
+ case 'error':
115
+ // We have `instanceof Error`
116
+ return 2
117
+ case 'promise':
118
+ // We have `instanceof Promise`
119
+ return 2
120
+ case 'custom-type':
121
+ // Up to the user
122
+ return 1
123
+ case 'record':
124
+ // Super loose, only type counts
125
+ return 3
126
+ case 'map':
127
+ // This is just super loose
128
+ return 4
129
+ case 'struct':
130
+ // We check each property individually
131
+ return 1
132
+ case 'variant':
133
+ // Pretty loose
134
+ return 2
135
+ case 'result-wrapper':
136
+ // Not loose at all
137
+ return 0
138
+ case 'void':
139
+ // Not a type
140
+ return 0
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Compares the "looselyness" of the types.
146
+ * Returns a positive number if {@linkcode a} is more loose than {@linkcode b},
147
+ * and a negative number if otherwise.
148
+ */
149
+ export function compareLooselyness(a: Type, b: Type): number {
150
+ return getTypeLooselyness(a) - getTypeLooselyness(b)
151
+ }
152
+
53
153
  export function isBooleanPropertyPrefix(name: string): boolean {
54
154
  return name.startsWith('is') || name.startsWith('has')
55
155
  }
@@ -23,6 +23,7 @@ export function createKotlinFunction(functionType: FunctionType): SourceFile[] {
23
23
  const extraImports = functionType
24
24
  .getRequiredImports('kotlin')
25
25
  .map((i) => `import ${i.name}`)
26
+ .filter(isNotDuplicate)
26
27
 
27
28
  const kotlinCode = `
28
29
  ${createFileMetadataString(`${name}.kt`)}
@@ -32,7 +33,6 @@ package ${packageName}
32
33
  import androidx.annotation.Keep
33
34
  import com.facebook.jni.HybridData
34
35
  import com.facebook.proguard.annotations.DoNotStrip
35
- import com.margelo.nitro.core.*
36
36
  import dalvik.annotation.optimization.FastNative
37
37
  ${extraImports.join('\n')}
38
38
 
@@ -6,7 +6,7 @@ import { createFileMetadataString, isNotDuplicate } from '../helpers.js'
6
6
  import type { HybridObjectSpec } from '../HybridObjectSpec.js'
7
7
  import { Method } from '../Method.js'
8
8
  import { Property } from '../Property.js'
9
- import type { SourceFile } from '../SourceFile.js'
9
+ import type { SourceFile, SourceImport } from '../SourceFile.js'
10
10
  import { HybridObjectType } from '../types/HybridObjectType.js'
11
11
  import { createFbjniHybridObject } from './FbjniHybridObject.js'
12
12
  import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js'
@@ -20,13 +20,26 @@ export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] {
20
20
  .map((m) => getMethodForwardImplementation(m))
21
21
  .join('\n\n')
22
22
 
23
- const extraImports = [
23
+ const extraImports: SourceImport[] = [
24
24
  ...spec.properties.flatMap((p) => p.getRequiredImports('kotlin')),
25
25
  ...spec.methods.flatMap((m) => m.getRequiredImports('kotlin')),
26
26
  ...spec.baseTypes.flatMap((b) =>
27
27
  new HybridObjectType(b).getRequiredImports('kotlin')
28
28
  ),
29
29
  ]
30
+ if (spec.isHybridView) {
31
+ extraImports.push({
32
+ name: 'com.margelo.nitro.views.HybridView',
33
+ space: 'system',
34
+ language: 'kotlin',
35
+ })
36
+ } else {
37
+ extraImports.push({
38
+ name: 'com.margelo.nitro.core.HybridObject',
39
+ space: 'system',
40
+ language: 'kotlin',
41
+ })
42
+ }
30
43
 
31
44
  let kotlinBase = spec.isHybridView ? 'HybridView' : 'HybridObject'
32
45
  if (spec.baseTypes.length > 0) {
@@ -40,14 +53,9 @@ export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] {
40
53
  kotlinBase = baseHybrid.getCode('kotlin')
41
54
  }
42
55
 
43
- const imports: string[] = []
44
- imports.push('import com.margelo.nitro.core.*')
45
- if (spec.isHybridView) {
46
- imports.push('import com.margelo.nitro.views.*')
47
- }
48
- imports.push(
49
- ...extraImports.map((i) => `import ${i.name}`).filter(isNotDuplicate)
50
- )
56
+ const imports = extraImports
57
+ .map((i) => `import ${i.name}`)
58
+ .filter(isNotDuplicate)
51
59
 
52
60
  const javaPackage = spec.config.getAndroidPackage('java/kotlin')
53
61
 
@@ -35,6 +35,7 @@ val ${p.escapedName}: ${p.getCode('kotlin')}
35
35
  const extraImports = structType.properties
36
36
  .flatMap((t) => t.getRequiredImports('kotlin'))
37
37
  .map((i) => `import ${i.name}`)
38
+ .filter(isNotDuplicate)
38
39
 
39
40
  const code = `
40
41
  ${createFileMetadataString(`${structType.structName}.kt`)}
@@ -43,7 +44,6 @@ package ${packageName}
43
44
 
44
45
  import androidx.annotation.Keep
45
46
  import com.facebook.proguard.annotations.DoNotStrip
46
- import com.margelo.nitro.core.*
47
47
  ${extraImports.join('\n')}
48
48
 
49
49
  /**
@@ -13,6 +13,10 @@ import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js'
13
13
 
14
14
  export function createKotlinVariant(variant: VariantType): SourceFile[] {
15
15
  const jsName = variant.variants.map((v) => v.getCode('kotlin')).join(' | ')
16
+ const cxxName = variant
17
+ .getCode('c++')
18
+ .replaceAll('/* ', '')
19
+ .replaceAll(' */', '')
16
20
  const kotlinName = variant.getAliasName('kotlin')
17
21
  const namespace = `J${kotlinName}_impl`
18
22
 
@@ -72,6 +76,7 @@ fun create(value: ${bridge.getTypeCode('kotlin')}): ${kotlinName} = ${innerName}
72
76
  const extraImports = variant.variants
73
77
  .flatMap((t) => t.getRequiredImports('kotlin'))
74
78
  .map((i) => `import ${i.name}`)
79
+ .filter(isNotDuplicate)
75
80
 
76
81
  const code = `
77
82
  ${createFileMetadataString(`${kotlinName}.kt`)}
@@ -210,7 +215,7 @@ ${createFileMetadataString(`J${kotlinName}.cpp`)}
210
215
 
211
216
  namespace ${cxxNamespace} {
212
217
  /**
213
- * Converts J${kotlinName} to ${variant.getCode('c++')}
218
+ * Converts J${kotlinName} to ${cxxName}
214
219
  */
215
220
  ${variant.getCode('c++')} J${kotlinName}::toCpp() const {
216
221
  ${indent(cppGetIfs.join(' else '), ' ')}
@@ -1,6 +1,6 @@
1
1
  import { NitroConfig } from '../../config/NitroConfig.js'
2
2
  import { indent } from '../../utils.js'
3
- import { createFileMetadataString } from '../helpers.js'
3
+ import { createFileMetadataString, isNotDuplicate } from '../helpers.js'
4
4
  import type { SourceFile } from '../SourceFile.js'
5
5
  import type { FunctionType } from '../types/FunctionType.js'
6
6
  import { SwiftCxxBridgedType } from './SwiftCxxBridgedType.js'
@@ -35,6 +35,7 @@ return ${returnType.parseFromSwiftToCpp('__result', 'swift')}
35
35
  const extraImports = functionType
36
36
  .getRequiredImports('swift')
37
37
  .map((i) => `import ${i.name}`)
38
+ .filter(isNotDuplicate)
38
39
 
39
40
  const code = `
40
41
  ${createFileMetadataString(`${swiftClassName}.swift`)}
@@ -1,5 +1,5 @@
1
1
  import { indent } from '../../utils.js'
2
- import { createFileMetadataString } from '../helpers.js'
2
+ import { createFileMetadataString, isNotDuplicate } from '../helpers.js'
3
3
  import type { SourceFile } from '../SourceFile.js'
4
4
  import { getTypeAs } from '../types/getTypeAs.js'
5
5
  import { OptionalType } from '../types/OptionalType.js'
@@ -41,6 +41,7 @@ export function createSwiftVariant(variant: VariantType): SourceFile {
41
41
  const extraImports = variant.variants
42
42
  .flatMap((t) => t.getRequiredImports('swift'))
43
43
  .map((i) => `import ${i.name}`)
44
+ .filter(isNotDuplicate)
44
45
 
45
46
  const code = `
46
47
  ${createFileMetadataString(`${typename}.swift`)}
@@ -1,5 +1,4 @@
1
1
  import type { Language } from '../../getPlatformSpecs.js'
2
- import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
3
2
  import type { SourceFile, SourceImport } from '../SourceFile.js'
4
3
  import type { Type, TypeKind } from './Type.js'
5
4
 
@@ -32,17 +31,28 @@ export class ArrayBufferType implements Type {
32
31
  }
33
32
  getRequiredImports(language: Language): SourceImport[] {
34
33
  const imports: SourceImport[] = []
35
- if (language === 'c++') {
36
- imports.push({
37
- name: 'NitroModules/ArrayBuffer.hpp',
38
- forwardDeclaration: getForwardDeclaration(
39
- 'class',
40
- 'ArrayBuffer',
41
- 'NitroModules'
42
- ),
43
- language: 'c++',
44
- space: 'system',
45
- })
34
+ switch (language) {
35
+ case 'c++':
36
+ imports.push({
37
+ language: 'c++',
38
+ name: 'NitroModules/ArrayBuffer.hpp',
39
+ space: 'system',
40
+ })
41
+ break
42
+ case 'swift':
43
+ imports.push({
44
+ name: 'NitroModules',
45
+ language: 'swift',
46
+ space: 'system',
47
+ })
48
+ break
49
+ case 'kotlin':
50
+ imports.push({
51
+ name: 'com.margelo.nitro.core.ArrayBuffer',
52
+ language: 'kotlin',
53
+ space: 'system',
54
+ })
55
+ break
46
56
  }
47
57
  return imports
48
58
  }
@@ -30,24 +30,33 @@ export class HybridObjectBaseType implements Type {
30
30
  }
31
31
  getRequiredImports(language: Language): SourceImport[] {
32
32
  const imports: SourceImport[] = []
33
- if (language === 'c++') {
34
- imports.push(
35
- {
36
- language: 'c++',
37
- name: 'memory',
38
- space: 'system',
39
- },
40
- {
41
- name: `NitroModules/HybridObject.hpp`,
42
- forwardDeclaration: getForwardDeclaration(
43
- 'class',
44
- 'HybridObject',
45
- 'margelo::nitro'
46
- ),
47
- language: 'c++',
33
+ switch (language) {
34
+ case 'c++':
35
+ imports.push(
36
+ {
37
+ language: 'c++',
38
+ name: 'memory',
39
+ space: 'system',
40
+ },
41
+ {
42
+ name: `NitroModules/HybridObject.hpp`,
43
+ forwardDeclaration: getForwardDeclaration(
44
+ 'class',
45
+ 'HybridObject',
46
+ 'margelo::nitro'
47
+ ),
48
+ language: 'c++',
49
+ space: 'system',
50
+ }
51
+ )
52
+ break
53
+ case 'kotlin':
54
+ imports.push({
55
+ name: 'com.margelo.nitro.core.HybridObject',
48
56
  space: 'system',
49
- }
50
- )
57
+ language: 'kotlin',
58
+ })
59
+ break
51
60
  }
52
61
  return imports
53
62
  }
@@ -1,5 +1,4 @@
1
1
  import type { Language } from '../../getPlatformSpecs.js'
2
- import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
3
2
  import type { SourceFile, SourceImport } from '../SourceFile.js'
4
3
  import type { Type, TypeKind } from './Type.js'
5
4
 
@@ -32,17 +31,28 @@ export class MapType implements Type {
32
31
  }
33
32
  getRequiredImports(language: Language): SourceImport[] {
34
33
  const imports: SourceImport[] = []
35
- if (language === 'c++') {
36
- imports.push({
37
- name: 'NitroModules/AnyMap.hpp',
38
- forwardDeclaration: getForwardDeclaration(
39
- 'class',
40
- 'AnyMap',
41
- 'NitroModules'
42
- ),
43
- language: 'c++',
44
- space: 'system',
45
- })
34
+ switch (language) {
35
+ case 'c++':
36
+ imports.push({
37
+ name: 'NitroModules/AnyMap.hpp',
38
+ language: 'c++',
39
+ space: 'system',
40
+ })
41
+ break
42
+ case 'swift':
43
+ imports.push({
44
+ name: 'NitroModules',
45
+ language: 'swift',
46
+ space: 'system',
47
+ })
48
+ break
49
+ case 'kotlin':
50
+ imports.push({
51
+ name: 'com.margelo.nitro.core.AnyMap',
52
+ language: 'kotlin',
53
+ space: 'system',
54
+ })
55
+ break
46
56
  }
47
57
  return imports
48
58
  }
@@ -60,12 +60,28 @@ export class PromiseType implements Type {
60
60
  getRequiredImports(language: Language): SourceImport[] {
61
61
  const imports: SourceImport[] =
62
62
  this.resultingType.getRequiredImports(language)
63
- if (language === 'c++') {
64
- imports.push({
65
- language: 'c++',
66
- name: 'NitroModules/Promise.hpp',
67
- space: 'system',
68
- })
63
+ switch (language) {
64
+ case 'c++':
65
+ imports.push({
66
+ language: 'c++',
67
+ name: 'NitroModules/Promise.hpp',
68
+ space: 'system',
69
+ })
70
+ break
71
+ case 'swift':
72
+ imports.push({
73
+ name: 'NitroModules',
74
+ language: 'swift',
75
+ space: 'system',
76
+ })
77
+ break
78
+ case 'kotlin':
79
+ imports.push({
80
+ name: 'com.margelo.nitro.core.Promise',
81
+ language: 'kotlin',
82
+ space: 'system',
83
+ })
84
+ break
69
85
  }
70
86
  return imports
71
87
  }