@rejaul/react-native-rust 1.0.1 → 1.1.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.
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+ var import_node_fs = __toESM(require("node:fs"));
26
+ var import_node_path = __toESM(require("node:path"));
27
+ var import_node_child_process = require("node:child_process");
28
+ var import_react_native_rust_lib = require("./react-native-rust-lib");
29
+ var import_app = require("./app");
30
+ function printHelp() {
31
+ console.log(`react-native-rust - Rust helper for React Native C++ TurboModule libraries
32
+
33
+ Usage:
34
+ react-native-rust create <name> Create a React Native library and initialize Rust support
35
+ react-native-rust init Add Rust support and generate functions from the Spec interface
36
+ react-native-rust init --app Add app-local Rust support to a React Native app
37
+ react-native-rust generate Regenerate Rust, C++, and TypeScript glue from Spec
38
+ react-native-rust doctor [target] Check Rust and optional ios/android/web prerequisites
39
+ react-native-rust build ios Build an iOS XCFramework
40
+ react-native-rust build android Build Android static archives for supported ABIs
41
+ react-native-rust build web Build a WebAssembly package for react-native-web
42
+ react-native-rust --help Show this help
43
+
44
+ Create a library first with the C++ module template from:
45
+ npx create-react-native-library@latest`);
46
+ }
47
+ function createProject(projectName) {
48
+ if (!projectName || projectName.startsWith("-") || import_node_path.default.basename(projectName) !== projectName) {
49
+ throw new Error("Usage: react-native-rust create <library-name>");
50
+ }
51
+ const projectPath = import_node_path.default.resolve(process.cwd(), projectName);
52
+ if (import_node_fs.default.existsSync(projectPath)) {
53
+ throw new Error(`The target directory already exists: ${projectPath}`);
54
+ }
55
+ console.log("In the scaffolder prompts, select a TurboModule library using C++.");
56
+ const scaffold = (0, import_node_child_process.spawnSync)("npx", ["--yes", "create-react-native-library@latest", projectName], {
57
+ cwd: process.cwd(),
58
+ stdio: "inherit",
59
+ encoding: "utf8"
60
+ });
61
+ if (scaffold.error) throw new Error(`React Native library creation failed: ${scaffold.error.message}`);
62
+ if (scaffold.status !== 0) throw new Error("React Native library creation failed.");
63
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(projectPath, "package.json"))) {
64
+ throw new Error(`The scaffolder did not create ${projectPath}.`);
65
+ }
66
+ const initialized = (0, import_node_child_process.spawnSync)(process.execPath, [__filename, "init"], {
67
+ cwd: projectPath,
68
+ stdio: "inherit",
69
+ encoding: "utf8"
70
+ });
71
+ if (initialized.error) {
72
+ throw new Error(`Rust initialization failed in ${projectPath}: ${initialized.error.message}`);
73
+ }
74
+ if (initialized.status !== 0) {
75
+ throw new Error(`Rust initialization failed in ${projectPath}.`);
76
+ }
77
+ console.log(`Created ${projectName}. Next run \`cd ${projectName} && yarn install\`.`);
78
+ }
79
+ function main(args) {
80
+ const [command, option] = args;
81
+ if (!command || command === "--help" || command === "-h") return printHelp();
82
+ if (command === "create") return createProject(option);
83
+ if (command === "init") {
84
+ if (option === "--app") return (0, import_app.initApp)();
85
+ if (option) throw new Error("Use `react-native-rust init --app` for an app, or run `react-native-rust init` from a C++ TurboModule library.");
86
+ return (0, import_react_native_rust_lib.init)();
87
+ }
88
+ const appRoot = process.cwd();
89
+ const appModule = (0, import_app.appModuleRoot)(appRoot);
90
+ if (command === "generate") {
91
+ if (!appModule) return (0, import_react_native_rust_lib.generate)();
92
+ return (0, import_app.inDirectory)(appModule, () => {
93
+ (0, import_react_native_rust_lib.generate)();
94
+ (0, import_app.runCodegen)(appRoot, appModule);
95
+ });
96
+ }
97
+ if (command === "doctor") {
98
+ if (option && option !== "ios" && option !== "android" && option !== "web") {
99
+ throw new Error("Choose a doctor target: ios, android, or web.");
100
+ }
101
+ const target = option;
102
+ return appModule ? (0, import_app.inDirectory)(appModule, () => (0, import_react_native_rust_lib.doctor)(target)) : (0, import_react_native_rust_lib.doctor)(target);
103
+ }
104
+ if (command === "build") {
105
+ if (!appModule) return (0, import_react_native_rust_lib.build)(option);
106
+ (0, import_app.runCodegen)(appRoot, appModule);
107
+ return (0, import_app.inDirectory)(appModule, () => (0, import_react_native_rust_lib.build)(option));
108
+ }
109
+ throw new Error(`Unknown command: ${command}. Run react-native-rust --help for usage.`);
110
+ }
111
+ try {
112
+ main(process.argv.slice(2));
113
+ } catch (error) {
114
+ console.error(`Error: ${error.message}`);
115
+ process.exitCode = 1;
116
+ }
package/dist/types.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var types_exports = {};
16
+ module.exports = __toCommonJS(types_exports);
package/package.json CHANGED
@@ -1,28 +1,45 @@
1
1
  {
2
2
  "name": "@rejaul/react-native-rust",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Generate Rust and native bindings from React Native TurboModule TypeScript interfaces.",
5
5
  "engines": {
6
6
  "node": ">=18"
7
7
  },
8
8
  "files": [
9
- "bin/",
9
+ "dist/",
10
10
  "README.md",
11
11
  "LICENSE",
12
12
  "CONTRIBUTING.md",
13
13
  "CODE_OF_CONDUCT.md"
14
14
  ],
15
15
  "bin": {
16
- "react-native-rust": "./bin/react-native-rust.js"
16
+ "react-native-rust": "./dist/react-native-rust.js"
17
17
  },
18
18
  "scripts": {
19
+ "build": "node scripts/build.js",
20
+ "typecheck": "tsc",
21
+ "lint": "eslint .",
22
+ "prepublishOnly": "npm run typecheck && npm run build",
23
+ "pretest": "npm run typecheck && npm run build",
19
24
  "test": "node --test"
20
25
  },
21
26
  "repository": {
22
27
  "type": "git",
23
28
  "url": "git+https://github.com/DeveloperRejaul/react-native-rust.git"
24
29
  },
25
- "keywords": [],
30
+ "keywords": [
31
+ "react-native",
32
+ "rust",
33
+ "typescript",
34
+ "turbomodule",
35
+ "codegen",
36
+ "cli",
37
+ "ios",
38
+ "android",
39
+ "jsi",
40
+ "cpp",
41
+ "ffi"
42
+ ],
26
43
  "author": "",
27
44
  "license": "ISC",
28
45
  "bugs": {
@@ -31,5 +48,12 @@
31
48
  "homepage": "https://github.com/DeveloperRejaul/react-native-rust#readme",
32
49
  "dependencies": {
33
50
  "typescript": "^5.9.3"
51
+ },
52
+ "devDependencies": {
53
+ "@eslint/js": "^10.0.1",
54
+ "@types/node": "^20.19.43",
55
+ "esbuild": "^0.28.2",
56
+ "eslint": "^10.11.0",
57
+ "typescript-eslint": "^8.71.0"
34
58
  }
35
59
  }
@@ -1,39 +0,0 @@
1
- const ts = require('typescript');
2
-
3
- const HEADER_START = '// react-native-rust:generated-methods:start';
4
- const HEADER_END = '// react-native-rust:generated-methods:end';
5
- const SOURCE_START = '// react-native-rust:generated-methods:start';
6
- const SOURCE_END = '// react-native-rust:generated-methods:end';
7
-
8
- const rustKeywords = new Set([
9
- 'as', 'async', 'await', 'break', 'const', 'continue', 'crate', 'dyn', 'else', 'enum', 'extern', 'false',
10
- 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return',
11
- 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while',
12
- ]);
13
-
14
- const cppKeywords = new Set([
15
- 'alignas', 'alignof', 'and', 'asm', 'auto', 'bool', 'break', 'case', 'catch', 'char', 'class', 'const',
16
- 'continue', 'default', 'delete', 'do', 'double', 'else', 'enum', 'explicit', 'export', 'extern', 'false',
17
- 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'namespace', 'new', 'noexcept', 'not',
18
- 'nullptr', 'operator', 'or', 'private', 'protected', 'public', 'register', 'return', 'short', 'signed',
19
- 'sizeof', 'static', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typename',
20
- 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'while', 'xor',
21
- ]);
22
-
23
- const supportedTypes = new Map([
24
- [ts.SyntaxKind.NumberKeyword, { kind: 'number', typescript: 'number', rust: 'f64', cpp: 'double', defaultValue: '0.0' }],
25
- [ts.SyntaxKind.BooleanKeyword, { kind: 'boolean', typescript: 'boolean', rust: 'bool', cpp: 'bool', defaultValue: 'false' }],
26
- [ts.SyntaxKind.StringKeyword, { kind: 'json', typescript: 'string', rust: 'String', cpp: 'jsi::String', defaultValue: 'String::new()' }],
27
- [ts.SyntaxKind.VoidKeyword, { kind: 'void', typescript: 'void', rust: '()', cpp: 'void', defaultValue: null }],
28
- ]);
29
-
30
- module.exports = {
31
- HEADER_START,
32
- HEADER_END,
33
- SOURCE_START,
34
- SOURCE_END,
35
- rustKeywords,
36
- cppKeywords,
37
- supportedTypes,
38
- ts,
39
- };
@@ -1,9 +0,0 @@
1
- const { parseSpec } = require('./spec');
2
- const { renderRustFfiModule } = require('./renderers');
3
- const { renderProjectBindings } = require('./project');
4
-
5
- module.exports = {
6
- parseSpec,
7
- renderRustFfiModule,
8
- renderProjectBindings,
9
- };
@@ -1,168 +0,0 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const {
4
- HEADER_START,
5
- HEADER_END,
6
- SOURCE_START,
7
- SOURCE_END,
8
- } = require('./constants');
9
- const { renderCppMethods, renderRustExports, renderRustHandler, renderRustModuleList, renderWrappers } = require('./renderers');
10
-
11
- function replaceMarkedRegion(source, startMarker, endMarker, generated) {
12
- const start = source.indexOf(startMarker);
13
- const end = source.indexOf(endMarker);
14
- if (start < 0 || end < start) return null;
15
- const block = `${startMarker}\n${generated}\n${endMarker}`;
16
- return `${source.slice(0, start)}${block}${source.slice(end + endMarker.length)}`;
17
- }
18
-
19
- function replaceTemplateMethod(source, markerStart, markerEnd, generated, methodNeedle) {
20
- const functionIndex = source.indexOf(methodNeedle);
21
- if (functionIndex < 0) throw new Error(`Could not locate C++ template method "${methodNeedle}".`);
22
- const lineStart = source.lastIndexOf('\n', functionIndex) + 1;
23
- const bodyStart = source.indexOf('{', functionIndex);
24
- if (bodyStart < 0) throw new Error(`Could not locate the body for C++ method "${methodNeedle}".`);
25
- let depth = 0;
26
- let bodyEnd = -1;
27
- for (let index = bodyStart; index < source.length; index += 1) {
28
- if (source[index] === '{') depth += 1;
29
- if (source[index] === '}') {
30
- depth -= 1;
31
- if (depth === 0) {
32
- bodyEnd = index + 1;
33
- break;
34
- }
35
- }
36
- }
37
- if (bodyEnd < 0) throw new Error(`Could not find the end of C++ method "${methodNeedle}".`);
38
- const block = `${markerStart}\n${generated}\n${markerEnd}`;
39
- return `${source.slice(0, lineStart)}${block}${source.slice(bodyEnd)}`;
40
- }
41
-
42
- function hasDefaultMultiplyBody(source, moduleName) {
43
- const methodIndex = source.indexOf(`${moduleName}Impl::multiply(`);
44
- if (methodIndex < 0) return false;
45
- const bodyStart = source.indexOf('{', methodIndex);
46
- if (bodyStart < 0) return false;
47
- let depth = 0;
48
- for (let index = bodyStart; index < source.length; index += 1) {
49
- if (source[index] === '{') depth += 1;
50
- if (source[index] === '}') {
51
- depth -= 1;
52
- if (depth === 0) {
53
- return source.slice(bodyStart + 1, index).replace(/\s+/g, '') === 'returna*b;';
54
- }
55
- }
56
- }
57
- return false;
58
- }
59
-
60
- function updateCppFile(source, markers, generated, templateMethod, methodNeedle, includeRustHeader = false) {
61
- let updated = source;
62
- if (includeRustHeader && !updated.includes('../rust/include/rust_api.h')) {
63
- if (!updated.includes('#pragma once')) throw new Error('Unsupported C++ module header template.');
64
- updated = updated.replace('#pragma once', '#pragma once\n\n#include "../rust/include/rust_api.h"');
65
- }
66
- const marked = replaceMarkedRegion(updated, markers[0], markers[1], generated);
67
- if (marked !== null) return marked;
68
- if (updated.includes(markers[0]) || updated.includes(markers[1])) {
69
- throw new Error('Incomplete react-native-rust generated markers found in C++ source.');
70
- }
71
- if (!templateMethod) throw new Error('C++ generated markers are missing; refusing to overwrite the module.');
72
- if (methodNeedle.endsWith(';')) {
73
- const count = updated.split(methodNeedle).length - 1;
74
- if (count !== 1) throw new Error('Could not uniquely locate the C++ template method declaration.');
75
- return updated.replace(methodNeedle, `${markers[0]}\n${generated}\n${markers[1]}`);
76
- }
77
- return replaceTemplateMethod(updated, markers[0], markers[1], generated, methodNeedle);
78
- }
79
-
80
- function normalizeSignature(text) {
81
- return text.replace(/\s+/g, '');
82
- }
83
-
84
- function validateExistingHandler(filePath, method) {
85
- const contents = fs.readFileSync(filePath, 'utf8');
86
- const signaturePattern = new RegExp(`pub\\s+fn\\s+${method.rustName}\\s*\\(([^)]*)\\)\\s*->\\s*([^\\{]+)\\{`);
87
- const match = contents.match(signaturePattern);
88
- const args = method.params.map((param) => {
89
- if (param.kind !== 'callback') return `${param.name}: ${param.rust}`;
90
- const callbackTypes = param.params.map((callbackParam) => callbackParam.rust).join(', ');
91
- return `${param.name}: &mut dyn FnMut(${callbackTypes})`;
92
- }).join(', ');
93
- const returnType = method.returnType.promise
94
- ? `Result<${method.returnType.rust}, String>`
95
- : method.returnType.rust;
96
- const expected = `${method.rustName}(${args}) -> ${returnType}`;
97
- const expectedSignature = `pub fn ${expected} {`;
98
- if (normalizeSignature(contents).includes(normalizeSignature(expectedSignature))) return;
99
- if (!match || normalizeSignature(match[0].replace(/^pub\s+fn\s+/, '').replace(/\{\s*$/, '')) !== normalizeSignature(expected)) {
100
- throw new Error(`Rust handler ${path.relative(process.cwd(), filePath)} does not match the TypeScript Spec. Update its signature before regenerating.`);
101
- }
102
- }
103
-
104
- function renderProjectBindings(root, moduleName, methods, initialize) {
105
- const headerPath = path.join(root, 'cpp', `${moduleName}Impl.h`);
106
- const sourcePath = path.join(root, 'cpp', `${moduleName}Impl.cpp`);
107
- const header = fs.readFileSync(headerPath, 'utf8');
108
- const source = fs.readFileSync(sourcePath, 'utf8');
109
- if (initialize && !hasDefaultMultiplyBody(source, moduleName)) {
110
- throw new Error('The C++ multiply demo was customized; move its logic into Rust before running init. No files were changed.');
111
- }
112
- const cpp = renderCppMethods(methods, moduleName);
113
- const generatedIncludes = cpp.source.match(/^#include .+$/gm) || [];
114
- const cppSource = cpp.source.replace(/^#include .+\n/gm, '');
115
- const headerTemplateMethod = 'double multiply(jsi::Runtime& rt, double a, double b);';
116
- const sourceTemplateMethod = `${moduleName}Impl::multiply(`;
117
- const updatedHeader = updateCppFile(header, [HEADER_START, HEADER_END], cpp.header, initialize, headerTemplateMethod, true);
118
- let updatedSource = updateCppFile(source, [SOURCE_START, SOURCE_END], cppSource, initialize, sourceTemplateMethod);
119
- if (generatedIncludes.length > 0) {
120
- const namespaceIndex = updatedSource.indexOf('\nnamespace facebook::react {');
121
- if (namespaceIndex < 0) throw new Error('Could not locate the C++ namespace for generated includes.');
122
- const missingIncludes = generatedIncludes.filter((include) => !updatedSource.includes(include));
123
- if (missingIncludes.length > 0) {
124
- const insertionPoint = namespaceIndex + 1;
125
- updatedSource = `${updatedSource.slice(0, insertionPoint)}${missingIncludes.join('\n')}\n${updatedSource.slice(insertionPoint)}`;
126
- }
127
- }
128
-
129
- const rustDirectory = path.join(root, 'rust', 'src');
130
- const moduleList = renderRustModuleList(methods);
131
- const updates = new Map([
132
- [headerPath, updatedHeader],
133
- [sourcePath, updatedSource],
134
- [path.join(rustDirectory, 'lib.rs'), renderRustExports(methods)],
135
- [path.join(rustDirectory, 'api', 'mod.rs'), moduleList],
136
- ]);
137
- for (const method of methods) {
138
- const handlerPath = path.join(rustDirectory, 'api', `${method.rustName}.rs`);
139
- if (fs.existsSync(handlerPath)) validateExistingHandler(handlerPath, method);
140
- else updates.set(handlerPath, renderRustHandler(method));
141
- }
142
-
143
- const wrappers = renderWrappers(methods, moduleName);
144
- const generatedModulePath = path.join(root, 'src', 'rust-generated');
145
- updates.set(`${generatedModulePath}.tsx`, wrappers.fallback);
146
- updates.set(`${generatedModulePath}.native.tsx`, wrappers.native);
147
- const indexPath = path.join(root, 'src', 'index.tsx');
148
- let index = fs.readFileSync(indexPath, 'utf8');
149
- for (const method of methods) {
150
- const oldExport = new RegExp(`^export\\s*\\{\\s*${method.name}\\s*\\}\\s*from\\s*['"]\\./${method.name}['"];?\\s*$`, 'm');
151
- index = index.replace(oldExport, '');
152
- }
153
- if (!index.includes("export * from './rust-generated';")) {
154
- index = `${index.trimEnd()}\nexport * from './rust-generated';\n`;
155
- }
156
- updates.set(indexPath, index);
157
- return updates;
158
- }
159
-
160
- module.exports = {
161
- replaceMarkedRegion,
162
- replaceTemplateMethod,
163
- hasDefaultMultiplyBody,
164
- updateCppFile,
165
- normalizeSignature,
166
- validateExistingHandler,
167
- renderProjectBindings,
168
- };
@@ -1,156 +0,0 @@
1
- const { supportedTypes, rustKeywords, cppKeywords, ts } = require('./constants');
2
-
3
- function typeInfo(typeNode, sourceFile, methodName, role) {
4
- const info = supportedTypes.get(typeNode.kind);
5
- if (info && !(role === 'parameter' && typeNode.kind === ts.SyntaxKind.VoidKeyword)) return info;
6
-
7
- if (role === 'return' && ts.isTypeReferenceNode(typeNode) && typeNode.typeName.getText(sourceFile) === 'Promise') {
8
- if (typeNode.typeArguments?.length !== 1) throw new Error(`Promise return in ${methodName}() must declare one result type.`);
9
- const result = typeInfo(typeNode.typeArguments[0], sourceFile, methodName, 'promise result');
10
- if (result.kind === 'callback' || result.promise) throw new Error(`Nested Promise or callback results are not supported in ${methodName}().`);
11
- return { ...result, typescript: typeNode.getText(sourceFile), promise: true };
12
- }
13
-
14
- const typeName = ts.isTypeReferenceNode(typeNode) ? typeNode.typeName.getText(sourceFile) : '';
15
- const objectType = typeNode.kind === ts.SyntaxKind.ObjectKeyword
16
- || ['CodegenTypes.UnsafeObject', 'UnsafeObject'].includes(typeName);
17
- if (objectType) {
18
- return {
19
- kind: 'json',
20
- typescript: typeNode.getText(sourceFile),
21
- rust: 'serde_json::Value',
22
- cpp: 'jsi::Object',
23
- defaultValue: 'serde_json::json!({})',
24
- };
25
- }
26
-
27
- const arrayElement = ts.isArrayTypeNode(typeNode)
28
- ? typeNode.elementType
29
- : ts.isTypeReferenceNode(typeNode)
30
- && ['Array', 'ReadonlyArray'].includes(typeNode.typeName.getText(sourceFile))
31
- && typeNode.typeArguments?.length === 1
32
- ? typeNode.typeArguments[0]
33
- : null;
34
- if (arrayElement) {
35
- const elementType = typeInfo(arrayElement, sourceFile, methodName, 'array element');
36
- if (elementType.kind === 'callback' || elementType.promise || elementType.kind === 'void') {
37
- throw new Error(`Unsupported array element type in ${methodName}().`);
38
- }
39
- return {
40
- kind: 'json',
41
- typescript: typeNode.getText(sourceFile),
42
- rust: 'serde_json::Value',
43
- cpp: 'jsi::Array',
44
- defaultValue: 'serde_json::json!([])',
45
- };
46
- }
47
-
48
- const text = typeNode.getText(sourceFile);
49
- throw new Error(`Unsupported ${role} type "${text}" in ${methodName}(). Supported types are number, boolean, string, arrays, and CodegenTypes.UnsafeObject${role === 'return' ? ', plus void and Promise<T>' : ''}.`);
50
- }
51
-
52
- function callbackInfo(typeNode, sourceFile, methodName) {
53
- if (!ts.isFunctionTypeNode(typeNode) || typeNode.typeParameters?.length) {
54
- throw new Error(`Unsupported callback signature in ${methodName}().`);
55
- }
56
- if (typeNode.type.kind !== ts.SyntaxKind.VoidKeyword || typeNode.parameters.length > 4) {
57
- throw new Error(`Callbacks in ${methodName}() must return void and have at most four parameters.`);
58
- }
59
- const params = typeNode.parameters.map((parameter) => {
60
- if (!ts.isIdentifier(parameter.name) || parameter.questionToken || parameter.dotDotDotToken || !parameter.type) {
61
- throw new Error(`Callback parameters in ${methodName}() must be named, required, and explicitly typed.`);
62
- }
63
- const info = typeInfo(parameter.type, sourceFile, methodName, 'callback parameter');
64
- if (info.promise || info.kind === 'callback' || info.kind === 'void') {
65
- throw new Error(`Unsupported callback parameter type in ${methodName}().`);
66
- }
67
- return { name: parameter.name.text, ...info };
68
- });
69
- return { kind: 'callback', typescript: typeNode.getText(sourceFile), cpp: 'jsi::Function', params };
70
- }
71
-
72
- function toSnakeCase(name) {
73
- return name
74
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
75
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
76
- .toLowerCase();
77
- }
78
-
79
- function validateIdentifier(name, location) {
80
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) || rustKeywords.has(name) || cppKeywords.has(name)) {
81
- throw new Error(`Unsupported identifier "${name}" in ${location}; use a non-keyword identifier.`);
82
- }
83
- }
84
-
85
- function parseSpec(sourceText, fileName = 'NativeModule.ts') {
86
- const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
87
- if (sourceFile.parseDiagnostics.length > 0) {
88
- const diagnostic = sourceFile.parseDiagnostics[0];
89
- throw new Error(`Could not parse ${fileName}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`);
90
- }
91
-
92
- const spec = sourceFile.statements.find((statement) => (
93
- ts.isInterfaceDeclaration(statement) && statement.name.text === 'Spec'
94
- ));
95
- if (!spec) throw new Error(`No "Spec" interface was found in ${fileName}.`);
96
- if (spec.typeParameters?.length) throw new Error('Generic TurboModule Spec interfaces are not supported.');
97
- if (spec.members.length === 0) throw new Error('The Spec interface must declare at least one method.');
98
-
99
- const names = new Set();
100
- const rustNames = new Set();
101
- const methods = spec.members.map((member) => {
102
- if (!ts.isMethodSignature(member)) {
103
- throw new Error('Only method signatures are supported in the Spec interface.');
104
- }
105
- if (!member.name || !ts.isIdentifier(member.name) || member.questionToken || member.typeParameters?.length) {
106
- throw new Error('Optional, computed, and generic Spec methods are not supported.');
107
- }
108
-
109
- const name = member.name.text;
110
- validateIdentifier(name, 'Spec method');
111
- if (names.has(name)) throw new Error(`Overloaded method "${name}" is not supported.`);
112
- names.add(name);
113
- const rustName = toSnakeCase(name);
114
- validateIdentifier(rustName, `Rust mapping for ${name}()`);
115
- if (rustNames.has(rustName)) throw new Error(`Multiple Spec methods map to the Rust name "${rustName}".`);
116
- rustNames.add(rustName);
117
-
118
- const params = member.parameters.map((parameter) => {
119
- if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken || parameter.questionToken || parameter.initializer) {
120
- throw new Error(`Rest, optional, and destructured parameters are not supported in ${name}().`);
121
- }
122
- const paramName = parameter.name.text;
123
- validateIdentifier(paramName, `parameter in ${name}()`);
124
- if (!parameter.type) throw new Error(`Parameter "${paramName}" in ${name}() needs an explicit type.`);
125
- const info = ts.isFunctionTypeNode(parameter.type)
126
- ? callbackInfo(parameter.type, sourceFile, name)
127
- : typeInfo(parameter.type, sourceFile, name, 'parameter');
128
- return { name: paramName, ...info };
129
- });
130
- if (new Set(params.map((param) => param.name)).size !== params.length) {
131
- throw new Error(`Duplicate parameter names are not supported in ${name}().`);
132
- }
133
- if (!member.type) throw new Error(`Method "${name}()" needs an explicit return type.`);
134
- return {
135
- name,
136
- rustName,
137
- symbol: `rnrs_${rustName}`,
138
- params,
139
- returnType: typeInfo(member.type, sourceFile, name, 'return'),
140
- };
141
- });
142
- for (const method of methods) {
143
- if (method.returnType.promise && method.params.some((param) => param.kind === 'callback')) {
144
- throw new Error(`Callbacks in Promise method ${method.name}() are not supported. Use a Codegen event emitter for asynchronous callbacks.`);
145
- }
146
- }
147
- return methods;
148
- }
149
-
150
- module.exports = {
151
- typeInfo,
152
- callbackInfo,
153
- toSnakeCase,
154
- validateIdentifier,
155
- parseSpec,
156
- };
package/bin/codegen.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('./codegen/index');