nitrogen 0.31.2 → 0.31.3
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.
- package/lib/createPlatformSpec.js +8 -0
- package/lib/getPlatformSpecs.d.ts +3 -2
- package/lib/getPlatformSpecs.js +12 -10
- package/lib/nitrogen.js +4 -14
- package/lib/syntax/Method.js +4 -0
- package/lib/syntax/c++/CppHybridObject.js +4 -2
- package/lib/syntax/createType.js +6 -6
- package/lib/syntax/isMemberOverridingFromBase.d.ts +11 -0
- package/lib/syntax/isMemberOverridingFromBase.js +64 -0
- package/lib/syntax/isOverridingFromBase.d.ts +11 -0
- package/lib/syntax/isOverridingFromBase.js +64 -0
- package/lib/syntax/kotlin/FbjniHybridObject.js +7 -0
- package/lib/syntax/kotlin/KotlinHybridObject.js +12 -2
- package/lib/syntax/kotlin/KotlinStruct.js +49 -14
- package/lib/syntax/kotlin/isBaseObjectMethodName.d.ts +7 -0
- package/lib/syntax/kotlin/isBaseObjectMethodName.js +9 -0
- package/lib/syntax/swift/SwiftHybridObject.js +8 -1
- package/lib/syntax/swift/SwiftHybridObjectBridge.js +12 -1
- package/lib/syntax/types/AnyHybridObjectType.d.ts +11 -0
- package/lib/syntax/types/AnyHybridObjectType.js +40 -0
- package/package.json +2 -2
- package/src/createPlatformSpec.ts +16 -0
- package/src/getPlatformSpecs.ts +24 -15
- package/src/nitrogen.ts +5 -16
- package/src/syntax/Method.ts +6 -0
- package/src/syntax/c++/CppHybridObject.ts +7 -2
- package/src/syntax/createType.ts +5 -5
- package/src/syntax/isMemberOverridingFromBase.ts +79 -0
- package/src/syntax/kotlin/FbjniHybridObject.ts +7 -0
- package/src/syntax/kotlin/KotlinHybridObject.ts +12 -2
- package/src/syntax/kotlin/KotlinStruct.ts +61 -15
- package/src/syntax/swift/SwiftHybridObject.ts +8 -1
- package/src/syntax/swift/SwiftHybridObjectBridge.ts +12 -1
- package/src/syntax/types/{HybridObjectBaseType.ts → AnyHybridObjectType.ts} +2 -9
|
@@ -9,6 +9,7 @@ import { createType } from './syntax/createType.js';
|
|
|
9
9
|
import { Parameter } from './syntax/Parameter.js';
|
|
10
10
|
import { getBaseTypes, getHybridObjectNitroModuleConfig } from './utils.js';
|
|
11
11
|
import { NitroConfig } from './config/NitroConfig.js';
|
|
12
|
+
import { isMemberOverridingFromBase } from './syntax/isMemberOverridingFromBase.js';
|
|
12
13
|
export function generatePlatformFiles(interfaceType, language) {
|
|
13
14
|
const spec = getHybridObjectSpec(interfaceType, language);
|
|
14
15
|
// TODO: We currently just call this so the HybridObject itself is a "known type".
|
|
@@ -105,6 +106,13 @@ function getHybridObjectSpec(type, language) {
|
|
|
105
106
|
isHybridView: isHybridView(type),
|
|
106
107
|
config: config,
|
|
107
108
|
};
|
|
109
|
+
for (const member of [...properties, ...methods]) {
|
|
110
|
+
const isOverridingBaseMember = isMemberOverridingFromBase(member.name, spec, language);
|
|
111
|
+
if (isOverridingBaseMember) {
|
|
112
|
+
throw new Error(`\`${name}.${member.name}\` is overriding a member of one of it's base classes. ` +
|
|
113
|
+
`This is unsupported, override on the native side instead!`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
108
116
|
return spec;
|
|
109
117
|
}
|
|
110
118
|
function generateCppFiles(spec) {
|
|
@@ -3,6 +3,7 @@ import type { InterfaceDeclaration, Type, TypeAliasDeclaration } from 'ts-morph'
|
|
|
3
3
|
export type Platform = keyof Required<PlatformSpec>;
|
|
4
4
|
export type Language = Required<PlatformSpec>[keyof PlatformSpec];
|
|
5
5
|
export declare function isDirectlyHybridObject(type: Type): boolean;
|
|
6
|
+
export declare function isDirectlyAnyHybridObject(type: Type): boolean;
|
|
6
7
|
export declare function extendsHybridObject(type: Type, recursive: boolean): boolean;
|
|
7
8
|
export declare function isHybridViewProps(type: Type): boolean;
|
|
8
9
|
export declare function isHybridViewMethods(type: Type): boolean;
|
|
@@ -13,5 +14,5 @@ export declare function isAnyHybridSubclass(type: Type): boolean;
|
|
|
13
14
|
* this method returns the platforms it exists on.
|
|
14
15
|
* If it doesn't extend `HybridObject`, this returns `undefined`.
|
|
15
16
|
*/
|
|
16
|
-
export declare function getHybridObjectPlatforms(declaration: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec
|
|
17
|
-
export declare function getHybridViewPlatforms(view: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec
|
|
17
|
+
export declare function getHybridObjectPlatforms(declaration: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec;
|
|
18
|
+
export declare function getHybridViewPlatforms(view: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec;
|
package/lib/getPlatformSpecs.js
CHANGED
|
@@ -39,22 +39,19 @@ function getPlatformSpec(typeName, platformSpecs) {
|
|
|
39
39
|
// Property name (ios, android)
|
|
40
40
|
const platform = property.getName();
|
|
41
41
|
if (!isValidPlatform(platform)) {
|
|
42
|
-
|
|
42
|
+
throw new Error(`${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
|
|
43
43
|
`Valid platforms are: [${allPlatforms.join(', ')}]`);
|
|
44
|
-
continue;
|
|
45
44
|
}
|
|
46
45
|
// Value (swift, kotlin, c++)
|
|
47
46
|
const language = getLiteralValue(property);
|
|
48
47
|
if (!isValidLanguage(language)) {
|
|
49
|
-
|
|
48
|
+
throw new Error(`${typeName}: Language ${language} is not a valid language for ${platform}! ` +
|
|
50
49
|
`Valid languages are: [${platformLanguages[platform].join(', ')}]`);
|
|
51
|
-
continue;
|
|
52
50
|
}
|
|
53
51
|
// Double-check that language works on this platform (android: kotlin/c++, ios: swift/c++)
|
|
54
52
|
if (!isValidLanguageForPlatform(language, platform)) {
|
|
55
|
-
|
|
53
|
+
throw new Error(`${typeName}: Language ${language} is not a valid language for ${platform}! ` +
|
|
56
54
|
`Valid languages are: [${platformLanguages[platform].join(', ')}]`);
|
|
57
|
-
continue;
|
|
58
55
|
}
|
|
59
56
|
// @ts-expect-error because TypeScript isn't smart enough yet to correctly cast after the `isValidLanguageForPlatform` check.
|
|
60
57
|
result[platform] = language;
|
|
@@ -86,6 +83,9 @@ function extendsType(type, name, recursive) {
|
|
|
86
83
|
export function isDirectlyHybridObject(type) {
|
|
87
84
|
return isDirectlyType(type, 'HybridObject');
|
|
88
85
|
}
|
|
86
|
+
export function isDirectlyAnyHybridObject(type) {
|
|
87
|
+
return isDirectlyType(type, 'AnyHybridObject');
|
|
88
|
+
}
|
|
89
89
|
export function extendsHybridObject(type, recursive) {
|
|
90
90
|
return extendsType(type, 'HybridObject', recursive);
|
|
91
91
|
}
|
|
@@ -128,9 +128,11 @@ export function getHybridObjectPlatforms(declaration) {
|
|
|
128
128
|
}
|
|
129
129
|
const genericArguments = base.getTypeArguments();
|
|
130
130
|
const platformSpecsArgument = genericArguments[0];
|
|
131
|
-
if (platformSpecsArgument == null
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
if (platformSpecsArgument == null ||
|
|
132
|
+
platformSpecsArgument.getProperties().length === 0) {
|
|
133
|
+
// it uses `HybridObject` without generic arguments. We throw as we don't know what to generate.
|
|
134
|
+
throw new Error(`HybridObject ${declaration.getName()} does not declare any platforms in the \`HybridObject\` type argument! ` +
|
|
135
|
+
`Pass at least one platform (and language) to \`interface ${declaration.getName()} extends HybridObject<{ ... }>\``);
|
|
134
136
|
}
|
|
135
137
|
return getPlatformSpec(declaration.getName(), platformSpecsArgument);
|
|
136
138
|
}
|
|
@@ -140,7 +142,7 @@ export function getHybridViewPlatforms(view) {
|
|
|
140
142
|
const isHybridViewType = Node.isTypeReference(hybridViewTypeNode) &&
|
|
141
143
|
hybridViewTypeNode.getTypeName().getText() === 'HybridView';
|
|
142
144
|
if (!isHybridViewType) {
|
|
143
|
-
|
|
145
|
+
throw new Error(`${view.getName()} looks like a HybridView, but doesn't seem to alias HybridView<...>!`);
|
|
144
146
|
}
|
|
145
147
|
const genericArguments = hybridViewTypeNode.getTypeArguments();
|
|
146
148
|
const platformSpecArg = genericArguments[2];
|
package/lib/nitrogen.js
CHANGED
|
@@ -62,29 +62,19 @@ export async function runNitrogen({ baseDirectory, outputDirectory, }) {
|
|
|
62
62
|
let platformSpec;
|
|
63
63
|
if (isHybridView(declaration.getType())) {
|
|
64
64
|
// Hybrid View Props
|
|
65
|
-
|
|
66
|
-
if (targetPlatforms == null) {
|
|
67
|
-
// It does not extend HybridView, continue..
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
platformSpec = targetPlatforms;
|
|
65
|
+
platformSpec = getHybridViewPlatforms(declaration);
|
|
71
66
|
}
|
|
72
67
|
else if (extendsHybridObject(declaration.getType(), true)) {
|
|
73
68
|
// Hybrid View
|
|
74
|
-
|
|
75
|
-
if (targetPlatforms == null) {
|
|
76
|
-
// It does not extend HybridObject, continue..
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
platformSpec = targetPlatforms;
|
|
69
|
+
platformSpec = getHybridObjectPlatforms(declaration);
|
|
80
70
|
}
|
|
81
71
|
else {
|
|
82
72
|
continue;
|
|
83
73
|
}
|
|
84
74
|
const platforms = Object.keys(platformSpec);
|
|
85
75
|
if (platforms.length === 0) {
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
throw new Error(`${typeName} does not declare any platforms in HybridObject<T> - nothing can be generated. ` +
|
|
77
|
+
`For example, to generate a C++ HybridObject, use \`interface ${typeName} extends HybridObject<{ ios: 'c++', android: 'c++' }> { ... }\``);
|
|
88
78
|
}
|
|
89
79
|
targetSpecs++;
|
|
90
80
|
Logger.info(` ⚙️ Generating specs for HybridObject "${chalk.bold(typeName)}"...`);
|
package/lib/syntax/Method.js
CHANGED
|
@@ -12,6 +12,10 @@ export class Method {
|
|
|
12
12
|
if (this.name.startsWith('__')) {
|
|
13
13
|
throw new Error(`Method names are not allowed to start with two underscores (__)! (In ${this.jsSignature})`);
|
|
14
14
|
}
|
|
15
|
+
if (this.name === 'dispose') {
|
|
16
|
+
// .dispose() does some special JSI magic that we loose if the user overrides it in his spec.
|
|
17
|
+
throw new Error(`dispose() must not be overridden from TypeScript! You can override dispose() natively.`);
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
get jsSignature() {
|
|
17
21
|
const returnType = this.returnType.kind;
|
|
@@ -23,6 +23,8 @@ export function createCppHybridObject(spec) {
|
|
|
23
23
|
.filter(isNotDuplicate);
|
|
24
24
|
const cxxNamespace = spec.config.getCxxNamespace('c++');
|
|
25
25
|
const name = getHybridObjectName(spec.name);
|
|
26
|
+
const properties = spec.properties.map((p) => p.getCode('c++', { virtual: true }));
|
|
27
|
+
const methods = spec.methods.map((m) => m.getCode('c++', { virtual: true }));
|
|
26
28
|
const bases = ['public virtual HybridObject'];
|
|
27
29
|
for (const base of spec.baseTypes) {
|
|
28
30
|
const baseName = getHybridObjectName(base.name).HybridTSpec;
|
|
@@ -70,11 +72,11 @@ namespace ${cxxNamespace} {
|
|
|
70
72
|
|
|
71
73
|
public:
|
|
72
74
|
// Properties
|
|
73
|
-
${indent(
|
|
75
|
+
${indent(properties.join('\n'), ' ')}
|
|
74
76
|
|
|
75
77
|
public:
|
|
76
78
|
// Methods
|
|
77
|
-
${indent(
|
|
79
|
+
${indent(methods.join('\n'), ' ')}
|
|
78
80
|
|
|
79
81
|
protected:
|
|
80
82
|
// Hybrid Setup
|
package/lib/syntax/createType.js
CHANGED
|
@@ -19,8 +19,8 @@ import { getInterfaceProperties } from './getInterfaceProperties.js';
|
|
|
19
19
|
import { VariantType } from './types/VariantType.js';
|
|
20
20
|
import { MapType } from './types/MapType.js';
|
|
21
21
|
import { TupleType } from './types/TupleType.js';
|
|
22
|
-
import { isAnyHybridSubclass,
|
|
23
|
-
import {
|
|
22
|
+
import { isAnyHybridSubclass, isDirectlyAnyHybridObject, isHybridView, } from '../getPlatformSpecs.js';
|
|
23
|
+
import { AnyHybridObjectType } from './types/AnyHybridObjectType.js';
|
|
24
24
|
import { ErrorType } from './types/ErrorType.js';
|
|
25
25
|
import { getBaseTypes, getHybridObjectNitroModuleConfig } from '../utils.js';
|
|
26
26
|
import { DateType } from './types/DateType.js';
|
|
@@ -253,6 +253,10 @@ export function createType(language, type, isOptional) {
|
|
|
253
253
|
return new VariantType(variants, name);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
+
else if (isDirectlyAnyHybridObject(type)) {
|
|
257
|
+
// It is a HybridObject directly/literally. Base type
|
|
258
|
+
return new AnyHybridObjectType();
|
|
259
|
+
}
|
|
256
260
|
else if (isAnyHybridSubclass(type)) {
|
|
257
261
|
// It is another HybridObject being referenced!
|
|
258
262
|
const typename = getHybridObjectName(type);
|
|
@@ -263,10 +267,6 @@ export function createType(language, type, isOptional) {
|
|
|
263
267
|
const sourceConfig = getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current;
|
|
264
268
|
return new HybridObjectType(typename, language, baseHybrids, sourceConfig);
|
|
265
269
|
}
|
|
266
|
-
else if (isDirectlyHybridObject(type)) {
|
|
267
|
-
// It is a HybridObject directly/literally. Base type
|
|
268
|
-
return new HybridObjectBaseType();
|
|
269
|
-
}
|
|
270
270
|
else if (type.isInterface()) {
|
|
271
271
|
// It is an `interface T { ... }`, which is a `struct`
|
|
272
272
|
const typename = type.getSymbolOrThrow().getName();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Language } from '../getPlatformSpecs.js';
|
|
2
|
+
import type { HybridObjectSpec } from './HybridObjectSpec.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when the given {@linkcode memberName} is overriding a
|
|
5
|
+
* property or method from any base class inside the given
|
|
6
|
+
* {@linkcode hybridObject}'s prototype chain (all the way up).
|
|
7
|
+
*
|
|
8
|
+
* For example, `"toString"` would return `true` since it overrides from base HybridObject.
|
|
9
|
+
* On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isMemberOverridingFromBase(memberName: string, hybridObject: HybridObjectSpec, language: Language): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function getMemberNamesOfBaseType(language) {
|
|
2
|
+
switch (language) {
|
|
3
|
+
case 'c++':
|
|
4
|
+
// C++ classes don't have any base type.
|
|
5
|
+
return [];
|
|
6
|
+
case 'swift':
|
|
7
|
+
// Swift classes conform to `AnyObject`, but that doesn't have any properties
|
|
8
|
+
return [];
|
|
9
|
+
case 'kotlin':
|
|
10
|
+
// Kotlin/JVM classes always extends `Any`, which has 3 methods
|
|
11
|
+
return ['toString', 'equals', 'hashCode'];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function getMemberNamesOfHybridObject() {
|
|
15
|
+
const allKeys = {
|
|
16
|
+
__type: true,
|
|
17
|
+
dispose: true,
|
|
18
|
+
equals: true,
|
|
19
|
+
name: true,
|
|
20
|
+
toString: true,
|
|
21
|
+
};
|
|
22
|
+
return Object.keys(allKeys);
|
|
23
|
+
}
|
|
24
|
+
function flatBaseTypes(type) {
|
|
25
|
+
return type.baseTypes.flatMap((b) => [b, ...flatBaseTypes(b)]);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns true when the given {@linkcode memberName} is overriding a
|
|
29
|
+
* property or method from any base class inside the given
|
|
30
|
+
* {@linkcode hybridObject}'s prototype chain (all the way up).
|
|
31
|
+
*
|
|
32
|
+
* For example, `"toString"` would return `true` since it overrides from base HybridObject.
|
|
33
|
+
* On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
|
|
34
|
+
*/
|
|
35
|
+
export function isMemberOverridingFromBase(memberName, hybridObject, language) {
|
|
36
|
+
// 1. Check if the HybridObject inherits from other HybridObjects,
|
|
37
|
+
// if yes, check if those have properties of that given name.
|
|
38
|
+
const allBases = flatBaseTypes(hybridObject);
|
|
39
|
+
const anyBaseOverrides = allBases.some((h) => {
|
|
40
|
+
if (h.properties.some((p) => p.name === memberName)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (h.methods.some((m) => m.name === memberName)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
});
|
|
48
|
+
if (anyBaseOverrides) {
|
|
49
|
+
// A HybridObject base type has the same property name - we need to override it.
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// 2. Check if the base `HybridObject` type contains a property of the given name
|
|
53
|
+
const baseHybridObjectProps = getMemberNamesOfHybridObject();
|
|
54
|
+
if (baseHybridObjectProps.includes(memberName)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
// 3. Check if the base type in our language contains a property of the given name
|
|
58
|
+
const baseTypeProps = getMemberNamesOfBaseType(language);
|
|
59
|
+
if (baseTypeProps.includes(memberName)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// 4. Apparently no base type has a property of that name - we are safe!
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Language } from '../getPlatformSpecs.js';
|
|
2
|
+
import type { HybridObjectSpec } from './HybridObjectSpec.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when the given {@linkcode memberName} is overriding a
|
|
5
|
+
* property or method from any base class inside the given
|
|
6
|
+
* {@linkcode hybridObject}'s prototype chain (all the way up).
|
|
7
|
+
*
|
|
8
|
+
* For example, `"toString"` would return `true` since it overrides from base HybridObject.
|
|
9
|
+
* On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isMemberOverridingFromBase(memberName: string, hybridObject: HybridObjectSpec, language: Language): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function getMemberNamesOfBaseType(language) {
|
|
2
|
+
switch (language) {
|
|
3
|
+
case 'c++':
|
|
4
|
+
// C++ classes don't have any base type.
|
|
5
|
+
return [];
|
|
6
|
+
case 'swift':
|
|
7
|
+
// Swift classes conform to `AnyObject`, but that doesn't have any properties
|
|
8
|
+
return [];
|
|
9
|
+
case 'kotlin':
|
|
10
|
+
// Kotlin/JVM classes always extends `Any`, which has 3 methods
|
|
11
|
+
return ['toString', 'equals', 'hashCode'];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function getMemberNamesOfHybridObject() {
|
|
15
|
+
const allKeys = {
|
|
16
|
+
__type: true,
|
|
17
|
+
dispose: true,
|
|
18
|
+
equals: true,
|
|
19
|
+
name: true,
|
|
20
|
+
toString: true,
|
|
21
|
+
};
|
|
22
|
+
return Object.keys(allKeys);
|
|
23
|
+
}
|
|
24
|
+
function flatBaseTypes(type) {
|
|
25
|
+
return type.baseTypes.flatMap((b) => [b, ...flatBaseTypes(b)]);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns true when the given {@linkcode memberName} is overriding a
|
|
29
|
+
* property or method from any base class inside the given
|
|
30
|
+
* {@linkcode hybridObject}'s prototype chain (all the way up).
|
|
31
|
+
*
|
|
32
|
+
* For example, `"toString"` would return `true` since it overrides from base HybridObject.
|
|
33
|
+
* On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
|
|
34
|
+
*/
|
|
35
|
+
export function isMemberOverridingFromBase(memberName, hybridObject, language) {
|
|
36
|
+
// 1. Check if the HybridObject inherits from other HybridObjects,
|
|
37
|
+
// if yes, check if those have properties of that given name.
|
|
38
|
+
const allBases = flatBaseTypes(hybridObject);
|
|
39
|
+
const anyBaseOverrides = allBases.some((h) => {
|
|
40
|
+
if (h.properties.some((p) => p.name === memberName)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (h.methods.some((m) => m.name === memberName)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
});
|
|
48
|
+
if (anyBaseOverrides) {
|
|
49
|
+
// A HybridObject base type has the same property name - we need to override it.
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// 2. Check if the base `HybridObject` type contains a property of the given name
|
|
53
|
+
const baseHybridObjectProps = getMemberNamesOfHybridObject();
|
|
54
|
+
if (baseHybridObjectProps.includes(memberName)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
// 3. Check if the base type in our language contains a property of the given name
|
|
58
|
+
const baseTypeProps = getMemberNamesOfBaseType(language);
|
|
59
|
+
if (baseTypeProps.includes(memberName)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// 4. Apparently no base type has a property of that name - we are safe!
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
@@ -94,6 +94,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
|
|
|
94
94
|
public:
|
|
95
95
|
size_t getExternalMemorySize() noexcept override;
|
|
96
96
|
void dispose() noexcept override;
|
|
97
|
+
std::string toString() override;
|
|
97
98
|
|
|
98
99
|
public:
|
|
99
100
|
inline const jni::global_ref<${name.JHybridTSpec}::javaobject>& getJavaPart() const noexcept {
|
|
@@ -176,6 +177,12 @@ namespace ${cxxNamespace} {
|
|
|
176
177
|
method(_javaPart);
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
std::string ${name.JHybridTSpec}::toString() {
|
|
181
|
+
static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
|
|
182
|
+
auto javaString = method(_javaPart);
|
|
183
|
+
return javaString->toStdString();
|
|
184
|
+
}
|
|
185
|
+
|
|
179
186
|
// Properties
|
|
180
187
|
${indent(propertiesImpl, ' ')}
|
|
181
188
|
|
|
@@ -83,6 +83,11 @@ abstract class ${name.HybridTSpec}: ${kotlinBase}() {
|
|
|
83
83
|
super.updateNative(hybridData)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Default implementation of \`HybridObject.toString()\`
|
|
87
|
+
override fun toString(): String {
|
|
88
|
+
return "[HybridObject ${name.T}]"
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
// Properties
|
|
87
92
|
${indent(properties, ' ')}
|
|
88
93
|
|
|
@@ -173,7 +178,9 @@ set(value) {
|
|
|
173
178
|
}
|
|
174
179
|
`.trim());
|
|
175
180
|
}
|
|
176
|
-
const code = property.getCode('kotlin', {
|
|
181
|
+
const code = property.getCode('kotlin', {
|
|
182
|
+
virtual: true,
|
|
183
|
+
});
|
|
177
184
|
return `
|
|
178
185
|
${code}
|
|
179
186
|
|
|
@@ -182,7 +189,10 @@ private ${keyword} ${property.name}_cxx: ${bridged.getTypeCode('kotlin')}
|
|
|
182
189
|
`.trim();
|
|
183
190
|
}
|
|
184
191
|
else {
|
|
185
|
-
const code = property.getCode('kotlin', {
|
|
192
|
+
const code = property.getCode('kotlin', {
|
|
193
|
+
doNotStrip: true,
|
|
194
|
+
virtual: true,
|
|
195
|
+
});
|
|
186
196
|
return code;
|
|
187
197
|
}
|
|
188
198
|
}
|
|
@@ -6,29 +6,41 @@ import { createFileMetadataString, isNotDuplicate } from '../helpers.js';
|
|
|
6
6
|
import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js';
|
|
7
7
|
export function createKotlinStruct(structType) {
|
|
8
8
|
const packageName = NitroConfig.current.getAndroidPackage('java/kotlin');
|
|
9
|
-
const
|
|
10
|
-
.
|
|
9
|
+
const bridgedProperties = structType.properties.map((p) => ({
|
|
10
|
+
name: p.escapedName,
|
|
11
|
+
type: new KotlinCxxBridgedType(p),
|
|
12
|
+
}));
|
|
13
|
+
// const parameters = structType.properties
|
|
14
|
+
// .map((p) =>
|
|
15
|
+
// `
|
|
16
|
+
// @DoNotStrip
|
|
17
|
+
// @Keep
|
|
18
|
+
// val ${p.escapedName}: ${p.getCode('kotlin')}
|
|
19
|
+
// `.trim()
|
|
20
|
+
// )
|
|
21
|
+
// .join(',\n')
|
|
22
|
+
const properties = bridgedProperties
|
|
23
|
+
.map(({ name, type }) => {
|
|
24
|
+
return `
|
|
11
25
|
@DoNotStrip
|
|
12
26
|
@Keep
|
|
13
|
-
val ${
|
|
14
|
-
`.trim()
|
|
27
|
+
val ${name}: ${type.getTypeCode('kotlin', false)}
|
|
28
|
+
`.trim();
|
|
29
|
+
})
|
|
15
30
|
.join(',\n');
|
|
16
|
-
const cxxCreateFunctionParameters =
|
|
17
|
-
.map((
|
|
18
|
-
|
|
19
|
-
return `${p.escapedName}: ${bridged.getTypeCode('kotlin', false)}`;
|
|
31
|
+
const cxxCreateFunctionParameters = bridgedProperties
|
|
32
|
+
.map(({ name, type }) => {
|
|
33
|
+
return `${name}: ${type.getTypeCode('kotlin', false)}`;
|
|
20
34
|
})
|
|
21
35
|
.join(', ');
|
|
22
|
-
const cxxCreateFunctionForward =
|
|
23
|
-
.map((p) =>
|
|
24
|
-
const bridged = new KotlinCxxBridgedType(p);
|
|
25
|
-
return bridged.parseFromCppToKotlin(p.escapedName, 'kotlin', false);
|
|
26
|
-
})
|
|
36
|
+
const cxxCreateFunctionForward = bridgedProperties
|
|
37
|
+
.map((p) => p.name)
|
|
27
38
|
.join(', ');
|
|
28
39
|
const extraImports = structType.properties
|
|
29
40
|
.flatMap((t) => t.getRequiredImports('kotlin'))
|
|
30
41
|
.map((i) => `import ${i.name}`)
|
|
31
42
|
.filter(isNotDuplicate);
|
|
43
|
+
const secondaryConstructor = createKotlinConstructor(structType);
|
|
32
44
|
const code = `
|
|
33
45
|
${createFileMetadataString(`${structType.structName}.kt`)}
|
|
34
46
|
|
|
@@ -44,8 +56,10 @@ ${extraImports.join('\n')}
|
|
|
44
56
|
@DoNotStrip
|
|
45
57
|
@Keep
|
|
46
58
|
data class ${structType.structName}(
|
|
47
|
-
${indent(
|
|
59
|
+
${indent(properties, ' ')}
|
|
48
60
|
) {
|
|
61
|
+
${indent(secondaryConstructor, ' ')}
|
|
62
|
+
|
|
49
63
|
private companion object {
|
|
50
64
|
/**
|
|
51
65
|
* Constructor called from C++
|
|
@@ -132,6 +146,27 @@ namespace ${cxxNamespace} {
|
|
|
132
146
|
});
|
|
133
147
|
return files;
|
|
134
148
|
}
|
|
149
|
+
function createKotlinConstructor(structType) {
|
|
150
|
+
const bridgedProperties = structType.properties.map((p) => ({
|
|
151
|
+
name: p.escapedName,
|
|
152
|
+
type: new KotlinCxxBridgedType(p),
|
|
153
|
+
}));
|
|
154
|
+
const needsSpecialHandling = bridgedProperties.some(({ type }) => type.needsSpecialHandling);
|
|
155
|
+
if (needsSpecialHandling) {
|
|
156
|
+
const kotlinParams = structType.properties.map((p) => `${p.escapedName}: ${p.getCode('kotlin')}`);
|
|
157
|
+
const paramsForward = bridgedProperties.map(({ name, type }) => type.parseFromKotlinToCpp(name, 'kotlin'));
|
|
158
|
+
return `
|
|
159
|
+
/**
|
|
160
|
+
* Create a new instance of ${structType.structName} from Kotlin
|
|
161
|
+
*/
|
|
162
|
+
constructor(${kotlinParams.join(', ')}):
|
|
163
|
+
this(${paramsForward.join(', ')})
|
|
164
|
+
`.trim();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
return `/* primary constructor */`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
135
170
|
function createJNIStructInitializer(structType) {
|
|
136
171
|
const lines = ['static const auto clazz = javaClassStatic();'];
|
|
137
172
|
for (const prop of structType.properties) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Method } from '../Method.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns `true` if the given {@linkcode method} is a
|
|
4
|
+
* method that exists in the base `Object` type in Java.
|
|
5
|
+
* If this is true, it needs an `override` modifier.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isBaseObjectMethodName(method: Method): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const kotlinAnyMethodNames = ['equals', 'hashCode', 'toString'];
|
|
2
|
+
/**
|
|
3
|
+
* Returns `true` if the given {@linkcode method} is a
|
|
4
|
+
* method that exists in the base `Object` type in Java.
|
|
5
|
+
* If this is true, it needs an `override` modifier.
|
|
6
|
+
*/
|
|
7
|
+
export function isBaseObjectMethodName(method) {
|
|
8
|
+
return kotlinAnyMethodNames.includes(method.name);
|
|
9
|
+
}
|
|
@@ -8,7 +8,7 @@ export function createSwiftHybridObject(spec) {
|
|
|
8
8
|
const name = getHybridObjectName(spec.name);
|
|
9
9
|
const protocolName = name.HybridTSpec;
|
|
10
10
|
const properties = spec.properties.map((p) => p.getCode('swift')).join('\n');
|
|
11
|
-
const methods = spec.methods.map((
|
|
11
|
+
const methods = spec.methods.map((m) => m.getCode('swift')).join('\n');
|
|
12
12
|
const extraImports = [
|
|
13
13
|
...spec.properties.flatMap((p) => p.getRequiredImports('swift')),
|
|
14
14
|
...spec.methods.flatMap((m) => m.getRequiredImports('swift')),
|
|
@@ -69,6 +69,13 @@ public protocol ${protocolName}_protocol: ${protocolBaseClasses.join(', ')} {
|
|
|
69
69
|
${indent(methods, ' ')}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
public extension ${protocolName}_protocol {
|
|
73
|
+
/// Default implementation of \`\`HybridObject.toString\`\`
|
|
74
|
+
func toString() -> String {
|
|
75
|
+
return "[HybridObject ${name.T}]"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
/// See \`\`${protocolName}\`\`
|
|
73
80
|
open class ${protocolName}_base${classBaseClasses.length > 0 ? `: ${classBaseClasses.join(',')}` : ''} {
|
|
74
81
|
${indent(baseMembers.join('\n'), ' ')}
|
|
@@ -148,7 +148,7 @@ ${hasBase ? `open class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `o
|
|
|
148
148
|
*/
|
|
149
149
|
public func getCxxPart() -> bridge.${bridge.specializationName} {
|
|
150
150
|
let cachedCxxPart = self.__cxxPart.lock()
|
|
151
|
-
if cachedCxxPart
|
|
151
|
+
if Bool(fromCxx: cachedCxxPart) {
|
|
152
152
|
return cachedCxxPart
|
|
153
153
|
} else {
|
|
154
154
|
let newCxxPart = bridge.${bridge.funcName}(self.toUnsafe())
|
|
@@ -177,6 +177,14 @@ ${hasBase ? `open class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `o
|
|
|
177
177
|
self.__implementation.dispose()
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Call toString() on the Swift class.
|
|
182
|
+
*/
|
|
183
|
+
@inline(__always)
|
|
184
|
+
public ${hasBase ? 'override func' : 'func'} toString() -> String {
|
|
185
|
+
return self.__implementation.toString()
|
|
186
|
+
}
|
|
187
|
+
|
|
180
188
|
// Properties
|
|
181
189
|
${indent(propertiesBridge.join('\n\n'), ' ')}
|
|
182
190
|
|
|
@@ -330,6 +338,9 @@ namespace ${cxxNamespace} {
|
|
|
330
338
|
void dispose() noexcept override {
|
|
331
339
|
_swiftPart.dispose();
|
|
332
340
|
}
|
|
341
|
+
std::string toString() override {
|
|
342
|
+
return _swiftPart.toString();
|
|
343
|
+
}
|
|
333
344
|
|
|
334
345
|
public:
|
|
335
346
|
// Properties
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Language } from '../../getPlatformSpecs.js';
|
|
2
|
+
import type { SourceFile, SourceImport } from '../SourceFile.js';
|
|
3
|
+
import type { Type, TypeKind } from './Type.js';
|
|
4
|
+
export declare class AnyHybridObjectType implements Type {
|
|
5
|
+
constructor();
|
|
6
|
+
get canBePassedByReference(): boolean;
|
|
7
|
+
get kind(): TypeKind;
|
|
8
|
+
getCode(language: Language): string;
|
|
9
|
+
getExtraFiles(): SourceFile[];
|
|
10
|
+
getRequiredImports(language: Language): SourceImport[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getForwardDeclaration } from '../c++/getForwardDeclaration.js';
|
|
2
|
+
export class AnyHybridObjectType {
|
|
3
|
+
constructor() { }
|
|
4
|
+
get canBePassedByReference() {
|
|
5
|
+
// It's a shared_ptr<..>, no copy.
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
get kind() {
|
|
9
|
+
return 'hybrid-object-base';
|
|
10
|
+
}
|
|
11
|
+
getCode(language) {
|
|
12
|
+
switch (language) {
|
|
13
|
+
case 'c++':
|
|
14
|
+
return `std::shared_ptr<HybridObject>`;
|
|
15
|
+
default:
|
|
16
|
+
throw new Error(`\`AnyHybridObject\` cannot be used directly in ${language} yet. Use a specific derived class of \`HybridObject\` instead!`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
getExtraFiles() {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
getRequiredImports(language) {
|
|
23
|
+
const imports = [];
|
|
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
|
+
}
|
|
38
|
+
return imports;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitrogen",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.3",
|
|
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.31.
|
|
39
|
+
"react-native-nitro-modules": "^0.31.3",
|
|
40
40
|
"ts-morph": "^27.0.0",
|
|
41
41
|
"yargs": "^18.0.0",
|
|
42
42
|
"zod": "^4.0.5"
|
|
@@ -19,6 +19,7 @@ import { createType } from './syntax/createType.js'
|
|
|
19
19
|
import { Parameter } from './syntax/Parameter.js'
|
|
20
20
|
import { getBaseTypes, getHybridObjectNitroModuleConfig } from './utils.js'
|
|
21
21
|
import { NitroConfig } from './config/NitroConfig.js'
|
|
22
|
+
import { isMemberOverridingFromBase } from './syntax/isMemberOverridingFromBase.js'
|
|
22
23
|
|
|
23
24
|
export function generatePlatformFiles(
|
|
24
25
|
interfaceType: Type,
|
|
@@ -151,6 +152,21 @@ function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec {
|
|
|
151
152
|
isHybridView: isHybridView(type),
|
|
152
153
|
config: config,
|
|
153
154
|
}
|
|
155
|
+
|
|
156
|
+
for (const member of [...properties, ...methods]) {
|
|
157
|
+
const isOverridingBaseMember = isMemberOverridingFromBase(
|
|
158
|
+
member.name,
|
|
159
|
+
spec,
|
|
160
|
+
language
|
|
161
|
+
)
|
|
162
|
+
if (isOverridingBaseMember) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`\`${name}.${member.name}\` is overriding a member of one of it's base classes. ` +
|
|
165
|
+
`This is unsupported, override on the native side instead!`
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
154
170
|
return spec
|
|
155
171
|
}
|
|
156
172
|
|
package/src/getPlatformSpecs.ts
CHANGED
|
@@ -54,30 +54,27 @@ function getPlatformSpec(typeName: string, platformSpecs: Type): PlatformSpec {
|
|
|
54
54
|
// Property name (ios, android)
|
|
55
55
|
const platform = property.getName()
|
|
56
56
|
if (!isValidPlatform(platform)) {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
throw new Error(
|
|
58
|
+
`${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
|
|
59
59
|
`Valid platforms are: [${allPlatforms.join(', ')}]`
|
|
60
60
|
)
|
|
61
|
-
continue
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
// Value (swift, kotlin, c++)
|
|
65
64
|
const language = getLiteralValue(property)
|
|
66
65
|
if (!isValidLanguage(language)) {
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${typeName}: Language ${language} is not a valid language for ${platform}! ` +
|
|
69
68
|
`Valid languages are: [${platformLanguages[platform].join(', ')}]`
|
|
70
69
|
)
|
|
71
|
-
continue
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
// Double-check that language works on this platform (android: kotlin/c++, ios: swift/c++)
|
|
75
73
|
if (!isValidLanguageForPlatform(language, platform)) {
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${typeName}: Language ${language} is not a valid language for ${platform}! ` +
|
|
78
76
|
`Valid languages are: [${platformLanguages[platform].join(', ')}]`
|
|
79
77
|
)
|
|
80
|
-
continue
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
// @ts-expect-error because TypeScript isn't smart enough yet to correctly cast after the `isValidLanguageForPlatform` check.
|
|
@@ -115,6 +112,10 @@ export function isDirectlyHybridObject(type: Type): boolean {
|
|
|
115
112
|
return isDirectlyType(type, 'HybridObject')
|
|
116
113
|
}
|
|
117
114
|
|
|
115
|
+
export function isDirectlyAnyHybridObject(type: Type): boolean {
|
|
116
|
+
return isDirectlyType(type, 'AnyHybridObject')
|
|
117
|
+
}
|
|
118
|
+
|
|
118
119
|
export function extendsHybridObject(type: Type, recursive: boolean): boolean {
|
|
119
120
|
return extendsType(type, 'HybridObject', recursive)
|
|
120
121
|
}
|
|
@@ -154,7 +155,7 @@ export function isAnyHybridSubclass(type: Type): boolean {
|
|
|
154
155
|
*/
|
|
155
156
|
export function getHybridObjectPlatforms(
|
|
156
157
|
declaration: InterfaceDeclaration | TypeAliasDeclaration
|
|
157
|
-
): PlatformSpec
|
|
158
|
+
): PlatformSpec {
|
|
158
159
|
const base = getBaseTypes(declaration.getType()).find((t) =>
|
|
159
160
|
isDirectlyHybridObject(t)
|
|
160
161
|
)
|
|
@@ -167,9 +168,15 @@ export function getHybridObjectPlatforms(
|
|
|
167
168
|
|
|
168
169
|
const genericArguments = base.getTypeArguments()
|
|
169
170
|
const platformSpecsArgument = genericArguments[0]
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
if (
|
|
172
|
+
platformSpecsArgument == null ||
|
|
173
|
+
platformSpecsArgument.getProperties().length === 0
|
|
174
|
+
) {
|
|
175
|
+
// it uses `HybridObject` without generic arguments. We throw as we don't know what to generate.
|
|
176
|
+
throw new Error(
|
|
177
|
+
`HybridObject ${declaration.getName()} does not declare any platforms in the \`HybridObject\` type argument! ` +
|
|
178
|
+
`Pass at least one platform (and language) to \`interface ${declaration.getName()} extends HybridObject<{ ... }>\``
|
|
179
|
+
)
|
|
173
180
|
}
|
|
174
181
|
|
|
175
182
|
return getPlatformSpec(declaration.getName(), platformSpecsArgument)
|
|
@@ -177,7 +184,7 @@ export function getHybridObjectPlatforms(
|
|
|
177
184
|
|
|
178
185
|
export function getHybridViewPlatforms(
|
|
179
186
|
view: InterfaceDeclaration | TypeAliasDeclaration
|
|
180
|
-
): PlatformSpec
|
|
187
|
+
): PlatformSpec {
|
|
181
188
|
if (Node.isTypeAliasDeclaration(view)) {
|
|
182
189
|
const hybridViewTypeNode = view.getTypeNode()
|
|
183
190
|
|
|
@@ -186,7 +193,9 @@ export function getHybridViewPlatforms(
|
|
|
186
193
|
hybridViewTypeNode.getTypeName().getText() === 'HybridView'
|
|
187
194
|
|
|
188
195
|
if (!isHybridViewType) {
|
|
189
|
-
|
|
196
|
+
throw new Error(
|
|
197
|
+
`${view.getName()} looks like a HybridView, but doesn't seem to alias HybridView<...>!`
|
|
198
|
+
)
|
|
190
199
|
}
|
|
191
200
|
|
|
192
201
|
const genericArguments = hybridViewTypeNode.getTypeArguments()
|
package/src/nitrogen.ts
CHANGED
|
@@ -108,31 +108,20 @@ export async function runNitrogen({
|
|
|
108
108
|
let platformSpec: PlatformSpec
|
|
109
109
|
if (isHybridView(declaration.getType())) {
|
|
110
110
|
// Hybrid View Props
|
|
111
|
-
|
|
112
|
-
if (targetPlatforms == null) {
|
|
113
|
-
// It does not extend HybridView, continue..
|
|
114
|
-
continue
|
|
115
|
-
}
|
|
116
|
-
platformSpec = targetPlatforms
|
|
111
|
+
platformSpec = getHybridViewPlatforms(declaration)
|
|
117
112
|
} else if (extendsHybridObject(declaration.getType(), true)) {
|
|
118
113
|
// Hybrid View
|
|
119
|
-
|
|
120
|
-
if (targetPlatforms == null) {
|
|
121
|
-
// It does not extend HybridObject, continue..
|
|
122
|
-
continue
|
|
123
|
-
}
|
|
124
|
-
platformSpec = targetPlatforms
|
|
114
|
+
platformSpec = getHybridObjectPlatforms(declaration)
|
|
125
115
|
} else {
|
|
126
116
|
continue
|
|
127
117
|
}
|
|
128
118
|
|
|
129
119
|
const platforms = Object.keys(platformSpec) as Platform[]
|
|
130
|
-
|
|
131
120
|
if (platforms.length === 0) {
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
throw new Error(
|
|
122
|
+
`${typeName} does not declare any platforms in HybridObject<T> - nothing can be generated. ` +
|
|
123
|
+
`For example, to generate a C++ HybridObject, use \`interface ${typeName} extends HybridObject<{ ios: 'c++', android: 'c++' }> { ... }\``
|
|
134
124
|
)
|
|
135
|
-
continue
|
|
136
125
|
}
|
|
137
126
|
|
|
138
127
|
targetSpecs++
|
package/src/syntax/Method.ts
CHANGED
|
@@ -50,6 +50,12 @@ export class Method implements CodeNode {
|
|
|
50
50
|
`Method names are not allowed to start with two underscores (__)! (In ${this.jsSignature})`
|
|
51
51
|
)
|
|
52
52
|
}
|
|
53
|
+
if (this.name === 'dispose') {
|
|
54
|
+
// .dispose() does some special JSI magic that we loose if the user overrides it in his spec.
|
|
55
|
+
throw new Error(
|
|
56
|
+
`dispose() must not be overridden from TypeScript! You can override dispose() natively.`
|
|
57
|
+
)
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
get jsSignature(): string {
|
|
@@ -29,6 +29,11 @@ export function createCppHybridObject(spec: HybridObjectSpec): SourceFile[] {
|
|
|
29
29
|
const cxxNamespace = spec.config.getCxxNamespace('c++')
|
|
30
30
|
const name = getHybridObjectName(spec.name)
|
|
31
31
|
|
|
32
|
+
const properties = spec.properties.map((p) =>
|
|
33
|
+
p.getCode('c++', { virtual: true })
|
|
34
|
+
)
|
|
35
|
+
const methods = spec.methods.map((m) => m.getCode('c++', { virtual: true }))
|
|
36
|
+
|
|
32
37
|
const bases = ['public virtual HybridObject']
|
|
33
38
|
for (const base of spec.baseTypes) {
|
|
34
39
|
const baseName = getHybridObjectName(base.name).HybridTSpec
|
|
@@ -77,11 +82,11 @@ namespace ${cxxNamespace} {
|
|
|
77
82
|
|
|
78
83
|
public:
|
|
79
84
|
// Properties
|
|
80
|
-
${indent(
|
|
85
|
+
${indent(properties.join('\n'), ' ')}
|
|
81
86
|
|
|
82
87
|
public:
|
|
83
88
|
// Methods
|
|
84
|
-
${indent(
|
|
89
|
+
${indent(methods.join('\n'), ' ')}
|
|
85
90
|
|
|
86
91
|
protected:
|
|
87
92
|
// Hybrid Setup
|
package/src/syntax/createType.ts
CHANGED
|
@@ -22,11 +22,11 @@ import { MapType } from './types/MapType.js'
|
|
|
22
22
|
import { TupleType } from './types/TupleType.js'
|
|
23
23
|
import {
|
|
24
24
|
isAnyHybridSubclass,
|
|
25
|
-
|
|
25
|
+
isDirectlyAnyHybridObject,
|
|
26
26
|
isHybridView,
|
|
27
27
|
type Language,
|
|
28
28
|
} from '../getPlatformSpecs.js'
|
|
29
|
-
import {
|
|
29
|
+
import { AnyHybridObjectType } from './types/AnyHybridObjectType.js'
|
|
30
30
|
import { ErrorType } from './types/ErrorType.js'
|
|
31
31
|
import { getBaseTypes, getHybridObjectNitroModuleConfig } from '../utils.js'
|
|
32
32
|
import { DateType } from './types/DateType.js'
|
|
@@ -314,6 +314,9 @@ export function createType(
|
|
|
314
314
|
const name = type.getAliasSymbol()?.getName()
|
|
315
315
|
return new VariantType(variants, name)
|
|
316
316
|
}
|
|
317
|
+
} else if (isDirectlyAnyHybridObject(type)) {
|
|
318
|
+
// It is a HybridObject directly/literally. Base type
|
|
319
|
+
return new AnyHybridObjectType()
|
|
317
320
|
} else if (isAnyHybridSubclass(type)) {
|
|
318
321
|
// It is another HybridObject being referenced!
|
|
319
322
|
const typename = getHybridObjectName(type)
|
|
@@ -324,9 +327,6 @@ export function createType(
|
|
|
324
327
|
const sourceConfig =
|
|
325
328
|
getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current
|
|
326
329
|
return new HybridObjectType(typename, language, baseHybrids, sourceConfig)
|
|
327
|
-
} else if (isDirectlyHybridObject(type)) {
|
|
328
|
-
// It is a HybridObject directly/literally. Base type
|
|
329
|
-
return new HybridObjectBaseType()
|
|
330
330
|
} else if (type.isInterface()) {
|
|
331
331
|
// It is an `interface T { ... }`, which is a `struct`
|
|
332
332
|
const typename = type.getSymbolOrThrow().getName()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { HybridObject } from 'react-native-nitro-modules'
|
|
2
|
+
import type { Language } from '../getPlatformSpecs.js'
|
|
3
|
+
import type { HybridObjectSpec } from './HybridObjectSpec.js'
|
|
4
|
+
|
|
5
|
+
function getMemberNamesOfBaseType(language: Language): string[] {
|
|
6
|
+
switch (language) {
|
|
7
|
+
case 'c++':
|
|
8
|
+
// C++ classes don't have any base type.
|
|
9
|
+
return []
|
|
10
|
+
case 'swift':
|
|
11
|
+
// Swift classes conform to `AnyObject`, but that doesn't have any properties
|
|
12
|
+
return []
|
|
13
|
+
case 'kotlin':
|
|
14
|
+
// Kotlin/JVM classes always extends `Any`, which has 3 methods
|
|
15
|
+
return ['toString', 'equals', 'hashCode']
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getMemberNamesOfHybridObject(): string[] {
|
|
19
|
+
type MemberName = keyof HybridObject<{}>
|
|
20
|
+
type HasToContainAllKeys = { [P in MemberName]: boolean }
|
|
21
|
+
const allKeys: HasToContainAllKeys = {
|
|
22
|
+
__type: true,
|
|
23
|
+
dispose: true,
|
|
24
|
+
equals: true,
|
|
25
|
+
name: true,
|
|
26
|
+
toString: true,
|
|
27
|
+
}
|
|
28
|
+
return Object.keys(allKeys)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function flatBaseTypes(type: HybridObjectSpec): HybridObjectSpec[] {
|
|
32
|
+
return type.baseTypes.flatMap((b) => [b, ...flatBaseTypes(b)])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns true when the given {@linkcode memberName} is overriding a
|
|
37
|
+
* property or method from any base class inside the given
|
|
38
|
+
* {@linkcode hybridObject}'s prototype chain (all the way up).
|
|
39
|
+
*
|
|
40
|
+
* For example, `"toString"` would return `true` since it overrides from base HybridObject.
|
|
41
|
+
* On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
|
|
42
|
+
*/
|
|
43
|
+
export function isMemberOverridingFromBase(
|
|
44
|
+
memberName: string,
|
|
45
|
+
hybridObject: HybridObjectSpec,
|
|
46
|
+
language: Language
|
|
47
|
+
): boolean {
|
|
48
|
+
// 1. Check if the HybridObject inherits from other HybridObjects,
|
|
49
|
+
// if yes, check if those have properties of that given name.
|
|
50
|
+
const allBases = flatBaseTypes(hybridObject)
|
|
51
|
+
const anyBaseOverrides = allBases.some((h) => {
|
|
52
|
+
if (h.properties.some((p) => p.name === memberName)) {
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
if (h.methods.some((m) => m.name === memberName)) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
return false
|
|
59
|
+
})
|
|
60
|
+
if (anyBaseOverrides) {
|
|
61
|
+
// A HybridObject base type has the same property name - we need to override it.
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Check if the base `HybridObject` type contains a property of the given name
|
|
66
|
+
const baseHybridObjectProps = getMemberNamesOfHybridObject()
|
|
67
|
+
if (baseHybridObjectProps.includes(memberName)) {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 3. Check if the base type in our language contains a property of the given name
|
|
72
|
+
const baseTypeProps = getMemberNamesOfBaseType(language)
|
|
73
|
+
if (baseTypeProps.includes(memberName)) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Apparently no base type has a property of that name - we are safe!
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
@@ -113,6 +113,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
|
|
|
113
113
|
public:
|
|
114
114
|
size_t getExternalMemorySize() noexcept override;
|
|
115
115
|
void dispose() noexcept override;
|
|
116
|
+
std::string toString() override;
|
|
116
117
|
|
|
117
118
|
public:
|
|
118
119
|
inline const jni::global_ref<${name.JHybridTSpec}::javaobject>& getJavaPart() const noexcept {
|
|
@@ -198,6 +199,12 @@ namespace ${cxxNamespace} {
|
|
|
198
199
|
method(_javaPart);
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
std::string ${name.JHybridTSpec}::toString() {
|
|
203
|
+
static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
|
|
204
|
+
auto javaString = method(_javaPart);
|
|
205
|
+
return javaString->toStdString();
|
|
206
|
+
}
|
|
207
|
+
|
|
201
208
|
// Properties
|
|
202
209
|
${indent(propertiesImpl, ' ')}
|
|
203
210
|
|
|
@@ -94,6 +94,11 @@ abstract class ${name.HybridTSpec}: ${kotlinBase}() {
|
|
|
94
94
|
super.updateNative(hybridData)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// Default implementation of \`HybridObject.toString()\`
|
|
98
|
+
override fun toString(): String {
|
|
99
|
+
return "[HybridObject ${name.T}]"
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
// Properties
|
|
98
103
|
${indent(properties, ' ')}
|
|
99
104
|
|
|
@@ -199,7 +204,9 @@ set(value) {
|
|
|
199
204
|
`.trim()
|
|
200
205
|
)
|
|
201
206
|
}
|
|
202
|
-
const code = property.getCode('kotlin', {
|
|
207
|
+
const code = property.getCode('kotlin', {
|
|
208
|
+
virtual: true,
|
|
209
|
+
})
|
|
203
210
|
return `
|
|
204
211
|
${code}
|
|
205
212
|
|
|
@@ -207,7 +214,10 @@ private ${keyword} ${property.name}_cxx: ${bridged.getTypeCode('kotlin')}
|
|
|
207
214
|
${indent(lines.join('\n'), ' ')}
|
|
208
215
|
`.trim()
|
|
209
216
|
} else {
|
|
210
|
-
const code = property.getCode('kotlin', {
|
|
217
|
+
const code = property.getCode('kotlin', {
|
|
218
|
+
doNotStrip: true,
|
|
219
|
+
virtual: true,
|
|
220
|
+
})
|
|
211
221
|
return code
|
|
212
222
|
}
|
|
213
223
|
}
|
|
@@ -7,29 +7,44 @@ import type { SourceFile } from '../SourceFile.js'
|
|
|
7
7
|
import type { StructType } from '../types/StructType.js'
|
|
8
8
|
import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js'
|
|
9
9
|
|
|
10
|
+
interface BridgedProperty {
|
|
11
|
+
name: string
|
|
12
|
+
type: KotlinCxxBridgedType
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export function createKotlinStruct(structType: StructType): SourceFile[] {
|
|
11
16
|
const packageName = NitroConfig.current.getAndroidPackage('java/kotlin')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
|
|
18
|
+
const bridgedProperties = structType.properties.map<BridgedProperty>((p) => ({
|
|
19
|
+
name: p.escapedName,
|
|
20
|
+
type: new KotlinCxxBridgedType(p),
|
|
21
|
+
}))
|
|
22
|
+
// const parameters = structType.properties
|
|
23
|
+
// .map((p) =>
|
|
24
|
+
// `
|
|
25
|
+
// @DoNotStrip
|
|
26
|
+
// @Keep
|
|
27
|
+
// val ${p.escapedName}: ${p.getCode('kotlin')}
|
|
28
|
+
// `.trim()
|
|
29
|
+
// )
|
|
30
|
+
// .join(',\n')
|
|
31
|
+
const properties = bridgedProperties
|
|
32
|
+
.map(({ name, type }) => {
|
|
33
|
+
return `
|
|
15
34
|
@DoNotStrip
|
|
16
35
|
@Keep
|
|
17
|
-
val ${
|
|
36
|
+
val ${name}: ${type.getTypeCode('kotlin', false)}
|
|
18
37
|
`.trim()
|
|
19
|
-
)
|
|
38
|
+
})
|
|
20
39
|
.join(',\n')
|
|
21
40
|
|
|
22
|
-
const cxxCreateFunctionParameters =
|
|
23
|
-
.map((
|
|
24
|
-
|
|
25
|
-
return `${p.escapedName}: ${bridged.getTypeCode('kotlin', false)}`
|
|
41
|
+
const cxxCreateFunctionParameters = bridgedProperties
|
|
42
|
+
.map(({ name, type }) => {
|
|
43
|
+
return `${name}: ${type.getTypeCode('kotlin', false)}`
|
|
26
44
|
})
|
|
27
45
|
.join(', ')
|
|
28
|
-
const cxxCreateFunctionForward =
|
|
29
|
-
.map((p) =>
|
|
30
|
-
const bridged = new KotlinCxxBridgedType(p)
|
|
31
|
-
return bridged.parseFromCppToKotlin(p.escapedName, 'kotlin', false)
|
|
32
|
-
})
|
|
46
|
+
const cxxCreateFunctionForward = bridgedProperties
|
|
47
|
+
.map((p) => p.name)
|
|
33
48
|
.join(', ')
|
|
34
49
|
|
|
35
50
|
const extraImports = structType.properties
|
|
@@ -37,6 +52,8 @@ val ${p.escapedName}: ${p.getCode('kotlin')}
|
|
|
37
52
|
.map((i) => `import ${i.name}`)
|
|
38
53
|
.filter(isNotDuplicate)
|
|
39
54
|
|
|
55
|
+
const secondaryConstructor = createKotlinConstructor(structType)
|
|
56
|
+
|
|
40
57
|
const code = `
|
|
41
58
|
${createFileMetadataString(`${structType.structName}.kt`)}
|
|
42
59
|
|
|
@@ -52,8 +69,10 @@ ${extraImports.join('\n')}
|
|
|
52
69
|
@DoNotStrip
|
|
53
70
|
@Keep
|
|
54
71
|
data class ${structType.structName}(
|
|
55
|
-
${indent(
|
|
72
|
+
${indent(properties, ' ')}
|
|
56
73
|
) {
|
|
74
|
+
${indent(secondaryConstructor, ' ')}
|
|
75
|
+
|
|
57
76
|
private companion object {
|
|
58
77
|
/**
|
|
59
78
|
* Constructor called from C++
|
|
@@ -150,6 +169,33 @@ namespace ${cxxNamespace} {
|
|
|
150
169
|
return files
|
|
151
170
|
}
|
|
152
171
|
|
|
172
|
+
function createKotlinConstructor(structType: StructType): string {
|
|
173
|
+
const bridgedProperties = structType.properties.map<BridgedProperty>((p) => ({
|
|
174
|
+
name: p.escapedName,
|
|
175
|
+
type: new KotlinCxxBridgedType(p),
|
|
176
|
+
}))
|
|
177
|
+
const needsSpecialHandling = bridgedProperties.some(
|
|
178
|
+
({ type }) => type.needsSpecialHandling
|
|
179
|
+
)
|
|
180
|
+
if (needsSpecialHandling) {
|
|
181
|
+
const kotlinParams = structType.properties.map(
|
|
182
|
+
(p) => `${p.escapedName}: ${p.getCode('kotlin')}`
|
|
183
|
+
)
|
|
184
|
+
const paramsForward = bridgedProperties.map(({ name, type }) =>
|
|
185
|
+
type.parseFromKotlinToCpp(name, 'kotlin')
|
|
186
|
+
)
|
|
187
|
+
return `
|
|
188
|
+
/**
|
|
189
|
+
* Create a new instance of ${structType.structName} from Kotlin
|
|
190
|
+
*/
|
|
191
|
+
constructor(${kotlinParams.join(', ')}):
|
|
192
|
+
this(${paramsForward.join(', ')})
|
|
193
|
+
`.trim()
|
|
194
|
+
} else {
|
|
195
|
+
return `/* primary constructor */`
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
153
199
|
function createJNIStructInitializer(structType: StructType): string {
|
|
154
200
|
const lines: string[] = ['static const auto clazz = javaClassStatic();']
|
|
155
201
|
for (const prop of structType.properties) {
|
|
@@ -11,7 +11,7 @@ export function createSwiftHybridObject(spec: HybridObjectSpec): SourceFile[] {
|
|
|
11
11
|
const name = getHybridObjectName(spec.name)
|
|
12
12
|
const protocolName = name.HybridTSpec
|
|
13
13
|
const properties = spec.properties.map((p) => p.getCode('swift')).join('\n')
|
|
14
|
-
const methods = spec.methods.map((
|
|
14
|
+
const methods = spec.methods.map((m) => m.getCode('swift')).join('\n')
|
|
15
15
|
const extraImports = [
|
|
16
16
|
...spec.properties.flatMap((p) => p.getRequiredImports('swift')),
|
|
17
17
|
...spec.methods.flatMap((m) => m.getRequiredImports('swift')),
|
|
@@ -83,6 +83,13 @@ public protocol ${protocolName}_protocol: ${protocolBaseClasses.join(', ')} {
|
|
|
83
83
|
${indent(methods, ' ')}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
public extension ${protocolName}_protocol {
|
|
87
|
+
/// Default implementation of \`\`HybridObject.toString\`\`
|
|
88
|
+
func toString() -> String {
|
|
89
|
+
return "[HybridObject ${name.T}]"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
/// See \`\`${protocolName}\`\`
|
|
87
94
|
open class ${protocolName}_base${classBaseClasses.length > 0 ? `: ${classBaseClasses.join(',')}` : ''} {
|
|
88
95
|
${indent(baseMembers.join('\n'), ' ')}
|
|
@@ -191,7 +191,7 @@ ${hasBase ? `open class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `o
|
|
|
191
191
|
*/
|
|
192
192
|
public func getCxxPart() -> bridge.${bridge.specializationName} {
|
|
193
193
|
let cachedCxxPart = self.__cxxPart.lock()
|
|
194
|
-
if cachedCxxPart
|
|
194
|
+
if Bool(fromCxx: cachedCxxPart) {
|
|
195
195
|
return cachedCxxPart
|
|
196
196
|
} else {
|
|
197
197
|
let newCxxPart = bridge.${bridge.funcName}(self.toUnsafe())
|
|
@@ -220,6 +220,14 @@ ${hasBase ? `open class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `o
|
|
|
220
220
|
self.__implementation.dispose()
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Call toString() on the Swift class.
|
|
225
|
+
*/
|
|
226
|
+
@inline(__always)
|
|
227
|
+
public ${hasBase ? 'override func' : 'func'} toString() -> String {
|
|
228
|
+
return self.__implementation.toString()
|
|
229
|
+
}
|
|
230
|
+
|
|
223
231
|
// Properties
|
|
224
232
|
${indent(propertiesBridge.join('\n\n'), ' ')}
|
|
225
233
|
|
|
@@ -390,6 +398,9 @@ namespace ${cxxNamespace} {
|
|
|
390
398
|
void dispose() noexcept override {
|
|
391
399
|
_swiftPart.dispose();
|
|
392
400
|
}
|
|
401
|
+
std::string toString() override {
|
|
402
|
+
return _swiftPart.toString();
|
|
403
|
+
}
|
|
393
404
|
|
|
394
405
|
public:
|
|
395
406
|
// Properties
|
|
@@ -3,7 +3,7 @@ import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
|
|
|
3
3
|
import type { SourceFile, SourceImport } from '../SourceFile.js'
|
|
4
4
|
import type { Type, TypeKind } from './Type.js'
|
|
5
5
|
|
|
6
|
-
export class
|
|
6
|
+
export class AnyHybridObjectType implements Type {
|
|
7
7
|
constructor() {}
|
|
8
8
|
|
|
9
9
|
get canBePassedByReference(): boolean {
|
|
@@ -21,7 +21,7 @@ export class HybridObjectBaseType implements Type {
|
|
|
21
21
|
return `std::shared_ptr<HybridObject>`
|
|
22
22
|
default:
|
|
23
23
|
throw new Error(
|
|
24
|
-
|
|
24
|
+
`\`AnyHybridObject\` cannot be used directly in ${language} yet. Use a specific derived class of \`HybridObject\` instead!`
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -50,13 +50,6 @@ export class HybridObjectBaseType implements Type {
|
|
|
50
50
|
}
|
|
51
51
|
)
|
|
52
52
|
break
|
|
53
|
-
case 'kotlin':
|
|
54
|
-
imports.push({
|
|
55
|
-
name: 'com.margelo.nitro.core.HybridObject',
|
|
56
|
-
space: 'system',
|
|
57
|
-
language: 'kotlin',
|
|
58
|
-
})
|
|
59
|
-
break
|
|
60
53
|
}
|
|
61
54
|
return imports
|
|
62
55
|
}
|