Skip to content

Commit 92e34cd

Browse files
committed
Fix CI: update snapshots, formatting, runtime test, add docs and review feedback
1 parent 01c4594 commit 92e34cd

8 files changed

Lines changed: 107 additions & 13 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1425,7 +1425,12 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
14251425
}
14261426
}
14271427

1428-
/// Walks extension members under the matching type’s state, returning whether the type was found
1428+
/// Walks extension members under the matching type’s state, returning whether the type was found.
1429+
///
1430+
/// Note: The lookup scans dictionaries keyed by `makeKey(name:namespace:)`, matching only by
1431+
/// plain name. If two types share a name but differ by namespace, `.first(where:)` picks
1432+
/// whichever comes first. This is acceptable today since namespace collisions are unlikely,
1433+
/// but may need refinement if namespace-qualified extension resolution is added.
14291434
func resolveExtension(_ ext: ExtensionDeclSyntax) -> Bool {
14301435
let name = ext.extendedType.trimmedDescription
14311436
let state: State
@@ -1435,6 +1440,13 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
14351440
state = .structBody(name: name, key: entry.key)
14361441
} else if let entry = exportedEnumByName.first(where: { $0.value.name == name }) {
14371442
state = .enumBody(name: name, key: entry.key)
1443+
} else if exportedProtocolByName.values.contains(where: { $0.name == name }) {
1444+
diagnose(
1445+
node: ext.extendedType,
1446+
message: "Protocol extensions are not supported by BridgeJS.",
1447+
hint: "You cannot extend `@JS` protocol '\(name)' with additional members"
1448+
)
1449+
return true
14381450
} else {
14391451
return false
14401452
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export interface DataPoint {
1616
label: string;
1717
optCount: number | null;
1818
optFlag: boolean | null;
19-
distanceFromOrigin(): number;
2019
}
2120
export interface Address {
2221
street: string;
@@ -44,6 +43,12 @@ export interface Container {
4443
object: any;
4544
optionalObject: any | null;
4645
}
46+
export interface Vector2D {
47+
dx: number;
48+
dy: number;
49+
magnitude(): number;
50+
scaled(factor: number): Vector2D;
51+
}
4752
export type PrecisionObject = typeof PrecisionValues;
4853

4954
/// Represents a Swift heap object like a class instance or an actor instance.

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,7 @@ export async function createInstantiator(options, swift) {
7474
const string = strStack.pop();
7575
const f64 = f64Stack.pop();
7676
const f641 = f64Stack.pop();
77-
const instance1 = { x: f641, y: f64, label: string, optCount: optValue1, optFlag: optValue };
78-
instance1.distanceFromOrigin = function() {
79-
structHelpers.DataPoint.lower(this);
80-
const ret = instance.exports.bjs_DataPoint_distanceFromOrigin();
81-
return ret;
82-
}.bind(instance1);
83-
return instance1;
77+
return { x: f641, y: f64, label: string, optCount: optValue1, optFlag: optValue };
8478
}
8579
});
8680
const __bjs_createAddressHelpers = () => ({
@@ -226,6 +220,29 @@ export async function createInstantiator(options, swift) {
226220
return { object: value, optionalObject: optValue };
227221
}
228222
});
223+
const __bjs_createVector2DHelpers = () => ({
224+
lower: (value) => {
225+
f64Stack.push(value.dx);
226+
f64Stack.push(value.dy);
227+
},
228+
lift: () => {
229+
const f64 = f64Stack.pop();
230+
const f641 = f64Stack.pop();
231+
const instance1 = { dx: f641, dy: f64 };
232+
instance1.magnitude = function() {
233+
structHelpers.Vector2D.lower(this);
234+
const ret = instance.exports.bjs_Vector2D_magnitude();
235+
return ret;
236+
}.bind(instance1);
237+
instance1.scaled = function(factor) {
238+
structHelpers.Vector2D.lower(this);
239+
const ret = instance.exports.bjs_Vector2D_scaled(factor);
240+
const structValue = structHelpers.Vector2D.lift();
241+
return structValue;
242+
}.bind(instance1);
243+
return instance1;
244+
}
245+
});
229246

230247
return {
231248
/**
@@ -343,6 +360,13 @@ export async function createInstantiator(options, swift) {
343360
const value = structHelpers.Container.lift();
344361
return swift.memory.retain(value);
345362
}
363+
bjs["swift_js_struct_lower_Vector2D"] = function(objectId) {
364+
structHelpers.Vector2D.lower(swift.memory.getObject(objectId));
365+
}
366+
bjs["swift_js_struct_lift_Vector2D"] = function() {
367+
const value = structHelpers.Vector2D.lift();
368+
return swift.memory.retain(value);
369+
}
346370
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
347371
if (isSome === 0) {
348372
tmpRetOptionalBool = null;
@@ -534,6 +558,9 @@ export async function createInstantiator(options, swift) {
534558
const ContainerHelpers = __bjs_createContainerHelpers();
535559
structHelpers.Container = ContainerHelpers;
536560

561+
const Vector2DHelpers = __bjs_createVector2DHelpers();
562+
structHelpers.Vector2D = Vector2DHelpers;
563+
537564
const exports = {
538565
Greeter,
539566
roundtrip: function bjs_roundtrip(session) {

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,53 @@ export type Exports = {
7777
}
7878
```
7979
80+
## Adding Members via Extensions
81+
82+
You can add exported methods, computed properties, and static members to a `@JS` class using extensions. The extension block itself does not need `@JS` - only the individual members do:
83+
84+
```swift
85+
@JS class Greeter {
86+
@JS var name: String
87+
88+
@JS init(name: String) {
89+
self.name = name
90+
}
91+
92+
@JS func greet() -> String {
93+
return "Hello, " + self.name + "!"
94+
}
95+
}
96+
97+
extension Greeter {
98+
@JS func greetEnthusiastically() -> String {
99+
return "Hey, " + self.name + "!!!"
100+
}
101+
102+
@JS var nameCount: Int { name.count }
103+
104+
@JS static func greetAnonymously() -> String {
105+
return "Hello."
106+
}
107+
108+
@JS static var defaultGreeting: String { "Hello, world!" }
109+
}
110+
```
111+
112+
This also works across files within the same module:
113+
114+
```swift
115+
// GreeterExtension.swift
116+
extension Greeter {
117+
@JS func greetFormally() -> String {
118+
return "Good day, " + self.name + "."
119+
}
120+
}
121+
```
122+
123+
All `@JS`-annotated members in extensions are merged into the same generated TypeScript interface as the original class declaration.
124+
125+
> Note: Extensions must target `@JS`-annotated types from the same module.
126+
80127
## How It Works
81128

82129
Classes use **reference semantics** when crossing the Swift/JavaScript boundary:
@@ -103,5 +150,6 @@ This differs from structs, which use copy semantics and transfer data by value.
103150
| Static / class properties: `static var`, `class let` | ✅ (See <doc:Exporting-Swift-Static-Properties> )|
104151
| Methods: `func` | ✅ (See <doc:Exporting-Swift-Function> ) |
105152
| Static/class methods: `static func`, `class func` | ✅ (See <doc:Exporting-Swift-Static-Functions> ) |
153+
| Extension methods/properties ||
106154
| Subscripts: `subscript()` ||
107155
| Generics ||

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,4 +514,5 @@ This differs from classes, which use reference semantics and share state across
514514
| Associated values: `JSObject` ||
515515
| Associated values: Arrays ||
516516
| Associated values: Optionals of all supported types ||
517+
| Extension static functions/properties ||
517518
| Generics ||

Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ This differs from classes, which use reference semantics and share state across
165165
| Instance methods ||
166166
| Static methods ||
167167
| Static properties ||
168+
| Extension methods/properties ||
168169
| Property observers (`willSet`, `didSet`) ||
169170
| Generics ||
170171
| Conformances ||

Tests/JavaScriptEventLoopTests/JSClosure+AsyncTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class JSClosureAsyncTests: XCTestCase {
7272
)!.value()
7373
XCTAssertEqual(result, 42.0)
7474
}
75-
75+
7676
func testAsyncOneshotClosureWithPriority() async throws {
7777
let priority = UnsafeSendableBox<TaskPriority?>(nil)
7878
let closure = JSOneshotClosure.async(priority: .high) { _ in
@@ -83,7 +83,7 @@ class JSClosureAsyncTests: XCTestCase {
8383
XCTAssertEqual(result, 42.0)
8484
XCTAssertEqual(priority.value, .high)
8585
}
86-
86+
8787
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
8888
func testAsyncOneshotClosureWithTaskExecutor() async throws {
8989
let executor = AnyTaskExecutor()
@@ -93,7 +93,7 @@ class JSClosureAsyncTests: XCTestCase {
9393
let result = try await JSPromise(from: closure.function!())!.value()
9494
XCTAssertEqual(result, 42.0)
9595
}
96-
96+
9797
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
9898
func testAsyncOneshotClosureWithTaskExecutorPreference() async throws {
9999
let executor = AnyTaskExecutor()

Tests/prelude.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ function testStructSupport(exports) {
801801
assert.equal(exports.DataPoint.dimensions, 2);
802802

803803
// Test struct extension instance methods
804-
const vec = new exports.Vector2D(3.0, 4.0);
804+
const vec = exports.Vector2D.init(3.0, 4.0);
805805
assert.equal(vec.magnitude(), 5.0);
806806
const scaled = vec.scaled(2.0);
807807
assert.equal(scaled.dx, 6.0);

0 commit comments

Comments
 (0)