Skip to content

Commit e970740

Browse files
[BridgeJS][ImportTS] Support export { thing } from "pkg" form in bridge-js.d.ts (#506)
1 parent a14541e commit e970740

File tree

7 files changed

+407
-0
lines changed

7 files changed

+407
-0
lines changed

Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ export class TypeProcessor {
5454
this.seenTypes = new Map();
5555
/** @type {string[]} Collected Swift code lines */
5656
this.swiftLines = [];
57+
58+
/** @type {Set<string>} */
59+
this.visitedDeclarationKeys = new Set();
5760
}
5861

5962
/**
@@ -117,6 +120,69 @@ export class TypeProcessor {
117120
this.visitFunctionDeclaration(node);
118121
} else if (ts.isClassDeclaration(node)) {
119122
this.visitClassDecl(node);
123+
} else if (ts.isExportDeclaration(node)) {
124+
this.visitExportDeclaration(node);
125+
}
126+
}
127+
128+
/**
129+
* Visit an export declaration and process re-exports like:
130+
* - export { Thing } from "./module";
131+
* - export { Thing as Alias } from "./module";
132+
* - export * from "./module";
133+
* @param {ts.ExportDeclaration} node
134+
*/
135+
visitExportDeclaration(node) {
136+
if (!node.moduleSpecifier) return;
137+
138+
const moduleSymbol = this.checker.getSymbolAtLocation(node.moduleSpecifier);
139+
if (!moduleSymbol) {
140+
this.diagnosticEngine.print("warning", "Failed to resolve module for export declaration", node);
141+
return;
142+
}
143+
144+
/** @type {ts.Symbol[]} */
145+
let targetSymbols = [];
146+
147+
if (!node.exportClause) {
148+
// export * from "..."
149+
targetSymbols = this.checker.getExportsOfModule(moduleSymbol);
150+
} else if (ts.isNamedExports(node.exportClause)) {
151+
const moduleExports = this.checker.getExportsOfModule(moduleSymbol);
152+
for (const element of node.exportClause.elements) {
153+
const originalName = element.propertyName?.text ?? element.name.text;
154+
155+
const match = moduleExports.find(s => s.name === originalName);
156+
if (match) {
157+
targetSymbols.push(match);
158+
continue;
159+
}
160+
161+
// Fallback for unusual bindings/resolution failures.
162+
const fallback = this.checker.getSymbolAtLocation(element.propertyName ?? element.name);
163+
if (fallback) {
164+
targetSymbols.push(fallback);
165+
continue;
166+
}
167+
168+
this.diagnosticEngine.print("warning", `Failed to resolve re-exported symbol '${originalName}'`, node);
169+
}
170+
} else {
171+
// export * as ns from "..." is not currently supported by BridgeJS imports.
172+
return;
173+
}
174+
175+
for (const symbol of targetSymbols) {
176+
const declarations = symbol.getDeclarations() ?? [];
177+
for (const declaration of declarations) {
178+
// Avoid duplicate emission when the same declaration is reached via multiple re-exports.
179+
const sourceFile = declaration.getSourceFile();
180+
const key = `${sourceFile.fileName}:${declaration.pos}:${declaration.end}`;
181+
if (this.visitedDeclarationKeys.has(key)) continue;
182+
this.visitedDeclarationKeys.add(key);
183+
184+
this.visitNode(declaration);
185+
}
120186
}
121187
}
122188

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { jsRoundTripNumber } from "./Support/ReExportTarget"
2+
export { JsGreeter } from "./Support/ReExportTarget"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function jsRoundTripNumber(v: number): number
2+
3+
export class JsGreeter {
4+
constructor(name: string);
5+
greet(): string;
6+
}
7+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export interface JsGreeter {
8+
greet(): string;
9+
}
10+
export type Exports = {
11+
}
12+
export type Imports = {
13+
jsRoundTripNumber(v: number): number;
14+
JsGreeter: {
15+
new(name: string): JsGreeter;
16+
}
17+
}
18+
export function createInstantiator(options: {
19+
imports: Imports;
20+
}, swift: any): Promise<{
21+
addImports: (importObject: WebAssembly.Imports) => void;
22+
setInstance: (instance: WebAssembly.Instance) => void;
23+
createExports: (instance: WebAssembly.Instance) => Exports;
24+
}>;
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export async function createInstantiator(options, swift) {
8+
let instance;
9+
let memory;
10+
let setException;
11+
const textDecoder = new TextDecoder("utf-8");
12+
const textEncoder = new TextEncoder("utf-8");
13+
let tmpRetString;
14+
let tmpRetBytes;
15+
let tmpRetException;
16+
let tmpRetOptionalBool;
17+
let tmpRetOptionalInt;
18+
let tmpRetOptionalFloat;
19+
let tmpRetOptionalDouble;
20+
let tmpRetOptionalHeapObject;
21+
let tmpRetTag;
22+
let tmpRetStrings = [];
23+
let tmpRetInts = [];
24+
let tmpRetF32s = [];
25+
let tmpRetF64s = [];
26+
let tmpParamInts = [];
27+
let tmpParamF32s = [];
28+
let tmpParamF64s = [];
29+
let tmpRetPointers = [];
30+
let tmpParamPointers = [];
31+
const enumHelpers = {};
32+
const structHelpers = {};
33+
34+
let _exports = null;
35+
let bjs = null;
36+
37+
return {
38+
/**
39+
* @param {WebAssembly.Imports} importObject
40+
*/
41+
addImports: (importObject, importsContext) => {
42+
bjs = {};
43+
importObject["bjs"] = bjs;
44+
const imports = options.getImports(importsContext);
45+
bjs["swift_js_return_string"] = function(ptr, len) {
46+
const bytes = new Uint8Array(memory.buffer, ptr, len);
47+
tmpRetString = textDecoder.decode(bytes);
48+
}
49+
bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) {
50+
const source = swift.memory.getObject(sourceId);
51+
const bytes = new Uint8Array(memory.buffer, bytesPtr);
52+
bytes.set(source);
53+
}
54+
bjs["swift_js_make_js_string"] = function(ptr, len) {
55+
const bytes = new Uint8Array(memory.buffer, ptr, len);
56+
return swift.memory.retain(textDecoder.decode(bytes));
57+
}
58+
bjs["swift_js_init_memory_with_result"] = function(ptr, len) {
59+
const target = new Uint8Array(memory.buffer, ptr, len);
60+
target.set(tmpRetBytes);
61+
tmpRetBytes = undefined;
62+
}
63+
bjs["swift_js_throw"] = function(id) {
64+
tmpRetException = swift.memory.retainByRef(id);
65+
}
66+
bjs["swift_js_retain"] = function(id) {
67+
return swift.memory.retainByRef(id);
68+
}
69+
bjs["swift_js_release"] = function(id) {
70+
swift.memory.release(id);
71+
}
72+
bjs["swift_js_push_tag"] = function(tag) {
73+
tmpRetTag = tag;
74+
}
75+
bjs["swift_js_push_int"] = function(v) {
76+
tmpRetInts.push(v | 0);
77+
}
78+
bjs["swift_js_push_f32"] = function(v) {
79+
tmpRetF32s.push(Math.fround(v));
80+
}
81+
bjs["swift_js_push_f64"] = function(v) {
82+
tmpRetF64s.push(v);
83+
}
84+
bjs["swift_js_push_string"] = function(ptr, len) {
85+
const bytes = new Uint8Array(memory.buffer, ptr, len);
86+
const value = textDecoder.decode(bytes);
87+
tmpRetStrings.push(value);
88+
}
89+
bjs["swift_js_pop_param_int32"] = function() {
90+
return tmpParamInts.pop();
91+
}
92+
bjs["swift_js_pop_param_f32"] = function() {
93+
return tmpParamF32s.pop();
94+
}
95+
bjs["swift_js_pop_param_f64"] = function() {
96+
return tmpParamF64s.pop();
97+
}
98+
bjs["swift_js_push_pointer"] = function(pointer) {
99+
tmpRetPointers.push(pointer);
100+
}
101+
bjs["swift_js_pop_param_pointer"] = function() {
102+
return tmpParamPointers.pop();
103+
}
104+
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
105+
if (isSome === 0) {
106+
tmpRetOptionalBool = null;
107+
} else {
108+
tmpRetOptionalBool = value !== 0;
109+
}
110+
}
111+
bjs["swift_js_return_optional_int"] = function(isSome, value) {
112+
if (isSome === 0) {
113+
tmpRetOptionalInt = null;
114+
} else {
115+
tmpRetOptionalInt = value | 0;
116+
}
117+
}
118+
bjs["swift_js_return_optional_float"] = function(isSome, value) {
119+
if (isSome === 0) {
120+
tmpRetOptionalFloat = null;
121+
} else {
122+
tmpRetOptionalFloat = Math.fround(value);
123+
}
124+
}
125+
bjs["swift_js_return_optional_double"] = function(isSome, value) {
126+
if (isSome === 0) {
127+
tmpRetOptionalDouble = null;
128+
} else {
129+
tmpRetOptionalDouble = value;
130+
}
131+
}
132+
bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) {
133+
if (isSome === 0) {
134+
tmpRetString = null;
135+
} else {
136+
const bytes = new Uint8Array(memory.buffer, ptr, len);
137+
tmpRetString = textDecoder.decode(bytes);
138+
}
139+
}
140+
bjs["swift_js_return_optional_object"] = function(isSome, objectId) {
141+
if (isSome === 0) {
142+
tmpRetString = null;
143+
} else {
144+
tmpRetString = swift.memory.getObject(objectId);
145+
}
146+
}
147+
bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) {
148+
if (isSome === 0) {
149+
tmpRetOptionalHeapObject = null;
150+
} else {
151+
tmpRetOptionalHeapObject = pointer;
152+
}
153+
}
154+
bjs["swift_js_get_optional_int_presence"] = function() {
155+
return tmpRetOptionalInt != null ? 1 : 0;
156+
}
157+
bjs["swift_js_get_optional_int_value"] = function() {
158+
const value = tmpRetOptionalInt;
159+
tmpRetOptionalInt = undefined;
160+
return value;
161+
}
162+
bjs["swift_js_get_optional_string"] = function() {
163+
const str = tmpRetString;
164+
tmpRetString = undefined;
165+
if (str == null) {
166+
return -1;
167+
} else {
168+
const bytes = textEncoder.encode(str);
169+
tmpRetBytes = bytes;
170+
return bytes.length;
171+
}
172+
}
173+
bjs["swift_js_get_optional_float_presence"] = function() {
174+
return tmpRetOptionalFloat != null ? 1 : 0;
175+
}
176+
bjs["swift_js_get_optional_float_value"] = function() {
177+
const value = tmpRetOptionalFloat;
178+
tmpRetOptionalFloat = undefined;
179+
return value;
180+
}
181+
bjs["swift_js_get_optional_double_presence"] = function() {
182+
return tmpRetOptionalDouble != null ? 1 : 0;
183+
}
184+
bjs["swift_js_get_optional_double_value"] = function() {
185+
const value = tmpRetOptionalDouble;
186+
tmpRetOptionalDouble = undefined;
187+
return value;
188+
}
189+
bjs["swift_js_get_optional_heap_object_pointer"] = function() {
190+
const pointer = tmpRetOptionalHeapObject;
191+
tmpRetOptionalHeapObject = undefined;
192+
return pointer || 0;
193+
}
194+
const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
195+
TestModule["bjs_jsRoundTripNumber"] = function bjs_jsRoundTripNumber(v) {
196+
try {
197+
let ret = imports.jsRoundTripNumber(v);
198+
return ret;
199+
} catch (error) {
200+
setException(error);
201+
return 0
202+
}
203+
}
204+
TestModule["bjs_JsGreeter_init"] = function bjs_JsGreeter_init(name) {
205+
try {
206+
const nameObject = swift.memory.getObject(name);
207+
swift.memory.release(name);
208+
return swift.memory.retain(new imports.JsGreeter(nameObject));
209+
} catch (error) {
210+
setException(error);
211+
return 0
212+
}
213+
}
214+
TestModule["bjs_JsGreeter_greet"] = function bjs_JsGreeter_greet(self) {
215+
try {
216+
let ret = swift.memory.getObject(self).greet();
217+
tmpRetBytes = textEncoder.encode(ret);
218+
return tmpRetBytes.length;
219+
} catch (error) {
220+
setException(error);
221+
}
222+
}
223+
},
224+
setInstance: (i) => {
225+
instance = i;
226+
memory = instance.exports.memory;
227+
228+
setException = (error) => {
229+
instance.exports._swift_js_exception.value = swift.memory.retain(error)
230+
}
231+
},
232+
/** @param {WebAssembly.Instance} instance */
233+
createExports: (instance) => {
234+
const js = swift.memory.heap;
235+
const exports = {
236+
};
237+
_exports = exports;
238+
return exports;
239+
},
240+
}
241+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
@_spi(Experimental) import JavaScriptKit
8+
9+
@JSFunction func jsRoundTripNumber(_ v: Double) throws (JSException) -> Double
10+
11+
@JSClass struct JsGreeter {
12+
@JSFunction init(_ name: String) throws (JSException)
13+
@JSFunction func greet() throws (JSException) -> String
14+
}

0 commit comments

Comments
 (0)