Skip to content

Commit 4c1c32c

Browse files
committed
Fix CI: update snapshots, formatting, runtime test, add docs and review feedback
1 parent a93a686 commit 4c1c32c

File tree

8 files changed

+107
-13
lines changed

8 files changed

+107
-13
lines changed

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
@@ -73,13 +73,7 @@ export async function createInstantiator(options, swift) {
7373
const string = strStack.pop();
7474
const f64 = f64Stack.pop();
7575
const f641 = f64Stack.pop();
76-
const instance1 = { x: f641, y: f64, label: string, optCount: optValue1, optFlag: optValue };
77-
instance1.distanceFromOrigin = function() {
78-
structHelpers.DataPoint.lower(this);
79-
const ret = instance.exports.bjs_DataPoint_distanceFromOrigin();
80-
return ret;
81-
}.bind(instance1);
82-
return instance1;
76+
return { x: f641, y: f64, label: string, optCount: optValue1, optFlag: optValue };
8377
}
8478
});
8579
const __bjs_createAddressHelpers = () => ({
@@ -225,6 +219,29 @@ export async function createInstantiator(options, swift) {
225219
return { object: value, optionalObject: optValue };
226220
}
227221
});
222+
const __bjs_createVector2DHelpers = () => ({
223+
lower: (value) => {
224+
f64Stack.push(value.dx);
225+
f64Stack.push(value.dy);
226+
},
227+
lift: () => {
228+
const f64 = f64Stack.pop();
229+
const f641 = f64Stack.pop();
230+
const instance1 = { dx: f641, dy: f64 };
231+
instance1.magnitude = function() {
232+
structHelpers.Vector2D.lower(this);
233+
const ret = instance.exports.bjs_Vector2D_magnitude();
234+
return ret;
235+
}.bind(instance1);
236+
instance1.scaled = function(factor) {
237+
structHelpers.Vector2D.lower(this);
238+
const ret = instance.exports.bjs_Vector2D_scaled(factor);
239+
const structValue = structHelpers.Vector2D.lift();
240+
return structValue;
241+
}.bind(instance1);
242+
return instance1;
243+
}
244+
});
228245

229246
return {
230247
/**
@@ -336,6 +353,13 @@ export async function createInstantiator(options, swift) {
336353
const value = structHelpers.Container.lift();
337354
return swift.memory.retain(value);
338355
}
356+
bjs["swift_js_struct_lower_Vector2D"] = function(objectId) {
357+
structHelpers.Vector2D.lower(swift.memory.getObject(objectId));
358+
}
359+
bjs["swift_js_struct_lift_Vector2D"] = function() {
360+
const value = structHelpers.Vector2D.lift();
361+
return swift.memory.retain(value);
362+
}
339363
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
340364
if (isSome === 0) {
341365
tmpRetOptionalBool = null;
@@ -527,6 +551,9 @@ export async function createInstantiator(options, swift) {
527551
const ContainerHelpers = __bjs_createContainerHelpers();
528552
structHelpers.Container = ContainerHelpers;
529553

554+
const Vector2DHelpers = __bjs_createVector2DHelpers();
555+
structHelpers.Vector2D = Vector2DHelpers;
556+
530557
const exports = {
531558
Greeter,
532559
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
@@ -797,7 +797,7 @@ function testStructSupport(exports) {
797797
assert.equal(exports.DataPoint.dimensions, 2);
798798

799799
// Test struct extension instance methods
800-
const vec = new exports.Vector2D(3.0, 4.0);
800+
const vec = exports.Vector2D.init(3.0, 4.0);
801801
assert.equal(vec.magnitude(), 5.0);
802802
const scaled = vec.scaled(2.0);
803803
assert.equal(scaled.dx, 6.0);

0 commit comments

Comments
 (0)