Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
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
29 changes: 28 additions & 1 deletion 29 Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,35 @@ extension JSClassMacro: MemberMacro {
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard declaration.is(StructDeclSyntax.self) else {
var fixIts: [FixIt] = []
let note = Note(
node: Syntax(declaration),
message: JSMacroNoteMessage(
message: "Use @JSClass on a struct wrapper to synthesize jsObject and JS bridging members."
)
)

if let classDecl = declaration.as(ClassDeclSyntax.self) {
let structKeyword = classDecl.classKeyword.with(\.tokenKind, .keyword(.struct))
fixIts.append(
FixIt(
message: JSMacroFixItMessage(message: "Change 'class' to 'struct'"),
changes: [
.replace(
oldNode: Syntax(classDecl.classKeyword),
newNode: Syntax(structKeyword)
)
]
)
)
}
context.diagnose(
Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedJSClassDeclaration)
Diagnostic(
node: Syntax(declaration),
message: JSMacroMessage.unsupportedJSClassDeclaration,
notes: [note],
fixIts: fixIts
)
)
return []
}
Expand Down
69 changes: 61 additions & 8 deletions 69 Plugins/BridgeJS/Sources/BridgeJSMacros/JSFunctionMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ extension JSFunctionMacro: BodyMacro {
if let functionDecl = declaration.as(FunctionDeclSyntax.self) {
let enclosingTypeName = JSMacroHelper.enclosingTypeName(from: context)
let isStatic = JSMacroHelper.isStatic(functionDecl.modifiers)
let isInstanceMember = enclosingTypeName != nil && !isStatic
let isTopLevel = enclosingTypeName == nil
let isInstanceMember = !isTopLevel && !isStatic
if !isTopLevel {
JSMacroHelper.diagnoseMissingJSClass(node: node, for: "JSFunction", in: context)
}

JSMacroHelper.diagnoseThrowsRequiresJSException(
signature: functionDecl.signature,
on: Syntax(functionDecl),
in: context
)

// Strip backticks from function name (e.g., "`prefix`" -> "prefix")
// Backticks are only needed for Swift identifiers, not function names
Expand All @@ -34,8 +44,7 @@ extension JSFunctionMacro: BodyMacro {

let effects = functionDecl.signature.effectSpecifiers
let isAsync = effects?.asyncSpecifier != nil
let isThrows = effects?.throwsClause != nil
let prefix = JSMacroHelper.tryAwaitPrefix(isAsync: isAsync, isThrows: isThrows)
let prefix = JSMacroHelper.tryAwaitPrefix(isAsync: isAsync, isThrows: true)

let isVoid = JSMacroHelper.isVoidReturn(functionDecl.signature.returnClause?.type)
let line = isVoid ? "\(prefix)\(call)" : "return \(prefix)\(call)"
Expand All @@ -45,28 +54,58 @@ extension JSFunctionMacro: BodyMacro {
if let initializerDecl = declaration.as(InitializerDeclSyntax.self) {
guard let enclosingTypeName = JSMacroHelper.enclosingTypeName(from: context) else {
context.diagnose(
Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration)
Diagnostic(
node: Syntax(declaration),
message: JSMacroMessage.unsupportedDeclaration,
notes: [
Note(
node: Syntax(declaration),
message: JSMacroNoteMessage(
message: "Move this initializer inside a JS wrapper type annotated with @JSClass."
)
)
]
)
)
return [CodeBlockItemSyntax(stringLiteral: "fatalError(\"@JSFunction init must be inside a type\")")]
}

JSMacroHelper.diagnoseMissingJSClass(node: node, for: "JSFunction", in: context)
JSMacroHelper.diagnoseThrowsRequiresJSException(
signature: initializerDecl.signature,
on: Syntax(initializerDecl),
in: context
)

let glueName = JSMacroHelper.glueName(baseName: "init", enclosingTypeName: enclosingTypeName)
let parameters = initializerDecl.signature.parameterClause.parameters
let arguments = JSMacroHelper.parameterNames(parameters)
let call = "\(glueName)(\(arguments.joined(separator: ", ")))"

let effects = initializerDecl.signature.effectSpecifiers
let isAsync = effects?.asyncSpecifier != nil
let isThrows = effects?.throwsClause != nil
let prefix = JSMacroHelper.tryAwaitPrefix(isAsync: isAsync, isThrows: isThrows)
let prefix = JSMacroHelper.tryAwaitPrefix(isAsync: isAsync, isThrows: true)

return [
CodeBlockItemSyntax(stringLiteral: "let jsObject = \(prefix)\(call)"),
CodeBlockItemSyntax(stringLiteral: "self.init(unsafelyWrapping: jsObject)"),
]
}

context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration))
context.diagnose(
Diagnostic(
node: Syntax(declaration),
message: JSMacroMessage.unsupportedDeclaration,
notes: [
Note(
node: Syntax(declaration),
message: JSMacroNoteMessage(
message: "Apply @JSFunction to a function or initializer on your @JSClass wrapper type."
)
)
]
)
)
return []
}
}
Expand All @@ -82,7 +121,21 @@ extension JSFunctionMacro: PeerMacro {
) throws -> [DeclSyntax] {
if declaration.is(FunctionDeclSyntax.self) { return [] }
if declaration.is(InitializerDeclSyntax.self) { return [] }
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration))
context.diagnose(
Diagnostic(
node: Syntax(declaration),
message: JSMacroMessage.unsupportedDeclaration,
notes: [
Note(
node: Syntax(declaration),
message: JSMacroNoteMessage(
message:
"Place @JSFunction on a function or initializer; use @JSGetter/@JSSetter for properties."
)
)
]
)
)
return []
}
}
36 changes: 33 additions & 3 deletions 36 Plugins/BridgeJS/Sources/BridgeJSMacros/JSGetterMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,30 @@ extension JSGetterMacro: AccessorMacro {
let binding = variableDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)
else {
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedVariable))
context.diagnose(
Diagnostic(
node: Syntax(declaration),
message: JSMacroMessage.unsupportedVariable,
notes: [
Note(
node: Syntax(declaration),
message: JSMacroNoteMessage(
message: "@JSGetter must be attached to a single stored or computed property."
)
)
]
)
)
return []
}

let enclosingTypeName = JSMacroHelper.enclosingTypeName(from: context)
let isStatic = JSMacroHelper.isStatic(variableDecl.modifiers)
let isInstanceMember = enclosingTypeName != nil && !isStatic
let isTopLevel = enclosingTypeName == nil
let isInstanceMember = !isTopLevel && !isStatic
if !isTopLevel {
JSMacroHelper.diagnoseMissingJSClass(node: node, for: "JSGetter", in: context)
}

// Strip backticks from property name (e.g., "`prefix`" -> "prefix")
// Backticks are only needed for Swift identifiers, not function names
Expand Down Expand Up @@ -71,7 +88,20 @@ extension JSGetterMacro: PeerMacro {
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard declaration.is(VariableDeclSyntax.self) else {
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedVariable))
context.diagnose(
Diagnostic(
node: Syntax(declaration),
message: JSMacroMessage.unsupportedVariable,
notes: [
Note(
node: Syntax(declaration),
message: JSMacroNoteMessage(
message: "@JSGetter must be attached to a single stored or computed property."
)
)
]
)
)
return []
}
return []
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.