Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@
"name" : "TS2Swift",
"setters" : [

],
"staticMethods" : [

]
}
]
Expand Down
22 changes: 22 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,24 @@ public struct ImportTS {
]
}

func renderStaticMethod(method: ImportedFunctionSkeleton) throws -> [DeclSyntax] {
let abiName = method.abiName(context: type, operation: "static")
let builder = CallJSEmission(moduleName: moduleName, abiName: abiName)
for param in method.parameters {
try builder.lowerParameter(param: param)
}
try builder.call(returnType: method.returnType)
try builder.liftReturnValue(returnType: method.returnType)
topLevelDecls.append(builder.renderImportDecl())
return [
builder.renderThunkDecl(
name: Self.thunkName(type: type, method: method),
parameters: method.parameters,
returnType: method.returnType
)
]
}

func renderConstructorDecl(constructor: ImportedConstructorSkeleton) throws -> [DeclSyntax] {
let builder = CallJSEmission(moduleName: moduleName, abiName: constructor.abiName(context: type))
for param in constructor.parameters {
Expand Down Expand Up @@ -462,6 +480,10 @@ public struct ImportTS {
decls.append(contentsOf: try renderConstructorDecl(constructor: constructor))
}

for method in type.staticMethods {
decls.append(contentsOf: try renderStaticMethod(method: method))
}

for getter in type.getters {
decls.append(try renderGetterDecl(getter: getter))
}
Expand Down
66 changes: 11 additions & 55 deletions Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1856,7 +1856,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
private let inputFilePath: String
private var jsClassNames: Set<String>
private let parent: SwiftToSkeleton

// MARK: - State Management

enum State {
Expand All @@ -1876,6 +1875,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
let from: JSImportFrom?
var constructor: ImportedConstructorSkeleton?
var methods: [ImportedFunctionSkeleton]
var staticMethods: [ImportedFunctionSkeleton]
var getters: [ImportedGetterSkeleton]
var setters: [ImportedSetterSkeleton]
}
Expand Down Expand Up @@ -2094,6 +2094,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
from: nil,
constructor: nil,
methods: [],
staticMethods: [],
getters: [],
setters: []
)
Expand All @@ -2107,6 +2108,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
from: from,
constructor: nil,
methods: [],
staticMethods: [],
getters: [],
setters: []
)
Expand All @@ -2121,6 +2123,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
from: type.from,
constructor: type.constructor,
methods: type.methods,
staticMethods: type.staticMethods,
getters: type.getters,
setters: type.setters,
documentation: nil
Expand Down Expand Up @@ -2165,12 +2168,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {

// MARK: - Visitor Methods

override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
let typeName = node.extendedType.trimmedDescription
collectStaticMembers(in: node.memberBlock.members, typeName: typeName)
return .skipChildren
}

override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
switch state {
case .topLevel:
Expand All @@ -2191,7 +2188,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {

private func handleTopLevelFunction(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes),
let function = parseFunction(jsFunction, node, enclosingTypeName: nil, isStaticMember: true)
let function = parseFunction(jsFunction, node)
{
importedFunctions.append(function)
return .skipChildren
Expand All @@ -2216,13 +2213,11 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
type: inout CurrentType
) -> Bool {
if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes) {
if isStaticMember {
parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: true).map {
importedFunctions.append($0)
}
} else {
parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: false).map {
type.methods.append($0)
if let method = parseFunction(jsFunction, node) {
if isStaticMember {
type.staticMethods.append(method)
} else {
type.methods.append(method)
}
}
return true
Expand Down Expand Up @@ -2313,38 +2308,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
}
}

// MARK: - Member Collection

private func collectStaticMembers(in members: MemberBlockItemListSyntax, typeName: String) {
for member in members {
if let function = member.decl.as(FunctionDeclSyntax.self) {
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) {
errors.append(
DiagnosticError(
node: function,
message:
"@JSSetter is not supported for static members. Use it only for instance members in @JSClass types."
)
)
}
} else if let variable = member.decl.as(VariableDeclSyntax.self),
AttributeChecker.hasJSGetterAttribute(variable.attributes)
{
errors.append(
DiagnosticError(
node: variable,
message:
"@JSGetter is not supported for static members. Use it only for instance members in @JSClass types."
)
)
}
}
}

// MARK: - Parsing Methods

private func parseConstructor(
Expand All @@ -2365,8 +2328,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
private func parseFunction(
_ jsFunction: AttributeSyntax,
_ node: FunctionDeclSyntax,
enclosingTypeName: String?,
isStaticMember: Bool
) -> ImportedFunctionSkeleton? {
guard validateEffects(node.signature.effectSpecifiers, node: node, attributeName: "JSFunction") != nil
else {
Expand All @@ -2376,12 +2337,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
let baseName = SwiftToSkeleton.normalizeIdentifier(node.name.text)
let jsName = AttributeChecker.extractJSName(from: jsFunction)
let from = AttributeChecker.extractJSImportFrom(from: jsFunction)
let name: String
if isStaticMember, let enclosingTypeName {
name = "\(enclosingTypeName)_\(baseName)"
} else {
name = baseName
}
let name = baseName

let parameters = parseParameters(from: node.signature.parameterClause)
let returnType: BridgeType
Expand Down
75 changes: 61 additions & 14 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2246,6 +2246,14 @@ extension BridgeJSLink {
)
}

func callStaticMethod(on objectExpr: String, name: String, returnType: BridgeType) throws -> String? {
let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
return try call(
calleeExpr: calleeExpr,
returnType: returnType
)
}

func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? {
let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)"
let accessExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
Expand Down Expand Up @@ -2318,7 +2326,7 @@ extension BridgeJSLink {
return loweredValues.first
}

private static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String {
static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String {
if propertyName.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil
{
return "\(objectExpr).\(propertyName)"
Expand Down Expand Up @@ -3130,6 +3138,32 @@ extension BridgeJSLink {
importObjectBuilder.assignToImportObject(name: setterAbiName, function: js)
importObjectBuilder.appendDts(dts)
}
for method in type.staticMethods {
let abiName = method.abiName(context: type, operation: "static")
let (js, dts) = try renderImportedStaticMethod(context: type, method: method)
importObjectBuilder.assignToImportObject(name: abiName, function: js)
importObjectBuilder.appendDts(dts)
}
if type.from == nil, type.constructor != nil || !type.staticMethods.isEmpty {
let dtsPrinter = CodeFragmentPrinter()
dtsPrinter.write("\(type.name): {")
dtsPrinter.indent {
if let constructor = type.constructor {
let returnType = BridgeType.jsObject(type.name)
dtsPrinter.write(
"new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));"
)
}
for method in type.staticMethods {
let methodName = method.jsName ?? method.name
let signature =
"\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));"
dtsPrinter.write(signature)
}
}
dtsPrinter.write("}")
importObjectBuilder.appendDts(dtsPrinter.lines)
}
for method in type.methods {
let (js, dts) = try renderImportedMethod(context: type, method: method)
importObjectBuilder.assignToImportObject(name: method.abiName(context: type), function: js)
Expand Down Expand Up @@ -3160,19 +3194,6 @@ extension BridgeJSLink {
returnType: returnType
)
importObjectBuilder.assignToImportObject(name: abiName, function: funcLines)

if type.from == nil {
let dtsPrinter = CodeFragmentPrinter()
dtsPrinter.write("\(type.name): {")
dtsPrinter.indent {
dtsPrinter.write(
"new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));"
)
}
dtsPrinter.write("}")

importObjectBuilder.appendDts(dtsPrinter.lines)
}
}

func renderImportedGetter(
Expand Down Expand Up @@ -3207,6 +3228,32 @@ extension BridgeJSLink {
return (funcLines, [])
}

func renderImportedStaticMethod(
context: ImportedTypeSkeleton,
method: ImportedFunctionSkeleton
) throws -> (js: [String], dts: [String]) {
let thunkBuilder = ImportedThunkBuilder()
for param in method.parameters {
try thunkBuilder.liftParameter(param: param)
}
let importRootExpr = context.from == .global ? "globalThis" : "imports"
let constructorExpr = ImportedThunkBuilder.propertyAccessExpr(
objectExpr: importRootExpr,
propertyName: context.jsName ?? context.name
)
let returnExpr = try thunkBuilder.callStaticMethod(
on: constructorExpr,
name: method.jsName ?? method.name,
returnType: method.returnType
)
let funcLines = thunkBuilder.renderFunction(
name: method.abiName(context: context, operation: "static"),
returnExpr: returnExpr,
returnType: method.returnType
)
return (funcLines, [])
}

func renderImportedMethod(
context: ImportedTypeSkeleton,
method: ImportedFunctionSkeleton
Expand Down
13 changes: 11 additions & 2 deletions Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -640,9 +640,14 @@ public struct ImportedFunctionSkeleton: Codable {
}

public func abiName(context: ImportedTypeSkeleton?) -> String {
return abiName(context: context, operation: nil)
}

public func abiName(context: ImportedTypeSkeleton?, operation: String?) -> String {
return ABINameGenerator.generateImportedABIName(
baseName: name,
context: context
context: context,
operation: operation
)
}
}
Expand Down Expand Up @@ -752,6 +757,8 @@ public struct ImportedTypeSkeleton: Codable {
public let from: JSImportFrom?
public let constructor: ImportedConstructorSkeleton?
public let methods: [ImportedFunctionSkeleton]
/// Static methods available on the JavaScript constructor.
public var staticMethods: [ImportedFunctionSkeleton]
public let getters: [ImportedGetterSkeleton]
public let setters: [ImportedSetterSkeleton]
public let documentation: String?
Expand All @@ -761,7 +768,8 @@ public struct ImportedTypeSkeleton: Codable {
jsName: String? = nil,
from: JSImportFrom? = nil,
constructor: ImportedConstructorSkeleton? = nil,
methods: [ImportedFunctionSkeleton],
methods: [ImportedFunctionSkeleton] = [],
staticMethods: [ImportedFunctionSkeleton] = [],
getters: [ImportedGetterSkeleton] = [],
setters: [ImportedSetterSkeleton] = [],
documentation: String? = nil
Expand All @@ -771,6 +779,7 @@ public struct ImportedTypeSkeleton: Codable {
self.from = from
self.constructor = constructor
self.methods = methods
self.staticMethods = staticMethods
self.getters = getters
self.setters = setters
self.documentation = documentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -821,8 +821,12 @@ export class TypeProcessor {
const returnType = this.visitType(signature.getReturnType(), node);
const effects = this.renderEffects({ isAsync: false });
const swiftMethodName = this.renderIdentifier(swiftName);
const isStatic = node.modifiers?.some(
(modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword
) ?? false;
const staticKeyword = isStatic ? "static " : "";

this.swiftLines.push(` ${annotation} func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`);
this.swiftLines.push(` ${annotation} ${staticKeyword}func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ exports[`ts2swift > snapshots Swift output for TypeScriptClass.d.ts > TypeScript
@JSFunction init(_ name: String) throws(JSException)
@JSFunction func greet() throws(JSException) -> String
@JSFunction func changeName(_ name: String) throws(JSException) -> Void
@JSFunction static func staticMethod(_ p1: Double, _ p2: String) throws(JSException) -> String
}
"
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ export class Greeter {
constructor(name: string);
greet(): string;
changeName(name: string): void;

static staticMethod(p1: number, p2: string): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@JSClass struct StaticBox {
@JSFunction static func create(_ value: Double) throws(JSException) -> StaticBox
@JSFunction func value() throws(JSException) -> Double
@JSFunction static func value() throws(JSException) -> Double
@JSFunction static func makeDefault() throws(JSException) -> StaticBox
@JSFunction(jsName: "with-dashes") static func dashed() throws(JSException) -> StaticBox
}

@JSClass struct WithCtor {
@JSFunction init(_ value: Double) throws(JSException)
@JSFunction static func create(_ value: Double) throws(JSException) -> WithCtor
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
"name" : "JSConsole",
"setters" : [

],
"staticMethods" : [

]
}
]
Expand Down
Loading