-
-
Notifications
You must be signed in to change notification settings - Fork 75
Expand file tree
/
Copy pathJSGetterMacro.swift
More file actions
109 lines (101 loc) · 4.06 KB
/
Copy pathJSGetterMacro.swift
File metadata and controls
109 lines (101 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftDiagnostics
public enum JSGetterMacro {}
extension JSGetterMacro: AccessorMacro {
public static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard let variableDecl = declaration.as(VariableDeclSyntax.self),
let binding = variableDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)
else {
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 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
let propertyName = JSMacroHelper.stripBackticks(identifier.identifier.text)
let getterName = JSMacroHelper.glueName(
baseName: propertyName,
enclosingTypeName: enclosingTypeName,
operation: "get"
)
var getterArgs: [String] = []
if isInstanceMember {
getterArgs.append("self.jsObject")
}
let getterCall: CodeBlockItemSyntax =
"return try \(raw: getterName)(\(raw: getterArgs.joined(separator: ", ")))"
let throwsClause = ThrowsClauseSyntax(
throwsSpecifier: .keyword(.throws),
leftParen: .leftParenToken(),
type: IdentifierTypeSyntax(name: .identifier("JSException")),
rightParen: .rightParenToken()
)
return [
AccessorDeclSyntax(
accessorSpecifier: .keyword(.get),
effectSpecifiers: AccessorEffectSpecifiersSyntax(
asyncSpecifier: nil,
throwsClause: throwsClause
),
body: CodeBlockSyntax {
getterCall
}
)
]
}
}
extension JSGetterMacro: PeerMacro {
/// Emits a diagnostic when @JSGetter is applied to a declaration that is not a variable (e.g. a function).
/// AccessorMacro may not be invoked for non-property declarations. For variables with multiple
/// bindings, the compiler emits its own diagnostic; we only diagnose non-variable decls here.
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard declaration.is(VariableDeclSyntax.self) else {
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 []
}
}