From ed57d5e6be2e58da66f2a35e684e80b2567079cf Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Jan 2026 01:23:12 +0900 Subject: [PATCH 1/6] =?UTF-8?q?-=20Added=20`@JSGetter(jsName:=20String=3F?= =?UTF-8?q?=20=3D=20nil)`=20so=20generated=20getters=20can=20target=20non-?= =?UTF-8?q?Swift/quoted=20JS=20property=20names=20(`Sources/JavaScriptKit/?= =?UTF-8?q?Macros.swift`).=20-=20Plumbed=20`jsName`=20through=20the=20impo?= =?UTF-8?q?rted-property=20IR=20(`Plugins/BridgeJS/Sources/BridgeJSSkeleto?= =?UTF-8?q?n/BridgeJSSkeleton.swift`)=20and=20extraction=20(`Plugins/Bridg?= =?UTF-8?q?eJS/Sources/BridgeJSCore/SwiftToSkeleton.swift`).=20-=20Updated?= =?UTF-8?q?=20TS=E2=86=92Swift=20generation=20to=20no=20longer=20drop=20qu?= =?UTF-8?q?oted/invalid=20TS=20property=20names;=20it=20now=20emits=20Swif?= =?UTF-8?q?t-safe=20identifiers=20plus=20`@JSGetter(jsName:=20...)`=20/=20?= =?UTF-8?q?`@JSSetter(jsName:=20...)`=20as=20needed=20(`Plugins/BridgeJS/S?= =?UTF-8?q?ources/TS2Swift/JavaScript/src/processor.js`).=20-=20Updated=20?= =?UTF-8?q?JS=20glue=20+=20generated=20`.d.ts`=20to=20use=20bracket=20prop?= =?UTF-8?q?erty=20access=20and=20quote=20invalid=20TS=20property=20keys=20?= =?UTF-8?q?(`Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift`).?= =?UTF-8?q?=20-=20Updated=20snapshots;=20`swift=20test=20--package-path=20?= =?UTF-8?q?./Plugins/BridgeJS`=20now=20passes=20(requires=20`npm=20ci`=20a?= =?UTF-8?q?t=20repo=20root=20to=20provide=20`typescript`).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BridgeJSCore/SwiftToSkeleton.swift | 39 ++-- .../Sources/BridgeJSLink/BridgeJSLink.swift | 54 ++++-- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 8 + .../TS2Swift/JavaScript/src/processor.js | 33 ++-- .../InvalidPropertyNames.Import.d.ts | 5 + .../InvalidPropertyNames.Import.js | 98 +++++++++- .../MultipleImportedTypes.Import.js | 10 +- .../BridgeJSLinkTests/Protocol.Export.js | 42 ++-- .../TS2SkeletonLike.Import.js | 4 +- .../TypeScriptClass.Import.js | 6 +- .../InvalidPropertyNames.Macros.swift | 10 + .../ImportTSTests/InvalidPropertyNames.swift | 180 ++++++++++++++++++ Sources/JavaScriptKit/Macros.swift | 5 +- 13 files changed, 414 insertions(+), 80 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index f18788e7d..f09a9140a 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1762,6 +1762,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { hasAttribute(attributes, name: "JSGetter") } + static func firstJSGetterAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? { + attributes?.first { attribute in + attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSGetter" + }?.as(AttributeSyntax.self) + } + static func hasJSSetterAttribute(_ attributes: AttributeListSyntax?) -> Bool { hasAttribute(attributes, name: "JSSetter") } @@ -1784,7 +1790,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { } } - /// Extracts the jsName argument value from a @JSSetter attribute, if present. + /// Extracts the `jsName` argument value from an attribute, if present. static func extractJSName(from attribute: AttributeSyntax) -> String? { guard let arguments = attribute.arguments?.as(LabeledExprListSyntax.self) else { return nil @@ -1883,22 +1889,15 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { // MARK: - Property Name Resolution - /// Helper for resolving property names from setter function names and jsName attributes + /// Helper for resolving property names from setter function names. private struct PropertyNameResolver { - /// Resolves property name and function base name from a setter function and optional jsName - /// - Returns: (propertyName, functionBaseName) where propertyName preserves case for getter matching, - /// and functionBaseName has lowercase first char for ABI generation + /// Resolves property name and function base name from a setter function. + /// - Returns: (propertyName, functionBaseName) where `propertyName` is derived from the setter name, + /// and `functionBaseName` has lowercase first char for ABI generation. static func resolve( functionName: String, - jsName: String?, normalizeIdentifier: (String) -> String ) -> (propertyName: String, functionBaseName: String)? { - if let jsName = jsName { - let propertyName = normalizeIdentifier(jsName) - let functionBaseName = propertyName.prefix(1).lowercased() + propertyName.dropFirst() - return (propertyName: propertyName, functionBaseName: functionBaseName) - } - let rawFunctionName = functionName.hasPrefix("`") && functionName.hasSuffix("`") && functionName.count > 2 ? String(functionName.dropFirst().dropLast()) @@ -2065,10 +2064,13 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { guard AttributeChecker.hasJSGetterAttribute(node.attributes) else { return .visitChildren } + guard let jsGetter = AttributeChecker.firstJSGetterAttribute(node.attributes) else { + return .skipChildren + } switch state { case .topLevel: - if let getter = parseGetterSkeleton(node) { + if let getter = parseGetterSkeleton(jsGetter, node) { importedGlobalGetters.append(getter) } return .skipChildren @@ -2085,7 +2087,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { "@JSGetter is not supported for static members. Use it only for instance members in @JSClass types." ) ) - } else if let getter = parseGetterSkeleton(node) { + } else if let getter = parseGetterSkeleton(jsGetter, node) { type.getters.append(getter) currentType = type } @@ -2223,7 +2225,10 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { return (identifier, typeAnnotation.type) } - private func parseGetterSkeleton(_ node: VariableDeclSyntax) -> ImportedGetterSkeleton? { + private func parseGetterSkeleton( + _ jsGetter: AttributeSyntax, + _ node: VariableDeclSyntax + ) -> ImportedGetterSkeleton? { guard let (identifier, type) = extractPropertyInfo(node) else { return nil } @@ -2231,8 +2236,10 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { return nil } let propertyName = SwiftToSkeleton.normalizeIdentifier(identifier.identifier.text) + let jsName = AttributeChecker.extractJSName(from: jsGetter) return ImportedGetterSkeleton( name: propertyName, + jsName: jsName, type: propertyType, documentation: nil, functionName: nil @@ -2252,7 +2259,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { guard let (propertyName, functionBaseName) = PropertyNameResolver.resolve( functionName: functionName, - jsName: validation.jsName, normalizeIdentifier: SwiftToSkeleton.normalizeIdentifier ) else { @@ -2261,6 +2267,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { return ImportedSetterSkeleton( name: propertyName, + jsName: validation.jsName, type: validation.valueType, documentation: nil, functionName: "\(functionBaseName)_set" diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 929e6e4cd..bcb19320b 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -1202,17 +1202,20 @@ public struct BridgeJSLink { // Add properties from getters var propertyNames = Set() for getter in type.getters { - propertyNames.insert(getter.name) - let hasSetter = type.setters.contains { $0.name == getter.name } + let propertyName = getter.jsName ?? getter.name + propertyNames.insert(propertyName) + let hasSetter = type.setters.contains { ($0.jsName ?? $0.name) == propertyName } let propertySignature = hasSetter - ? "\(getter.name): \(resolveTypeScriptType(getter.type));" - : "readonly \(getter.name): \(resolveTypeScriptType(getter.type));" + ? "\(renderTSPropertyName(propertyName)): \(resolveTypeScriptType(getter.type));" + : "readonly \(renderTSPropertyName(propertyName)): \(resolveTypeScriptType(getter.type));" printer.write(propertySignature) } // Add setters that don't have corresponding getters - for setter in type.setters where !propertyNames.contains(setter.name) { - printer.write("\(setter.name): \(resolveTypeScriptType(setter.type));") + for setter in type.setters { + let propertyName = setter.jsName ?? setter.name + guard !propertyNames.contains(propertyName) else { continue } + printer.write("\(renderTSPropertyName(propertyName)): \(resolveTypeScriptType(setter.type));") } printer.unindent() @@ -1387,6 +1390,20 @@ public struct BridgeJSLink { return "(\(parameterSignatures.joined(separator: ", "))): \(returnTypeWithEffect)" } + private func renderTSPropertyName(_ name: String) -> String { + // TypeScript allows quoted property names for keys that aren't valid identifiers. + if name.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil { + return name + } + return "\"\(Self.escapeForJavaScriptStringLiteral(name))\"" + } + + fileprivate static func escapeForJavaScriptStringLiteral(_ string: String) -> String { + string + .replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "\"", with: "\\\"") + } + /// Helper method to append JSDoc comments for parameters with default values private func appendJSDocIfNeeded(for parameters: [Parameter], to lines: inout [String]) { let jsDocLines = DefaultValueUtils.formatJSDoc(for: parameters) @@ -2151,6 +2168,9 @@ extension BridgeJSLink { } func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? { + let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(name) + let accessExpr = + "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)[\"\(escapedName)\"]" if context == .exportSwift, returnType.usesSideChannelForOptionalReturn() { guard case .optional(let wrappedType) = returnType else { fatalError("usesSideChannelForOptionalReturn returned true for non-optional type") @@ -2158,7 +2178,7 @@ extension BridgeJSLink { let resultVar = scope.variable("ret") body.write( - "let \(resultVar) = \(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name);" + "let \(resultVar) = \(accessExpr);" ) let fragment = try IntrinsicJSFragment.protocolPropertyOptionalToSideChannel(wrappedType: wrappedType) @@ -2168,14 +2188,15 @@ extension BridgeJSLink { } return try call( - callExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)", + callExpr: accessExpr, returnType: returnType ) } func callPropertySetter(name: String, returnType: BridgeType) { + let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(name) let call = - "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name) = \(parameterForwardings.joined(separator: ", "))" + "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)[\"\(escapedName)\"] = \(parameterForwardings.joined(separator: ", "))" body.write("\(call);") } @@ -2185,7 +2206,8 @@ extension BridgeJSLink { } let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType, context: context) - let expr = "imports[\"\(name)\"]" + let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(name) + let expr = "imports[\"\(escapedName)\"]" let returnExpr: String? if loweringFragment.parameters.count == 0 { @@ -2948,14 +2970,15 @@ extension BridgeJSLink { getter: ImportedGetterSkeleton ) throws { let thunkBuilder = ImportedThunkBuilder() - let returnExpr = try thunkBuilder.getImportProperty(name: getter.name, returnType: getter.type) + let jsName = getter.jsName ?? getter.name + let returnExpr = try thunkBuilder.getImportProperty(name: jsName, returnType: getter.type) let abiName = getter.abiName(context: nil) let funcLines = thunkBuilder.renderFunction( name: abiName, returnExpr: returnExpr, returnType: getter.type ) - importObjectBuilder.appendDts(["readonly \(getter.name): \(getter.type.tsType);"]) + importObjectBuilder.appendDts(["readonly \(renderTSPropertyName(jsName)): \(getter.type.tsType);"]) importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) } @@ -2976,7 +2999,10 @@ extension BridgeJSLink { getter: getter, abiName: getterAbiName, emitCall: { thunkBuilder in - return try thunkBuilder.callPropertyGetter(name: getter.name, returnType: getter.type) + return try thunkBuilder.callPropertyGetter( + name: getter.jsName ?? getter.name, + returnType: getter.type + ) } ) importObjectBuilder.assignToImportObject(name: getterAbiName, function: js) @@ -2992,7 +3018,7 @@ extension BridgeJSLink { try thunkBuilder.liftParameter( param: Parameter(label: nil, name: "newValue", type: setter.type) ) - thunkBuilder.callPropertySetter(name: setter.name, returnType: setter.type) + thunkBuilder.callPropertySetter(name: setter.jsName ?? setter.name, returnType: setter.type) return nil } ) diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 28e4b6dcd..7040d607d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -626,6 +626,8 @@ public struct ImportedConstructorSkeleton: Codable { public struct ImportedGetterSkeleton: Codable { public let name: String + /// The JavaScript property name to read from, if different from `name`. + public let jsName: String? public let type: BridgeType public let documentation: String? /// Name of the getter function if it's a separate function (from @JSGetter) @@ -633,11 +635,13 @@ public struct ImportedGetterSkeleton: Codable { public init( name: String, + jsName: String? = nil, type: BridgeType, documentation: String? = nil, functionName: String? = nil ) { self.name = name + self.jsName = jsName self.type = type self.documentation = documentation self.functionName = functionName @@ -661,6 +665,8 @@ public struct ImportedGetterSkeleton: Codable { public struct ImportedSetterSkeleton: Codable { public let name: String + /// The JavaScript property name to write to, if different from `name`. + public let jsName: String? public let type: BridgeType public let documentation: String? /// Name of the setter function if it's a separate function (from @JSSetter) @@ -668,11 +674,13 @@ public struct ImportedSetterSkeleton: Codable { public init( name: String, + jsName: String? = nil, type: BridgeType, documentation: String? = nil, functionName: String? = nil ) { self.name = name + self.jsName = jsName self.type = type self.documentation = documentation self.functionName = functionName diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js index 6a752ab80..87663cb88 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js @@ -390,21 +390,28 @@ export class TypeProcessor { /** * @param {ts.PropertyDeclaration | ts.PropertySignature} node - * @returns {{ name: string, type: string, isReadonly: boolean, documentation: string | undefined } | null} + * @returns {{ jsName: string, swiftName: string, type: string, isReadonly: boolean, documentation: string | undefined } | null} */ visitPropertyDecl(node) { if (!node.name) return null; - - const propertyName = node.name.getText(); - if (!isValidSwiftDeclName(propertyName)) { + /** @type {string | null} */ + let jsName = null; + if (ts.isIdentifier(node.name)) { + jsName = node.name.text; + } else if (ts.isStringLiteral(node.name) || ts.isNumericLiteral(node.name)) { + jsName = node.name.text; + } else { + // Computed property names like `[Symbol.iterator]` are not supported yet. return null; } + const swiftName = isValidSwiftDeclName(jsName) ? jsName : makeValidSwiftIdentifier(jsName, { emptyFallback: "_" }); + const type = this.checker.getTypeAtLocation(node) const swiftType = this.visitType(type, node); const isReadonly = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false; const documentation = this.getFullJSDocText(node); - return { name: propertyName, type: swiftType, isReadonly, documentation }; + return { jsName, swiftName, type: swiftType, isReadonly, documentation }; } /** @@ -626,17 +633,21 @@ export class TypeProcessor { if (!property) return; const type = property.type; - const name = this.renderIdentifier(property.name); + const swiftName = this.renderIdentifier(property.swiftName); + const needsJSGetterName = property.jsName !== property.swiftName; + const escapedJSName = property.jsName.replaceAll("\\", "\\\\").replaceAll("\"", "\\\\\""); + const getterAnnotation = needsJSGetterName ? `@JSGetter(jsName: "${escapedJSName}")` : "@JSGetter"; // Always render getter - this.swiftLines.push(` @JSGetter var ${name}: ${type}`); + this.swiftLines.push(` ${getterAnnotation} var ${swiftName}: ${type}`); // Render setter if not readonly if (!property.isReadonly) { - const capitalizedName = property.name.charAt(0).toUpperCase() + property.name.slice(1); - const needsJSNameField = property.name.charAt(0) != capitalizedName.charAt(0).toLowerCase(); - const setterName = `set${capitalizedName}`; - const annotation = needsJSNameField ? `@JSSetter(jsName: "${property.name}")` : "@JSSetter"; + const capitalizedSwiftName = property.swiftName.charAt(0).toUpperCase() + property.swiftName.slice(1); + const derivedPropertyName = property.swiftName.charAt(0).toLowerCase() + property.swiftName.slice(1); + const needsJSNameField = property.jsName !== derivedPropertyName; + const setterName = `set${capitalizedSwiftName}`; + const annotation = needsJSNameField ? `@JSSetter(jsName: "${escapedJSName}")` : "@JSSetter"; this.swiftLines.push(` ${annotation} func ${this.renderIdentifier(setterName)}(_ value: ${type}) ${this.renderEffects({ isAsync: false })}`); } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts index 2b0474bb5..bcd4c7392 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts @@ -11,6 +11,11 @@ export interface ArrayBufferLike { export interface WeirdNaming { as(): void; normalProperty: string; + "property-with-dashes": number; + "123invalidStart": boolean; + "property with spaces": string; + "@specialChar": number; + constructor: string; for: string; Any: string; } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js index 60435e569..585b276de 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -212,7 +212,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_ArrayBufferLike_byteLength_get"] = function bjs_ArrayBufferLike_byteLength_get(self) { try { - let ret = swift.memory.getObject(self).byteLength; + let ret = swift.memory.getObject(self)["byteLength"]; return ret; } catch (error) { setException(error); @@ -230,7 +230,52 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_normalProperty_get"] = function bjs_WeirdNaming_normalProperty_get(self) { try { - let ret = swift.memory.getObject(self).normalProperty; + let ret = swift.memory.getObject(self)["normalProperty"]; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_property_with_dashes_get"] = function bjs_WeirdNaming_property_with_dashes_get(self) { + try { + let ret = swift.memory.getObject(self)["property-with-dashes"]; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WeirdNaming__123invalidStart_get"] = function bjs_WeirdNaming__123invalidStart_get(self) { + try { + let ret = swift.memory.getObject(self)["123invalidStart"]; + return ret ? 1 : 0; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WeirdNaming_property_with_spaces_get"] = function bjs_WeirdNaming_property_with_spaces_get(self) { + try { + let ret = swift.memory.getObject(self)["property with spaces"]; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming__specialChar_get"] = function bjs_WeirdNaming__specialChar_get(self) { + try { + let ret = swift.memory.getObject(self)["@specialChar"]; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WeirdNaming_constructor_get"] = function bjs_WeirdNaming_constructor_get(self) { + try { + let ret = swift.memory.getObject(self)["constructor"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -239,7 +284,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_for_get"] = function bjs_WeirdNaming_for_get(self) { try { - let ret = swift.memory.getObject(self).for; + let ret = swift.memory.getObject(self)["for"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -248,7 +293,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_Any_get"] = function bjs_WeirdNaming_Any_get(self) { try { - let ret = swift.memory.getObject(self).Any; + let ret = swift.memory.getObject(self)["Any"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -259,7 +304,46 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self).normalProperty = newValueObject; + swift.memory.getObject(self)["normalProperty"] = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_property_with_dashes_set"] = function bjs_WeirdNaming_property_with_dashes_set(self, newValue) { + try { + swift.memory.getObject(self)["property-with-dashes"] = newValue; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming__123invalidStart_set"] = function bjs_WeirdNaming__123invalidStart_set(self, newValue) { + try { + swift.memory.getObject(self)["123invalidStart"] = newValue !== 0; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_property_with_spaces_set"] = function bjs_WeirdNaming_property_with_spaces_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self)["property with spaces"] = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming__specialChar_set"] = function bjs_WeirdNaming__specialChar_set(self, newValue) { + try { + swift.memory.getObject(self)["@specialChar"] = newValue; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_constructor_set"] = function bjs_WeirdNaming_constructor_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self)["constructor"] = newValueObject; } catch (error) { setException(error); } @@ -268,7 +352,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self).for = newValueObject; + swift.memory.getObject(self)["for"] = newValueObject; } catch (error) { setException(error); } @@ -277,7 +361,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self).Any = newValueObject; + swift.memory.getObject(self)["Any"] = newValueObject; } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 40a3f4e3f..59457a1a2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -223,7 +223,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_DatabaseConnection_isConnected_get"] = function bjs_DatabaseConnection_isConnected_get(self) { try { - let ret = swift.memory.getObject(self).isConnected; + let ret = swift.memory.getObject(self)["isConnected"]; return ret ? 1 : 0; } catch (error) { setException(error); @@ -232,7 +232,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_DatabaseConnection_connectionTimeout_get"] = function bjs_DatabaseConnection_connectionTimeout_get(self) { try { - let ret = swift.memory.getObject(self).connectionTimeout; + let ret = swift.memory.getObject(self)["connectionTimeout"]; return ret; } catch (error) { setException(error); @@ -241,7 +241,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_DatabaseConnection_connectionTimeout_set"] = function bjs_DatabaseConnection_connectionTimeout_set(self, newValue) { try { - swift.memory.getObject(self).connectionTimeout = newValue; + swift.memory.getObject(self)["connectionTimeout"] = newValue; } catch (error) { setException(error); } @@ -268,7 +268,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_Logger_level_get"] = function bjs_Logger_level_get(self) { try { - let ret = swift.memory.getObject(self).level; + let ret = swift.memory.getObject(self)["level"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -295,7 +295,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_ConfigManager_configPath_get"] = function bjs_ConfigManager_configPath_get(self) { try { - let ret = swift.memory.getObject(self).configPath; + let ret = swift.memory.getObject(self)["configPath"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js index d6b9a4f01..ee2212bbe 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js @@ -269,7 +269,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_MyViewControllerDelegate_eventCount_get"] = function bjs_MyViewControllerDelegate_eventCount_get(self) { try { - let ret = swift.memory.getObject(self).eventCount; + let ret = swift.memory.getObject(self)["eventCount"]; return ret; } catch (error) { setException(error); @@ -278,14 +278,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_eventCount_set"] = function bjs_MyViewControllerDelegate_eventCount_set(self, value) { try { - swift.memory.getObject(self).eventCount = value; + swift.memory.getObject(self)["eventCount"] = value; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_delegateName_get"] = function bjs_MyViewControllerDelegate_delegateName_get(self) { try { - let ret = swift.memory.getObject(self).delegateName; + let ret = swift.memory.getObject(self)["delegateName"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -294,7 +294,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_optionalName_get"] = function bjs_MyViewControllerDelegate_optionalName_get(self) { try { - let ret = swift.memory.getObject(self).optionalName; + let ret = swift.memory.getObject(self)["optionalName"]; tmpRetString = ret; } catch (error) { setException(error); @@ -307,14 +307,14 @@ export async function createInstantiator(options, swift) { obj = swift.memory.getObject(valueWrappedValue); swift.memory.release(valueWrappedValue); } - swift.memory.getObject(self).optionalName = valueIsSome ? obj : null; + swift.memory.getObject(self)["optionalName"] = valueIsSome ? obj : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_optionalRawEnum_get"] = function bjs_MyViewControllerDelegate_optionalRawEnum_get(self) { try { - let ret = swift.memory.getObject(self).optionalRawEnum; + let ret = swift.memory.getObject(self)["optionalRawEnum"]; tmpRetString = ret; } catch (error) { setException(error); @@ -327,14 +327,14 @@ export async function createInstantiator(options, swift) { obj = swift.memory.getObject(valueWrappedValue); swift.memory.release(valueWrappedValue); } - swift.memory.getObject(self).optionalRawEnum = valueIsSome ? obj : null; + swift.memory.getObject(self)["optionalRawEnum"] = valueIsSome ? obj : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_rawStringEnum_get"] = function bjs_MyViewControllerDelegate_rawStringEnum_get(self) { try { - let ret = swift.memory.getObject(self).rawStringEnum; + let ret = swift.memory.getObject(self)["rawStringEnum"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -345,14 +345,14 @@ export async function createInstantiator(options, swift) { try { const valueObject = swift.memory.getObject(value); swift.memory.release(value); - swift.memory.getObject(self).rawStringEnum = valueObject; + swift.memory.getObject(self)["rawStringEnum"] = valueObject; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_result_get"] = function bjs_MyViewControllerDelegate_result_get(self) { try { - let ret = swift.memory.getObject(self).result; + let ret = swift.memory.getObject(self)["result"]; const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); return caseId; } catch (error) { @@ -362,14 +362,14 @@ export async function createInstantiator(options, swift) { TestModule["bjs_MyViewControllerDelegate_result_set"] = function bjs_MyViewControllerDelegate_result_set(self, value) { try { const enumValue = enumHelpers.Result.raise(value, tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s); - swift.memory.getObject(self).result = enumValue; + swift.memory.getObject(self)["result"] = enumValue; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_optionalResult_get"] = function bjs_MyViewControllerDelegate_optionalResult_get(self) { try { - let ret = swift.memory.getObject(self).optionalResult; + let ret = swift.memory.getObject(self)["optionalResult"]; const isSome = ret != null; if (isSome) { const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); @@ -387,14 +387,14 @@ export async function createInstantiator(options, swift) { if (valueIsSome) { enumValue = enumHelpers.Result.raise(valueWrappedValue, tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s); } - swift.memory.getObject(self).optionalResult = valueIsSome ? enumValue : null; + swift.memory.getObject(self)["optionalResult"] = valueIsSome ? enumValue : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_direction_get"] = function bjs_MyViewControllerDelegate_direction_get(self) { try { - let ret = swift.memory.getObject(self).direction; + let ret = swift.memory.getObject(self)["direction"]; return ret; } catch (error) { setException(error); @@ -403,14 +403,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_direction_set"] = function bjs_MyViewControllerDelegate_direction_set(self, value) { try { - swift.memory.getObject(self).direction = value; + swift.memory.getObject(self)["direction"] = value; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_directionOptional_get"] = function bjs_MyViewControllerDelegate_directionOptional_get(self) { try { - let ret = swift.memory.getObject(self).directionOptional; + let ret = swift.memory.getObject(self)["directionOptional"]; const isSome = ret != null; return isSome ? (ret | 0) : -1; } catch (error) { @@ -419,14 +419,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_directionOptional_set"] = function bjs_MyViewControllerDelegate_directionOptional_set(self, valueIsSome, valueWrappedValue) { try { - swift.memory.getObject(self).directionOptional = valueIsSome ? valueWrappedValue : null; + swift.memory.getObject(self)["directionOptional"] = valueIsSome ? valueWrappedValue : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_priority_get"] = function bjs_MyViewControllerDelegate_priority_get(self) { try { - let ret = swift.memory.getObject(self).priority; + let ret = swift.memory.getObject(self)["priority"]; return ret; } catch (error) { setException(error); @@ -435,14 +435,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_priority_set"] = function bjs_MyViewControllerDelegate_priority_set(self, value) { try { - swift.memory.getObject(self).priority = value; + swift.memory.getObject(self)["priority"] = value; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_priorityOptional_get"] = function bjs_MyViewControllerDelegate_priorityOptional_get(self) { try { - let ret = swift.memory.getObject(self).priorityOptional; + let ret = swift.memory.getObject(self)["priorityOptional"]; tmpRetOptionalInt = ret; } catch (error) { setException(error); @@ -450,7 +450,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_priorityOptional_set"] = function bjs_MyViewControllerDelegate_priorityOptional_set(self, valueIsSome, valueWrappedValue) { try { - swift.memory.getObject(self).priorityOptional = valueIsSome ? valueWrappedValue : null; + swift.memory.getObject(self)["priorityOptional"] = valueIsSome ? valueWrappedValue : null; } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 2bbae4fec..7c11cb01c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -214,7 +214,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_TypeScriptProcessor_version_get"] = function bjs_TypeScriptProcessor_version_get(self) { try { - let ret = swift.memory.getObject(self).version; + let ret = swift.memory.getObject(self)["version"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -245,7 +245,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_CodeGenerator_outputFormat_get"] = function bjs_CodeGenerator_outputFormat_get(self) { try { - let ret = swift.memory.getObject(self).outputFormat; + let ret = swift.memory.getObject(self)["outputFormat"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 6c44fd008..0209bd3fd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -204,7 +204,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_Greeter_name_get"] = function bjs_Greeter_name_get(self) { try { - let ret = swift.memory.getObject(self).name; + let ret = swift.memory.getObject(self)["name"]; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -213,7 +213,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_Greeter_age_get"] = function bjs_Greeter_age_get(self) { try { - let ret = swift.memory.getObject(self).age; + let ret = swift.memory.getObject(self)["age"]; return ret; } catch (error) { setException(error); @@ -224,7 +224,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self).name = newValueObject; + swift.memory.getObject(self)["name"] = newValueObject; } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift index 6fa9b6d8c..73cd7cf45 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift @@ -18,6 +18,16 @@ @JSClass struct WeirdNaming { @JSGetter var normalProperty: String @JSSetter func setNormalProperty(_ value: String) throws (JSException) + @JSGetter(jsName: "property-with-dashes") var property_with_dashes: Double + @JSSetter(jsName: "property-with-dashes") func setProperty_with_dashes(_ value: Double) throws (JSException) + @JSGetter(jsName: "123invalidStart") var _123invalidStart: Bool + @JSSetter(jsName: "123invalidStart") func set_123invalidStart(_ value: Bool) throws (JSException) + @JSGetter(jsName: "property with spaces") var property_with_spaces: String + @JSSetter(jsName: "property with spaces") func setProperty_with_spaces(_ value: String) throws (JSException) + @JSGetter(jsName: "@specialChar") var _specialChar: Double + @JSSetter(jsName: "@specialChar") func set_specialChar(_ value: Double) throws (JSException) + @JSGetter var constructor: String + @JSSetter func setConstructor(_ value: String) throws (JSException) @JSGetter var `for`: String @JSSetter func setFor(_ value: String) throws (JSException) @JSGetter var `Any`: String diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift index ba9e925e0..2922a23f5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift @@ -79,6 +79,51 @@ fileprivate func bjs_WeirdNaming_normalProperty_get(_ self: Int32) -> Int32 { } #endif +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_property_with_dashes_get") +fileprivate func bjs_WeirdNaming_property_with_dashes_get(_ self: Int32) -> Float64 +#else +fileprivate func bjs_WeirdNaming_property_with_dashes_get(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming__123invalidStart_get") +fileprivate func bjs_WeirdNaming__123invalidStart_get(_ self: Int32) -> Int32 +#else +fileprivate func bjs_WeirdNaming__123invalidStart_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_property_with_spaces_get") +fileprivate func bjs_WeirdNaming_property_with_spaces_get(_ self: Int32) -> Int32 +#else +fileprivate func bjs_WeirdNaming_property_with_spaces_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming__specialChar_get") +fileprivate func bjs_WeirdNaming__specialChar_get(_ self: Int32) -> Float64 +#else +fileprivate func bjs_WeirdNaming__specialChar_get(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_constructor_get") +fileprivate func bjs_WeirdNaming_constructor_get(_ self: Int32) -> Int32 +#else +fileprivate func bjs_WeirdNaming_constructor_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_for_get") fileprivate func bjs_WeirdNaming_for_get(_ self: Int32) -> Int32 @@ -106,6 +151,51 @@ fileprivate func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValue: I } #endif +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_property_with_dashes_set") +fileprivate func bjs_WeirdNaming_property_with_dashes_set(_ self: Int32, _ newValue: Float64) -> Void +#else +fileprivate func bjs_WeirdNaming_property_with_dashes_set(_ self: Int32, _ newValue: Float64) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming__123invalidStart_set") +fileprivate func bjs_WeirdNaming__123invalidStart_set(_ self: Int32, _ newValue: Int32) -> Void +#else +fileprivate func bjs_WeirdNaming__123invalidStart_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_property_with_spaces_set") +fileprivate func bjs_WeirdNaming_property_with_spaces_set(_ self: Int32, _ newValue: Int32) -> Void +#else +fileprivate func bjs_WeirdNaming_property_with_spaces_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming__specialChar_set") +fileprivate func bjs_WeirdNaming__specialChar_set(_ self: Int32, _ newValue: Float64) -> Void +#else +fileprivate func bjs_WeirdNaming__specialChar_set(_ self: Int32, _ newValue: Float64) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_constructor_set") +fileprivate func bjs_WeirdNaming_constructor_set(_ self: Int32, _ newValue: Int32) -> Void +#else +fileprivate func bjs_WeirdNaming_constructor_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_for_set") fileprivate func bjs_WeirdNaming_for_set(_ self: Int32, _ newValue: Int32) -> Void @@ -142,6 +232,51 @@ func _$WeirdNaming_normalProperty_get(_ self: JSObject) throws(JSException) -> S return String.bridgeJSLiftReturn(ret) } +func _$WeirdNaming_property_with_dashes_get(_ self: JSObject) throws(JSException) -> Double { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_WeirdNaming_property_with_dashes_get(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} + +func _$WeirdNaming__123invalidStart_get(_ self: JSObject) throws(JSException) -> Bool { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_WeirdNaming__123invalidStart_get(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return Bool.bridgeJSLiftReturn(ret) +} + +func _$WeirdNaming_property_with_spaces_get(_ self: JSObject) throws(JSException) -> String { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_WeirdNaming_property_with_spaces_get(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) +} + +func _$WeirdNaming__specialChar_get(_ self: JSObject) throws(JSException) -> Double { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_WeirdNaming__specialChar_get(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} + +func _$WeirdNaming_constructor_get(_ self: JSObject) throws(JSException) -> String { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_WeirdNaming_constructor_get(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) +} + func _$WeirdNaming_for_get(_ self: JSObject) throws(JSException) -> String { let selfValue = self.bridgeJSLowerParameter() let ret = bjs_WeirdNaming_for_get(selfValue) @@ -169,6 +304,51 @@ func _$WeirdNaming_normalProperty_set(_ self: JSObject, _ newValue: String) thro } } +func _$WeirdNaming_property_with_dashes_set(_ self: JSObject, _ newValue: Double) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + let newValueValue = newValue.bridgeJSLowerParameter() + bjs_WeirdNaming_property_with_dashes_set(selfValue, newValueValue) + if let error = _swift_js_take_exception() { + throw error + } +} + +func _$WeirdNaming__123invalidStart_set(_ self: JSObject, _ newValue: Bool) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + let newValueValue = newValue.bridgeJSLowerParameter() + bjs_WeirdNaming__123invalidStart_set(selfValue, newValueValue) + if let error = _swift_js_take_exception() { + throw error + } +} + +func _$WeirdNaming_property_with_spaces_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + let newValueValue = newValue.bridgeJSLowerParameter() + bjs_WeirdNaming_property_with_spaces_set(selfValue, newValueValue) + if let error = _swift_js_take_exception() { + throw error + } +} + +func _$WeirdNaming__specialChar_set(_ self: JSObject, _ newValue: Double) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + let newValueValue = newValue.bridgeJSLowerParameter() + bjs_WeirdNaming__specialChar_set(selfValue, newValueValue) + if let error = _swift_js_take_exception() { + throw error + } +} + +func _$WeirdNaming_constructor_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + let newValueValue = newValue.bridgeJSLowerParameter() + bjs_WeirdNaming_constructor_set(selfValue, newValueValue) + if let error = _swift_js_take_exception() { + throw error + } +} + func _$WeirdNaming_for_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { let selfValue = self.bridgeJSLowerParameter() let newValueValue = newValue.bridgeJSLowerParameter() diff --git a/Sources/JavaScriptKit/Macros.swift b/Sources/JavaScriptKit/Macros.swift index 8cc3dbc64..5a0571ab5 100644 --- a/Sources/JavaScriptKit/Macros.swift +++ b/Sources/JavaScriptKit/Macros.swift @@ -112,6 +112,9 @@ public macro JS(namespace: String? = nil, enumStyle: JSEnumStyle = .const) = Bui /// /// This macro is used by BridgeJS-generated Swift declarations. /// +/// - Parameter jsName: An optional string that specifies the name of the JavaScript property to read from. +/// If not provided, the Swift property name is used. +/// /// Example: /// /// ```swift @@ -125,7 +128,7 @@ public macro JS(namespace: String? = nil, enumStyle: JSEnumStyle = .const) = Bui /// ``` @attached(accessor) @_spi(Experimental) -public macro JSGetter() = +public macro JSGetter(jsName: String? = nil) = #externalMacro(module: "BridgeJSMacros", type: "JSGetterMacro") /// A macro that generates a Swift function body that writes a value to JavaScript. From 790605023d5dea0d2f4890b340d5a1aef05696a9 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Jan 2026 08:24:42 +0900 Subject: [PATCH 2/6] Adjusted imported-property JS glue to prefer dot access when the property name is a normal identifier, falling back to bracket access only when needed. - Implementation: `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` - Updated snapshots (including `Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js:215`) - `swift test --package-path ./Plugins/BridgeJS` passes --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 19 ++++++--- .../InvalidPropertyNames.Import.js | 18 ++++---- .../MultipleImportedTypes.Import.js | 10 ++--- .../BridgeJSLinkTests/Protocol.Export.js | 42 +++++++++---------- .../TS2SkeletonLike.Import.js | 4 +- .../TypeScriptClass.Import.js | 6 +-- 6 files changed, 53 insertions(+), 46 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index bcb19320b..4dce2f0ef 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -2168,9 +2168,8 @@ extension BridgeJSLink { } func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? { - let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(name) - let accessExpr = - "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)[\"\(escapedName)\"]" + let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)" + let accessExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) if context == .exportSwift, returnType.usesSideChannelForOptionalReturn() { guard case .optional(let wrappedType) = returnType else { fatalError("usesSideChannelForOptionalReturn returned true for non-optional type") @@ -2194,9 +2193,9 @@ extension BridgeJSLink { } func callPropertySetter(name: String, returnType: BridgeType) { - let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(name) - let call = - "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)[\"\(escapedName)\"] = \(parameterForwardings.joined(separator: ", "))" + let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)" + let accessExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) + let call = "\(accessExpr) = \(parameterForwardings.joined(separator: ", "))" body.write("\(call);") } @@ -2236,6 +2235,14 @@ extension BridgeJSLink { assert(loweredValues.count <= 1, "Lowering fragment should produce at most one value") return loweredValues.first } + + private static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String { + if propertyName.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil { + return "\(objectExpr).\(propertyName)" + } + let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(propertyName) + return "\(objectExpr)[\"\(escapedName)\"]" + } } class ImportObjectBuilder { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js index 585b276de..022b14e0e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -212,7 +212,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_ArrayBufferLike_byteLength_get"] = function bjs_ArrayBufferLike_byteLength_get(self) { try { - let ret = swift.memory.getObject(self)["byteLength"]; + let ret = swift.memory.getObject(self).byteLength; return ret; } catch (error) { setException(error); @@ -230,7 +230,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_normalProperty_get"] = function bjs_WeirdNaming_normalProperty_get(self) { try { - let ret = swift.memory.getObject(self)["normalProperty"]; + let ret = swift.memory.getObject(self).normalProperty; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -275,7 +275,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_constructor_get"] = function bjs_WeirdNaming_constructor_get(self) { try { - let ret = swift.memory.getObject(self)["constructor"]; + let ret = swift.memory.getObject(self).constructor; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -284,7 +284,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_for_get"] = function bjs_WeirdNaming_for_get(self) { try { - let ret = swift.memory.getObject(self)["for"]; + let ret = swift.memory.getObject(self).for; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -293,7 +293,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_WeirdNaming_Any_get"] = function bjs_WeirdNaming_Any_get(self) { try { - let ret = swift.memory.getObject(self)["Any"]; + let ret = swift.memory.getObject(self).Any; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -304,7 +304,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self)["normalProperty"] = newValueObject; + swift.memory.getObject(self).normalProperty = newValueObject; } catch (error) { setException(error); } @@ -343,7 +343,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self)["constructor"] = newValueObject; + swift.memory.getObject(self).constructor = newValueObject; } catch (error) { setException(error); } @@ -352,7 +352,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self)["for"] = newValueObject; + swift.memory.getObject(self).for = newValueObject; } catch (error) { setException(error); } @@ -361,7 +361,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self)["Any"] = newValueObject; + swift.memory.getObject(self).Any = newValueObject; } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 59457a1a2..40a3f4e3f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -223,7 +223,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_DatabaseConnection_isConnected_get"] = function bjs_DatabaseConnection_isConnected_get(self) { try { - let ret = swift.memory.getObject(self)["isConnected"]; + let ret = swift.memory.getObject(self).isConnected; return ret ? 1 : 0; } catch (error) { setException(error); @@ -232,7 +232,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_DatabaseConnection_connectionTimeout_get"] = function bjs_DatabaseConnection_connectionTimeout_get(self) { try { - let ret = swift.memory.getObject(self)["connectionTimeout"]; + let ret = swift.memory.getObject(self).connectionTimeout; return ret; } catch (error) { setException(error); @@ -241,7 +241,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_DatabaseConnection_connectionTimeout_set"] = function bjs_DatabaseConnection_connectionTimeout_set(self, newValue) { try { - swift.memory.getObject(self)["connectionTimeout"] = newValue; + swift.memory.getObject(self).connectionTimeout = newValue; } catch (error) { setException(error); } @@ -268,7 +268,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_Logger_level_get"] = function bjs_Logger_level_get(self) { try { - let ret = swift.memory.getObject(self)["level"]; + let ret = swift.memory.getObject(self).level; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -295,7 +295,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_ConfigManager_configPath_get"] = function bjs_ConfigManager_configPath_get(self) { try { - let ret = swift.memory.getObject(self)["configPath"]; + let ret = swift.memory.getObject(self).configPath; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js index ee2212bbe..d6b9a4f01 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js @@ -269,7 +269,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_MyViewControllerDelegate_eventCount_get"] = function bjs_MyViewControllerDelegate_eventCount_get(self) { try { - let ret = swift.memory.getObject(self)["eventCount"]; + let ret = swift.memory.getObject(self).eventCount; return ret; } catch (error) { setException(error); @@ -278,14 +278,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_eventCount_set"] = function bjs_MyViewControllerDelegate_eventCount_set(self, value) { try { - swift.memory.getObject(self)["eventCount"] = value; + swift.memory.getObject(self).eventCount = value; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_delegateName_get"] = function bjs_MyViewControllerDelegate_delegateName_get(self) { try { - let ret = swift.memory.getObject(self)["delegateName"]; + let ret = swift.memory.getObject(self).delegateName; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -294,7 +294,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_optionalName_get"] = function bjs_MyViewControllerDelegate_optionalName_get(self) { try { - let ret = swift.memory.getObject(self)["optionalName"]; + let ret = swift.memory.getObject(self).optionalName; tmpRetString = ret; } catch (error) { setException(error); @@ -307,14 +307,14 @@ export async function createInstantiator(options, swift) { obj = swift.memory.getObject(valueWrappedValue); swift.memory.release(valueWrappedValue); } - swift.memory.getObject(self)["optionalName"] = valueIsSome ? obj : null; + swift.memory.getObject(self).optionalName = valueIsSome ? obj : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_optionalRawEnum_get"] = function bjs_MyViewControllerDelegate_optionalRawEnum_get(self) { try { - let ret = swift.memory.getObject(self)["optionalRawEnum"]; + let ret = swift.memory.getObject(self).optionalRawEnum; tmpRetString = ret; } catch (error) { setException(error); @@ -327,14 +327,14 @@ export async function createInstantiator(options, swift) { obj = swift.memory.getObject(valueWrappedValue); swift.memory.release(valueWrappedValue); } - swift.memory.getObject(self)["optionalRawEnum"] = valueIsSome ? obj : null; + swift.memory.getObject(self).optionalRawEnum = valueIsSome ? obj : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_rawStringEnum_get"] = function bjs_MyViewControllerDelegate_rawStringEnum_get(self) { try { - let ret = swift.memory.getObject(self)["rawStringEnum"]; + let ret = swift.memory.getObject(self).rawStringEnum; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -345,14 +345,14 @@ export async function createInstantiator(options, swift) { try { const valueObject = swift.memory.getObject(value); swift.memory.release(value); - swift.memory.getObject(self)["rawStringEnum"] = valueObject; + swift.memory.getObject(self).rawStringEnum = valueObject; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_result_get"] = function bjs_MyViewControllerDelegate_result_get(self) { try { - let ret = swift.memory.getObject(self)["result"]; + let ret = swift.memory.getObject(self).result; const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); return caseId; } catch (error) { @@ -362,14 +362,14 @@ export async function createInstantiator(options, swift) { TestModule["bjs_MyViewControllerDelegate_result_set"] = function bjs_MyViewControllerDelegate_result_set(self, value) { try { const enumValue = enumHelpers.Result.raise(value, tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s); - swift.memory.getObject(self)["result"] = enumValue; + swift.memory.getObject(self).result = enumValue; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_optionalResult_get"] = function bjs_MyViewControllerDelegate_optionalResult_get(self) { try { - let ret = swift.memory.getObject(self)["optionalResult"]; + let ret = swift.memory.getObject(self).optionalResult; const isSome = ret != null; if (isSome) { const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); @@ -387,14 +387,14 @@ export async function createInstantiator(options, swift) { if (valueIsSome) { enumValue = enumHelpers.Result.raise(valueWrappedValue, tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s); } - swift.memory.getObject(self)["optionalResult"] = valueIsSome ? enumValue : null; + swift.memory.getObject(self).optionalResult = valueIsSome ? enumValue : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_direction_get"] = function bjs_MyViewControllerDelegate_direction_get(self) { try { - let ret = swift.memory.getObject(self)["direction"]; + let ret = swift.memory.getObject(self).direction; return ret; } catch (error) { setException(error); @@ -403,14 +403,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_direction_set"] = function bjs_MyViewControllerDelegate_direction_set(self, value) { try { - swift.memory.getObject(self)["direction"] = value; + swift.memory.getObject(self).direction = value; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_directionOptional_get"] = function bjs_MyViewControllerDelegate_directionOptional_get(self) { try { - let ret = swift.memory.getObject(self)["directionOptional"]; + let ret = swift.memory.getObject(self).directionOptional; const isSome = ret != null; return isSome ? (ret | 0) : -1; } catch (error) { @@ -419,14 +419,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_directionOptional_set"] = function bjs_MyViewControllerDelegate_directionOptional_set(self, valueIsSome, valueWrappedValue) { try { - swift.memory.getObject(self)["directionOptional"] = valueIsSome ? valueWrappedValue : null; + swift.memory.getObject(self).directionOptional = valueIsSome ? valueWrappedValue : null; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_priority_get"] = function bjs_MyViewControllerDelegate_priority_get(self) { try { - let ret = swift.memory.getObject(self)["priority"]; + let ret = swift.memory.getObject(self).priority; return ret; } catch (error) { setException(error); @@ -435,14 +435,14 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_priority_set"] = function bjs_MyViewControllerDelegate_priority_set(self, value) { try { - swift.memory.getObject(self)["priority"] = value; + swift.memory.getObject(self).priority = value; } catch (error) { setException(error); } } TestModule["bjs_MyViewControllerDelegate_priorityOptional_get"] = function bjs_MyViewControllerDelegate_priorityOptional_get(self) { try { - let ret = swift.memory.getObject(self)["priorityOptional"]; + let ret = swift.memory.getObject(self).priorityOptional; tmpRetOptionalInt = ret; } catch (error) { setException(error); @@ -450,7 +450,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_priorityOptional_set"] = function bjs_MyViewControllerDelegate_priorityOptional_set(self, valueIsSome, valueWrappedValue) { try { - swift.memory.getObject(self)["priorityOptional"] = valueIsSome ? valueWrappedValue : null; + swift.memory.getObject(self).priorityOptional = valueIsSome ? valueWrappedValue : null; } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 7c11cb01c..2bbae4fec 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -214,7 +214,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_TypeScriptProcessor_version_get"] = function bjs_TypeScriptProcessor_version_get(self) { try { - let ret = swift.memory.getObject(self)["version"]; + let ret = swift.memory.getObject(self).version; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -245,7 +245,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_CodeGenerator_outputFormat_get"] = function bjs_CodeGenerator_outputFormat_get(self) { try { - let ret = swift.memory.getObject(self)["outputFormat"]; + let ret = swift.memory.getObject(self).outputFormat; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 0209bd3fd..6c44fd008 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -204,7 +204,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_Greeter_name_get"] = function bjs_Greeter_name_get(self) { try { - let ret = swift.memory.getObject(self)["name"]; + let ret = swift.memory.getObject(self).name; tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { @@ -213,7 +213,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_Greeter_age_get"] = function bjs_Greeter_age_get(self) { try { - let ret = swift.memory.getObject(self)["age"]; + let ret = swift.memory.getObject(self).age; return ret; } catch (error) { setException(error); @@ -224,7 +224,7 @@ export async function createInstantiator(options, swift) { try { const newValueObject = swift.memory.getObject(newValue); swift.memory.release(newValue); - swift.memory.getObject(self)["name"] = newValueObject; + swift.memory.getObject(self).name = newValueObject; } catch (error) { setException(error); } From 0497d2611205389840156fe1d189b2cf1ca39cf0 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Jan 2026 08:39:17 +0900 Subject: [PATCH 3/6] Added `jsName` support to `@JSFunction` and `@JSClass`, end-to-end. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated macro APIs: `Sources/JavaScriptKit/Macros.swift` - Plumbed `jsName` through the imported skeleton + Swift parser: `Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift`, `Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift` - Updated JS glue + generated `.d.ts` to use the JS names (dot access when possible, bracket access when needed): `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` - Updated TS→Swift generator to emit `@JSClass(jsName: ...)` / `@JSFunction(jsName: ...)` when it has to sanitize names: `Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js` - Added coverage for a renamed class + quoted method name: `Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts` (snapshots refreshed; `swift test --package-path ./Plugins/BridgeJS` passes) --- .../BridgeJSCore/SwiftToSkeleton.swift | 44 +++++++--- .../Sources/BridgeJSLink/BridgeJSLink.swift | 26 +++--- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 15 +++- .../TS2Swift/JavaScript/src/processor.js | 81 +++++++++++++++---- .../Inputs/InvalidPropertyNames.d.ts | 7 ++ .../InvalidPropertyNames.Import.d.ts | 10 +++ .../InvalidPropertyNames.Import.js | 31 +++++++ .../InvalidPropertyNames.Macros.swift | 11 +++ .../ImportTSTests/InvalidPropertyNames.swift | 68 ++++++++++++++++ Sources/JavaScriptKit/Macros.swift | 7 +- 10 files changed, 260 insertions(+), 40 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index f09a9140a..b1dced37e 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1743,6 +1743,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { // Current type being collected (when in jsClassBody state) private struct CurrentType { let name: String + let jsName: String? var constructor: ImportedConstructorSkeleton? var methods: [ImportedFunctionSkeleton] var getters: [ImportedGetterSkeleton] @@ -1758,6 +1759,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { hasAttribute(attributes, name: "JSFunction") } + static func firstJSFunctionAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? { + attributes?.first { attribute in + attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSFunction" + }?.as(AttributeSyntax.self) + } + static func hasJSGetterAttribute(_ attributes: AttributeListSyntax?) -> Bool { hasAttribute(attributes, name: "JSGetter") } @@ -1782,6 +1789,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { hasAttribute(attributes, name: "JSClass") } + static func firstJSClassAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? { + attributes?.first { attribute in + attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSClass" + }?.as(AttributeSyntax.self) + } + static func hasAttribute(_ attributes: AttributeListSyntax?, name: String) -> Bool { guard let attributes else { return false } return attributes.contains { attribute in @@ -1929,7 +1942,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func enterJSClass(_ typeName: String) { stateStack.append(.jsClassBody(name: typeName)) - currentType = CurrentType(name: typeName, constructor: nil, methods: [], getters: [], setters: []) + currentType = CurrentType(name: typeName, jsName: nil, constructor: nil, methods: [], getters: [], setters: []) + } + + private func enterJSClass(_ typeName: String, jsName: String?) { + stateStack.append(.jsClassBody(name: typeName)) + currentType = CurrentType(name: typeName, jsName: jsName, constructor: nil, methods: [], getters: [], setters: []) } private func exitJSClass() { @@ -1937,6 +1955,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { importedTypes.append( ImportedTypeSkeleton( name: type.name, + jsName: type.jsName, constructor: type.constructor, methods: type.methods, getters: type.getters, @@ -1951,7 +1970,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { if AttributeChecker.hasJSClassAttribute(node.attributes) { - enterJSClass(node.name.text) + let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName) + enterJSClass(node.name.text, jsName: jsName) } return .visitChildren } @@ -1964,7 +1984,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { if AttributeChecker.hasJSClassAttribute(node.attributes) { - enterJSClass(node.name.text) + let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName) + enterJSClass(node.name.text, jsName: jsName) } return .visitChildren } @@ -2002,8 +2023,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { } private func handleTopLevelFunction(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - if AttributeChecker.hasJSFunctionAttribute(node.attributes), - let function = parseFunction(node, enclosingTypeName: nil, isStaticMember: true) + if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes), + let function = parseFunction(jsFunction, node, enclosingTypeName: nil, isStaticMember: true) { importedFunctions.append(function) return .skipChildren @@ -2027,13 +2048,13 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { isStaticMember: Bool, type: inout CurrentType ) -> Bool { - if AttributeChecker.hasJSFunctionAttribute(node.attributes) { + if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes) { if isStaticMember { - parseFunction(node, enclosingTypeName: typeName, isStaticMember: true).map { + parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: true).map { importedFunctions.append($0) } } else { - parseFunction(node, enclosingTypeName: typeName, isStaticMember: false).map { + parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: false).map { type.methods.append($0) } } @@ -2130,8 +2151,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func collectStaticMembers(in members: MemberBlockItemListSyntax, typeName: String) { for member in members { if let function = member.decl.as(FunctionDeclSyntax.self) { - if AttributeChecker.hasJSFunctionAttribute(function.attributes), - let parsed = parseFunction(function, enclosingTypeName: typeName, isStaticMember: true) + if let jsFunction = AttributeChecker.firstJSFunctionAttribute(function.attributes), + let parsed = parseFunction(jsFunction, function, enclosingTypeName: typeName, isStaticMember: true) { importedFunctions.append(parsed) } else if AttributeChecker.hasJSSetterAttribute(function.attributes) { @@ -2175,6 +2196,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { } private func parseFunction( + _ jsFunction: AttributeSyntax, _ node: FunctionDeclSyntax, enclosingTypeName: String?, isStaticMember: Bool @@ -2185,6 +2207,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { } let baseName = SwiftToSkeleton.normalizeIdentifier(node.name.text) + let jsName = AttributeChecker.extractJSName(from: jsFunction) let name: String if isStaticMember, let enclosingTypeName { name = "\(enclosingTypeName)_\(baseName)" @@ -2204,6 +2227,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { } return ImportedFunctionSkeleton( name: name, + jsName: jsName, parameters: parameters, returnType: returnType, documentation: nil diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 4dce2f0ef..c42437dbe 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -1194,8 +1194,9 @@ public struct BridgeJSLink { // Add methods for method in type.methods { + let methodName = method.jsName ?? method.name let methodSignature = - "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" + "\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" printer.write(methodSignature) } @@ -2127,7 +2128,8 @@ extension BridgeJSLink { } func call(name: String, returnType: BridgeType) throws -> String? { - return try self.call(calleeExpr: "imports.\(name)", returnType: returnType) + let calleeExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: name) + return try self.call(calleeExpr: calleeExpr, returnType: returnType) } private func call(calleeExpr: String, returnType: BridgeType) throws -> String? { @@ -2153,16 +2155,19 @@ extension BridgeJSLink { ) } - func callConstructor(name: String) throws -> String? { - let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" - let type: BridgeType = .jsObject(name) + func callConstructor(jsName: String, swiftTypeName: String) throws -> String? { + let ctorExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: jsName) + let call = "new \(ctorExpr)(\(parameterForwardings.joined(separator: ", ")))" + let type: BridgeType = .jsObject(swiftTypeName) let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type, context: context) return try lowerReturnValue(returnType: type, returnExpr: call, loweringFragment: loweringFragment) } func callMethod(name: String, returnType: BridgeType) throws -> String? { + let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)" + let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) return try call( - calleeExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)", + calleeExpr: calleeExpr, returnType: returnType ) } @@ -2957,7 +2962,8 @@ extension BridgeJSLink { for param in function.parameters { try thunkBuilder.liftParameter(param: param) } - let returnExpr = try thunkBuilder.call(name: function.name, returnType: function.returnType) + let jsName = function.jsName ?? function.name + let returnExpr = try thunkBuilder.call(name: jsName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName(context: nil), returnExpr: returnExpr, @@ -2966,7 +2972,7 @@ extension BridgeJSLink { let effects = Effects(isAsync: false, isThrows: false) importObjectBuilder.appendDts( [ - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));" + "\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));" ] ) importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines) @@ -3049,7 +3055,7 @@ extension BridgeJSLink { try thunkBuilder.liftParameter(param: param) } let returnType = BridgeType.jsObject(type.name) - let returnExpr = try thunkBuilder.callConstructor(name: type.name) + let returnExpr = try thunkBuilder.callConstructor(jsName: type.jsName ?? type.name, swiftTypeName: type.name) let abiName = constructor.abiName(context: type) let funcLines = thunkBuilder.renderFunction( name: abiName, @@ -3111,7 +3117,7 @@ extension BridgeJSLink { for param in method.parameters { try thunkBuilder.liftParameter(param: param) } - let returnExpr = try thunkBuilder.callMethod(name: method.name, returnType: method.returnType) + let returnExpr = try thunkBuilder.callMethod(name: method.jsName ?? method.name, returnType: method.returnType) let funcLines = thunkBuilder.renderFunction( name: method.abiName(context: context), returnExpr: returnExpr, diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 7040d607d..3baebe6da 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -590,12 +590,21 @@ public struct ExportedSkeleton: Codable { public struct ImportedFunctionSkeleton: Codable { public let name: String + /// The JavaScript function/method name to call, if different from `name`. + public let jsName: String? public let parameters: [Parameter] public let returnType: BridgeType public let documentation: String? - public init(name: String, parameters: [Parameter], returnType: BridgeType, documentation: String? = nil) { + public init( + name: String, + jsName: String? = nil, + parameters: [Parameter], + returnType: BridgeType, + documentation: String? = nil + ) { self.name = name + self.jsName = jsName self.parameters = parameters self.returnType = returnType self.documentation = documentation @@ -704,6 +713,8 @@ public struct ImportedSetterSkeleton: Codable { public struct ImportedTypeSkeleton: Codable { public let name: String + /// The JavaScript constructor name to use for `init(...)`, if different from `name`. + public let jsName: String? public let constructor: ImportedConstructorSkeleton? public let methods: [ImportedFunctionSkeleton] public let getters: [ImportedGetterSkeleton] @@ -712,6 +723,7 @@ public struct ImportedTypeSkeleton: Codable { public init( name: String, + jsName: String? = nil, constructor: ImportedConstructorSkeleton? = nil, methods: [ImportedFunctionSkeleton], getters: [ImportedGetterSkeleton] = [], @@ -719,6 +731,7 @@ public struct ImportedTypeSkeleton: Codable { documentation: String? = nil ) { self.name = name + self.jsName = jsName self.constructor = constructor self.methods = methods self.getters = getters diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js index 87663cb88..615cffa25 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js @@ -60,6 +60,33 @@ export class TypeProcessor { /** @type {Set} */ this.visitedDeclarationKeys = new Set(); + + /** @type {Map} */ + this.swiftTypeNameByJSTypeName = new Map(); + } + + /** + * Convert a TypeScript type name to a valid Swift type identifier. + * @param {string} jsTypeName + * @returns {string} + * @private + */ + swiftTypeName(jsTypeName) { + const cached = this.swiftTypeNameByJSTypeName.get(jsTypeName); + if (cached) return cached; + const swiftName = isValidSwiftDeclName(jsTypeName) ? jsTypeName : makeValidSwiftIdentifier(jsTypeName, { emptyFallback: "_" }); + this.swiftTypeNameByJSTypeName.set(jsTypeName, swiftName); + return swiftName; + } + + /** + * Render a Swift type identifier from a TypeScript type name. + * @param {string} jsTypeName + * @returns {string} + * @private + */ + renderTypeIdentifier(jsTypeName) { + return this.renderIdentifier(this.swiftTypeName(jsTypeName)); } /** @@ -292,7 +319,7 @@ export class TypeProcessor { canBeStringEnum = false; canBeIntEnum = false; } - const swiftEnumName = this.renderIdentifier(enumName); + const swiftEnumName = this.renderTypeIdentifier(enumName); const dedupeNames = (items) => { const seen = new Map(); return items.map(item => { @@ -341,10 +368,10 @@ export class TypeProcessor { */ visitFunctionDeclaration(node) { if (!node.name) return; - const name = node.name.getText(); - if (!isValidSwiftDeclName(name)) { - return; - } + const jsName = node.name.text; + const swiftName = this.swiftTypeName(jsName); + const escapedJSName = jsName.replaceAll("\\", "\\\\").replaceAll("\"", "\\\\\""); + const annotation = jsName !== swiftName ? `@JSFunction(jsName: "${escapedJSName}")` : "@JSFunction"; const signature = this.checker.getSignatureFromDeclaration(node); if (!signature) return; @@ -352,9 +379,9 @@ export class TypeProcessor { const params = this.renderParameters(signature.getParameters(), node); const returnType = this.visitType(signature.getReturnType(), node); const effects = this.renderEffects({ isAsync: false }); - const swiftName = this.renderIdentifier(name); + const swiftFuncName = this.renderIdentifier(swiftName); - this.swiftLines.push(`@JSFunction func ${swiftName}(${params}) ${effects} -> ${returnType}`); + this.swiftLines.push(`${annotation} func ${swiftFuncName}(${params}) ${effects} -> ${returnType}`); this.swiftLines.push(""); } @@ -433,8 +460,12 @@ export class TypeProcessor { visitClassDecl(node) { if (!node.name) return; - const className = this.renderIdentifier(node.name.text); - this.swiftLines.push(`@JSClass struct ${className} {`); + const jsName = node.name.text; + const swiftName = this.swiftTypeName(jsName); + const escapedJSName = jsName.replaceAll("\\", "\\\\").replaceAll("\"", "\\\\\""); + const annotation = jsName !== swiftName ? `@JSClass(jsName: "${escapedJSName}")` : "@JSClass"; + const className = this.renderIdentifier(swiftName); + this.swiftLines.push(`${annotation} struct ${className} {`); // Process members in declaration order for (const member of node.members) { @@ -491,8 +522,11 @@ export class TypeProcessor { if (this.emittedStructuredTypeNames.has(name)) return; this.emittedStructuredTypeNames.add(name); - const typeName = this.renderIdentifier(name); - this.swiftLines.push(`@JSClass struct ${typeName} {`); + const swiftName = this.swiftTypeName(name); + const escapedJSName = name.replaceAll("\\", "\\\\").replaceAll("\"", "\\\\\""); + const annotation = name !== swiftName ? `@JSClass(jsName: "${escapedJSName}")` : "@JSClass"; + const typeName = this.renderIdentifier(swiftName); + this.swiftLines.push(`${annotation} struct ${typeName} {`); // Collect all declarations with their positions to preserve order /** @type {Array<{ decl: ts.Node, symbol: ts.Symbol, position: number }>} */ @@ -578,7 +612,7 @@ export class TypeProcessor { if (symbol && (symbol.flags & ts.SymbolFlags.Enum) !== 0) { const typeName = symbol.name; this.seenTypes.set(type, node); - return this.renderIdentifier(typeName); + return this.renderTypeIdentifier(typeName); } if (this.checker.isArrayType(type) || this.checker.isTupleType(type) || type.getCallSignatures().length > 0) { @@ -598,7 +632,7 @@ export class TypeProcessor { return "JSObject"; } this.seenTypes.set(type, node); - return this.renderIdentifier(typeName); + return this.renderTypeIdentifier(typeName); } const swiftType = convert(type); this.processedTypes.set(type, swiftType); @@ -659,8 +693,21 @@ export class TypeProcessor { */ renderMethod(node) { if (!node.name) return; - const name = node.name.getText(); - if (!isValidSwiftDeclName(name)) return; + /** @type {string | null} */ + let jsName = null; + if (ts.isIdentifier(node.name)) { + jsName = node.name.text; + } else if (ts.isStringLiteral(node.name) || ts.isNumericLiteral(node.name)) { + jsName = node.name.text; + } else { + // Computed property names like `[Symbol.iterator]` are not supported yet. + return; + } + + const swiftName = this.swiftTypeName(jsName); + const needsJSNameField = jsName !== swiftName; + const escapedJSName = jsName.replaceAll("\\", "\\\\").replaceAll("\"", "\\\\\""); + const annotation = needsJSNameField ? `@JSFunction(jsName: "${escapedJSName}")` : "@JSFunction"; const signature = this.checker.getSignatureFromDeclaration(node); if (!signature) return; @@ -668,9 +715,9 @@ export class TypeProcessor { const params = this.renderParameters(signature.getParameters(), node); const returnType = this.visitType(signature.getReturnType(), node); const effects = this.renderEffects({ isAsync: false }); - const swiftName = this.renderIdentifier(name); + const swiftMethodName = this.renderIdentifier(swiftName); - this.swiftLines.push(` @JSFunction func ${swiftName}(${params}) ${effects} -> ${returnType}`); + this.swiftLines.push(` ${annotation} func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`); } /** diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts index d21f3c207..b9d3722b8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts @@ -21,3 +21,10 @@ interface WeirdNaming { export function createArrayBuffer(): ArrayBufferLike; export function createWeirdObject(): WeirdNaming; + +export class $Weird { + constructor(); + "method-with-dashes"(): void; +} + +export function createWeirdClass(): $Weird; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts index bcd4c7392..42d0d4123 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts @@ -10,6 +10,7 @@ export interface ArrayBufferLike { } export interface WeirdNaming { as(): void; + try(): void; normalProperty: string; "property-with-dashes": number; "123invalidStart": boolean; @@ -19,11 +20,20 @@ export interface WeirdNaming { for: string; Any: string; } +export interface _Weird { + "method-with-dashes"(): void; +} +export interface _Weird { +} export type Exports = { } export type Imports = { createArrayBuffer(): ArrayBufferLike; createWeirdObject(): WeirdNaming; + createWeirdClass(): _Weird; + _Weird: { + new(): _Weird; + } } export function createInstantiator(options: { imports: Imports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js index 022b14e0e..66cbe7937 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -210,6 +210,15 @@ export async function createInstantiator(options, swift) { return 0 } } + TestModule["bjs_createWeirdClass"] = function bjs_createWeirdClass() { + try { + let ret = imports.createWeirdClass(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } TestModule["bjs_ArrayBufferLike_byteLength_get"] = function bjs_ArrayBufferLike_byteLength_get(self) { try { let ret = swift.memory.getObject(self).byteLength; @@ -373,6 +382,28 @@ export async function createInstantiator(options, swift) { setException(error); } } + TestModule["bjs_WeirdNaming_try"] = function bjs_WeirdNaming_try(self) { + try { + swift.memory.getObject(self).try(); + } catch (error) { + setException(error); + } + } + TestModule["bjs__Weird_init"] = function bjs__Weird_init() { + try { + return swift.memory.retain(new imports.$Weird()); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs__Weird_method_with_dashes"] = function bjs__Weird_method_with_dashes(self) { + try { + swift.memory.getObject(self)["method-with-dashes"](); + } catch (error) { + setException(error); + } + } }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift index 73cd7cf45..bfd8589e4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift @@ -33,4 +33,15 @@ @JSGetter var `Any`: String @JSSetter(jsName: "Any") func setAny(_ value: String) throws (JSException) @JSFunction func `as`() throws (JSException) -> Void + @JSFunction func `try`() throws (JSException) -> Void +} + +@JSClass(jsName: "$Weird") struct _Weird { + @JSFunction init() throws (JSException) + @JSFunction(jsName: "method-with-dashes") func method_with_dashes() throws (JSException) -> Void +} + +@JSFunction func createWeirdClass() throws (JSException) -> _Weird + +@JSClass(jsName: "$Weird") struct _Weird { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift index 2922a23f5..0ef52d4d7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift @@ -32,6 +32,23 @@ func _$createWeirdObject() throws(JSException) -> WeirdNaming { return WeirdNaming.bridgeJSLiftReturn(ret) } +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_createWeirdClass") +fileprivate func bjs_createWeirdClass() -> Int32 +#else +fileprivate func bjs_createWeirdClass() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +func _$createWeirdClass() throws(JSException) -> _Weird { + let ret = bjs_createWeirdClass() + if let error = _swift_js_take_exception() { + throw error + } + return _Weird.bridgeJSLiftReturn(ret) +} + #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_ArrayBufferLike_byteLength_get") fileprivate func bjs_ArrayBufferLike_byteLength_get(_ self: Int32) -> Float64 @@ -223,6 +240,15 @@ fileprivate func bjs_WeirdNaming_as(_ self: Int32) -> Void { } #endif +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs_WeirdNaming_try") +fileprivate func bjs_WeirdNaming_try(_ self: Int32) -> Void +#else +fileprivate func bjs_WeirdNaming_try(_ self: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + func _$WeirdNaming_normalProperty_get(_ self: JSObject) throws(JSException) -> String { let selfValue = self.bridgeJSLowerParameter() let ret = bjs_WeirdNaming_normalProperty_get(selfValue) @@ -373,4 +399,46 @@ func _$WeirdNaming_as(_ self: JSObject) throws(JSException) -> Void { if let error = _swift_js_take_exception() { throw error } +} + +func _$WeirdNaming_try(_ self: JSObject) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + bjs_WeirdNaming_try(selfValue) + if let error = _swift_js_take_exception() { + throw error + } +} + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs__Weird_init") +fileprivate func bjs__Weird_init() -> Int32 +#else +fileprivate func bjs__Weird_init() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "Check", name: "bjs__Weird_method_with_dashes") +fileprivate func bjs__Weird_method_with_dashes(_ self: Int32) -> Void +#else +fileprivate func bjs__Weird_method_with_dashes(_ self: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif + +func _$_Weird_init() throws(JSException) -> JSObject { + let ret = bjs__Weird_init() + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$_Weird_method_with_dashes(_ self: JSObject) throws(JSException) -> Void { + let selfValue = self.bridgeJSLowerParameter() + bjs__Weird_method_with_dashes(selfValue) + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Sources/JavaScriptKit/Macros.swift b/Sources/JavaScriptKit/Macros.swift index 5a0571ab5..61797a58e 100644 --- a/Sources/JavaScriptKit/Macros.swift +++ b/Sources/JavaScriptKit/Macros.swift @@ -162,9 +162,12 @@ public macro JSSetter(jsName: String? = nil) = /// @JSFunction func greet() throws (JSException) -> String /// @JSFunction init(_ name: String) throws (JSException) /// ``` +/// +/// - Parameter jsName: An optional string that specifies the name of the JavaScript function or method to call. +/// If not provided, the Swift function name is used. @attached(body) @_spi(Experimental) -public macro JSFunction() = +public macro JSFunction(jsName: String? = nil) = #externalMacro(module: "BridgeJSMacros", type: "JSFunctionMacro") /// A macro that adds bridging members for a Swift type that represents a JavaScript class. @@ -187,5 +190,5 @@ public macro JSFunction() = @attached(member, names: arbitrary) @attached(extension, conformances: _JSBridgedClass) @_spi(Experimental) -public macro JSClass() = +public macro JSClass(jsName: String? = nil) = #externalMacro(module: "BridgeJSMacros", type: "JSClassMacro") From e3a10d727129f7bd5111cd9582cbcca387af2230 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Jan 2026 08:47:11 +0900 Subject: [PATCH 4/6] Fixed: TS2Swift was emitting class types twice (once via `visitClassDecl`, then again via `visitStructuredType` when the same type showed up in `seenTypes`), which caused the duplicate `_Weird` interface in `InvalidPropertyNames.Import.d.ts`. - Change: `Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js` now records emitted class names in `emittedStructuredTypeNames` so the structured-type pass skips them. - Snapshots refreshed; `swift test --package-path ./Plugins/BridgeJS` passes. --- Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js | 3 +++ .../BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts | 2 -- .../ImportTSTests/InvalidPropertyNames.Macros.swift | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js index 615cffa25..1992b49f2 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js @@ -461,6 +461,9 @@ export class TypeProcessor { if (!node.name) return; const jsName = node.name.text; + if (this.emittedStructuredTypeNames.has(jsName)) return; + this.emittedStructuredTypeNames.add(jsName); + const swiftName = this.swiftTypeName(jsName); const escapedJSName = jsName.replaceAll("\\", "\\\\").replaceAll("\"", "\\\\\""); const annotation = jsName !== swiftName ? `@JSClass(jsName: "${escapedJSName}")` : "@JSClass"; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts index 42d0d4123..2efd24317 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts @@ -23,8 +23,6 @@ export interface WeirdNaming { export interface _Weird { "method-with-dashes"(): void; } -export interface _Weird { -} export type Exports = { } export type Imports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift index bfd8589e4..43d6a3eb9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.Macros.swift @@ -42,6 +42,3 @@ } @JSFunction func createWeirdClass() throws (JSException) -> _Weird - -@JSClass(jsName: "$Weird") struct _Weird { -} From 64e7febac8e73a1d8b2ca05489ea170e983f9be7 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Jan 2026 09:02:59 +0900 Subject: [PATCH 5/6] Implemented runtime coverage for `jsName` on `@JSFunction` and `@JSClass`. - Added new import API + XCTest: `Tests/BridgeJSRuntimeTests/bridge-js.d.ts`, `Tests/BridgeJSRuntimeTests/ImportAPITests.swift` - Wired JS-side implementations: `Tests/prelude.mjs` - Regenerated pre-generated files: `./Utilities/bridge-js-generate.sh` (updated `Tests/BridgeJSRuntimeTests/Generated/*`) - Verified: `SWIFT_SDK_ID=DEVELOPMENT-SNAPSHOT-2025-11-03-a-wasm32-unknown-wasip1 make unittest` passes --- .../Generated/BridgeJS.Macros.swift | 7 +++ .../Generated/BridgeJS.swift | 52 +++++++++++++++++++ .../Generated/JavaScript/BridgeJS.json | 41 +++++++++++++++ .../BridgeJSRuntimeTests/ImportAPITests.swift | 7 +++ Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 8 +++ Tests/prelude.mjs | 10 ++++ 6 files changed, 125 insertions(+) diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift index 65a46a3f2..ffcfca8dd 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift @@ -40,3 +40,10 @@ extension FeatureFlag: _BridgedSwiftEnumNoPayload {} } @JSFunction func runAsyncWorks() throws (JSException) -> JSPromise + +@JSFunction(jsName: "$jsWeirdFunction") func _jsWeirdFunction() throws (JSException) -> Double + +@JSClass(jsName: "$WeirdClass") struct _WeirdClass { + @JSFunction init() throws (JSException) + @JSFunction(jsName: "method-with-dashes") func method_with_dashes() throws (JSException) -> String +} diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 5195bee1e..ed2e5240f 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -6449,6 +6449,23 @@ func _$runAsyncWorks() throws(JSException) -> JSPromise { return JSPromise.bridgeJSLiftReturn(ret) } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs__jsWeirdFunction") +fileprivate func bjs__jsWeirdFunction() -> Float64 +#else +fileprivate func bjs__jsWeirdFunction() -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +func _$_jsWeirdFunction() throws(JSException) -> Double { + let ret = bjs__jsWeirdFunction() + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_init") fileprivate func bjs_JsGreeter_init(_ name: Int32, _ prefix: Int32) -> Int32 @@ -6558,6 +6575,41 @@ func _$JsGreeter_changeName(_ self: JSObject, _ name: String) throws(JSException } } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs__WeirdClass_init") +fileprivate func bjs__WeirdClass_init() -> Int32 +#else +fileprivate func bjs__WeirdClass_init() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs__WeirdClass_method_with_dashes") +fileprivate func bjs__WeirdClass_method_with_dashes(_ self: Int32) -> Int32 +#else +fileprivate func bjs__WeirdClass_method_with_dashes(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +func _$_WeirdClass_init() throws(JSException) -> JSObject { + let ret = bjs__WeirdClass_init() + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$_WeirdClass_method_with_dashes(_ self: JSObject) throws(JSException) -> String { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs__WeirdClass_method_with_dashes(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsApplyInt") fileprivate func bjs_jsApplyInt(_ value: Int32, _ transform: UnsafeMutableRawPointer) -> Int32 diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 66a87b063..fb2770ae6 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -9155,6 +9155,18 @@ "_0" : "JSPromise" } } + }, + { + "jsName" : "$jsWeirdFunction", + "name" : "_jsWeirdFunction", + "parameters" : [ + + ], + "returnType" : { + "double" : { + + } + } } ], "types" : [ @@ -9240,6 +9252,35 @@ } } ] + }, + { + "constructor" : { + "parameters" : [ + + ] + }, + "getters" : [ + + ], + "jsName" : "$WeirdClass", + "methods" : [ + { + "jsName" : "method-with-dashes", + "name" : "method_with_dashes", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "_WeirdClass", + "setters" : [ + + ] } ] }, diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift index 5ceca3bf0..ea9f8c68f 100644 --- a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -120,4 +120,11 @@ class ImportAPITests: XCTestCase { XCTAssertEqual(ret, 5) XCTAssertEqual(total, 10) } + + func testJSNameFunctionAndClass() throws { + XCTAssertEqual(try _jsWeirdFunction(), 42) + + let obj = try _WeirdClass() + XCTAssertEqual(try obj.method_with_dashes(), "ok") + } } diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index 87de440d8..983d6052d 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -23,3 +23,11 @@ export class JsGreeter { } export function runAsyncWorks(): Promise; + +// jsName tests +export function $jsWeirdFunction(): number; + +export class $WeirdClass { + constructor(); + "method-with-dashes"(): string; +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 66283b723..47f30a926 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -50,6 +50,9 @@ export async function setupOptions(options, context) { "jsRoundTripFeatureFlag": (flag) => { return flag; }, + "$jsWeirdFunction": () => { + return 42; + }, JsGreeter: class { /** * @param {string} name @@ -67,6 +70,13 @@ export async function setupOptions(options, context) { this.name = name; } }, + $WeirdClass: class { + constructor() { + } + ["method-with-dashes"]() { + return "ok"; + } + }, Foo: ImportedFoo, runAsyncWorks: async () => { const exports = importsContext.getExports(); From 374d796e9bf759b3f17b46fd375d66b99c25c414 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Jan 2026 09:06:48 +0900 Subject: [PATCH 6/6] Format BridgeJS generator sources --- .../BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift | 9 ++++++++- Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index b1dced37e..e2f1c3cf4 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1947,7 +1947,14 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func enterJSClass(_ typeName: String, jsName: String?) { stateStack.append(.jsClassBody(name: typeName)) - currentType = CurrentType(name: typeName, jsName: jsName, constructor: nil, methods: [], getters: [], setters: []) + currentType = CurrentType( + name: typeName, + jsName: jsName, + constructor: nil, + methods: [], + getters: [], + setters: [] + ) } private func exitJSClass() { diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index c42437dbe..ab4251c2c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -2242,7 +2242,8 @@ extension BridgeJSLink { } private static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String { - if propertyName.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil { + if propertyName.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil + { return "\(objectExpr).\(propertyName)" } let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(propertyName)