expo-modules-macros 0.19.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/linux.yml +132 -0
- package/.github/workflows/publish.yml +160 -11
- package/.github/workflows/windows.yml +3 -28
- package/AGENTS.md +11 -6
- package/README.md +15 -9
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesMacros/ClosureArguments.swift +1 -1
- package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +23 -10
- package/apple/Sources/ExpoModulesMacros/EventMacro.swift +12 -1
- package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +2 -0
- package/apple/Sources/ExpoModulesMacros/JSNameCollisions.swift +64 -0
- package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +2 -1
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +176 -91
- package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +3 -0
- package/apple/Sources/ExpoModulesMacros/UnionMacro.swift +14 -1
- package/apple/Sources/ExpoModulesScanner/Exports/Availability.swift +259 -0
- package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +151 -32
- package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +48 -65
- package/apple/Sources/ExpoModulesScanner/Exports/ScanExports.swift +1 -1
- package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +179 -46
- package/build/index.d.ts +16 -6
- package/build/index.js +51 -28
- package/build/types.d.ts +86 -19
- package/build/types.js +1 -1
- package/package.json +5 -3
|
@@ -147,61 +147,51 @@ extension ExportedSurface {
|
|
|
147
147
|
let index = RefIndex(surface: self)
|
|
148
148
|
return ExportedSurface(
|
|
149
149
|
modules: modules.map { module in
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
},
|
|
163
|
-
file: module.file
|
|
164
|
-
)
|
|
150
|
+
var resolved = module
|
|
151
|
+
resolved.functions = module.functions.map { $0.resolvingRefs(using: index, in: module.name) }
|
|
152
|
+
resolved.properties = module.properties.map { $0.resolvingRefs(using: index, in: module.name) }
|
|
153
|
+
resolved.events = module.events.map { $0.resolvingRefs(using: index, in: module.name) }
|
|
154
|
+
resolved.classes = module.classes.map { exportedClass in
|
|
155
|
+
guard let entry = index.lookup(exportedClass.name, in: module.name),
|
|
156
|
+
entry.kind == .sharedObject else {
|
|
157
|
+
return exportedClass
|
|
158
|
+
}
|
|
159
|
+
return ExportedClass(name: entry.name, jsName: entry.jsName)
|
|
160
|
+
}
|
|
161
|
+
return resolved
|
|
165
162
|
},
|
|
166
163
|
sharedObjects: sharedObjects.map { sharedObject in
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
constructorParameters: sharedObject.constructorParameters?.map {
|
|
164
|
+
var resolved = sharedObject
|
|
165
|
+
if let initializer = sharedObject.initializer {
|
|
166
|
+
resolved.initializer?.parameters = initializer.parameters.map {
|
|
171
167
|
$0.resolvingRefs(using: index, in: sharedObject.name)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
resolved.functions = sharedObject.functions.map { $0.resolvingRefs(using: index, in: sharedObject.name) }
|
|
171
|
+
resolved.properties = sharedObject.properties.map { $0.resolvingRefs(using: index, in: sharedObject.name) }
|
|
172
|
+
resolved.events = sharedObject.events.map { $0.resolvingRefs(using: index, in: sharedObject.name) }
|
|
173
|
+
return resolved
|
|
178
174
|
},
|
|
179
175
|
records: records.map { record in
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
hasDefault: property.hasDefault
|
|
188
|
-
)
|
|
189
|
-
},
|
|
190
|
-
file: record.file
|
|
191
|
-
)
|
|
176
|
+
var resolved = record
|
|
177
|
+
resolved.properties = record.properties.map { property in
|
|
178
|
+
var resolvedProperty = property
|
|
179
|
+
resolvedProperty.type = property.type.resolvingRefs(using: index, in: record.name)
|
|
180
|
+
return resolvedProperty
|
|
181
|
+
}
|
|
182
|
+
return resolved
|
|
192
183
|
},
|
|
193
184
|
// An enum's raw type is a primitive or an unscanned spelling, never a ref to another scanned
|
|
194
185
|
// type, so there is nothing in one to resolve.
|
|
195
186
|
enums: enums,
|
|
196
187
|
unions: unions.map { union in
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
188
|
+
var resolved = union
|
|
189
|
+
resolved.members = union.members.map { member in
|
|
190
|
+
var resolvedMember = member
|
|
191
|
+
resolvedMember.type = member.type.resolvingRefs(using: index, in: union.name)
|
|
192
|
+
return resolvedMember
|
|
193
|
+
}
|
|
194
|
+
return resolved
|
|
205
195
|
}
|
|
206
196
|
)
|
|
207
197
|
}
|
|
@@ -209,40 +199,33 @@ extension ExportedSurface {
|
|
|
209
199
|
|
|
210
200
|
extension ExportedParameter {
|
|
211
201
|
fileprivate func resolvingRefs(using index: RefIndex, in scope: String) -> ExportedParameter {
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
var resolved = self
|
|
203
|
+
resolved.type = type.resolvingRefs(using: index, in: scope)
|
|
204
|
+
return resolved
|
|
214
205
|
}
|
|
215
206
|
}
|
|
216
207
|
|
|
217
208
|
extension ExportedFunction {
|
|
218
209
|
fileprivate func resolvingRefs(using index: RefIndex, in scope: String) -> ExportedFunction {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
returns: returns?.resolvingRefs(using: index, in: scope),
|
|
224
|
-
isAsync: isAsync,
|
|
225
|
-
isThrowing: isThrowing,
|
|
226
|
-
isStatic: isStatic
|
|
227
|
-
)
|
|
210
|
+
var resolved = self
|
|
211
|
+
resolved.parameters = parameters.map { $0.resolvingRefs(using: index, in: scope) }
|
|
212
|
+
resolved.returns = returns?.resolvingRefs(using: index, in: scope)
|
|
213
|
+
return resolved
|
|
228
214
|
}
|
|
229
215
|
}
|
|
230
216
|
|
|
231
217
|
extension ExportedProperty {
|
|
232
218
|
fileprivate func resolvingRefs(using index: RefIndex, in scope: String) -> ExportedProperty {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
type: type?.resolvingRefs(using: index, in: scope),
|
|
237
|
-
isSettable: isSettable,
|
|
238
|
-
isStatic: isStatic
|
|
239
|
-
)
|
|
219
|
+
var resolved = self
|
|
220
|
+
resolved.type = type?.resolvingRefs(using: index, in: scope)
|
|
221
|
+
return resolved
|
|
240
222
|
}
|
|
241
223
|
}
|
|
242
224
|
|
|
243
225
|
extension ExportedEvent {
|
|
244
226
|
fileprivate func resolvingRefs(using index: RefIndex, in scope: String) -> ExportedEvent {
|
|
245
|
-
|
|
246
|
-
|
|
227
|
+
var resolved = self
|
|
228
|
+
resolved.payload = payload?.resolvingRefs(using: index, in: scope)
|
|
229
|
+
return resolved
|
|
247
230
|
}
|
|
248
231
|
}
|
|
@@ -46,7 +46,7 @@ func scanExports(paths: [String]) -> ScanExportsResult {
|
|
|
46
46
|
conformances: [enumerableConformanceName]
|
|
47
47
|
) { source, file in
|
|
48
48
|
let tree = Parser.parse(source: source)
|
|
49
|
-
let visitor = SurfaceVisitor(file: file)
|
|
49
|
+
let visitor = SurfaceVisitor(file: file, tree: tree)
|
|
50
50
|
visitor.walk(tree)
|
|
51
51
|
modules.append(contentsOf: visitor.modules)
|
|
52
52
|
sharedObjects.append(contentsOf: visitor.sharedObjects)
|
|
@@ -9,16 +9,25 @@ import SwiftSyntax
|
|
|
9
9
|
/// Types nested in another type's body, or in a file-scope `extension`, are reported under their
|
|
10
10
|
/// qualified Swift name (`Options.Encoding`), so a ref to one can be resolved from the scope it is
|
|
11
11
|
/// written in. A type declared in a function body or a closure is local and is not reported.
|
|
12
|
+
///
|
|
13
|
+
/// Each declaration and member reports the platforms that build it (see `PlatformResolver`), and one
|
|
14
|
+
/// that no platform builds is not reported. `#if` is read only where the runtime sees what it holds:
|
|
15
|
+
/// around types, `@Event` members, and enum cases. `@ExpoModule`, `@SharedObject`, `@Record`, and
|
|
16
|
+
/// `@Union` read only the members written directly in the type body, so `@JS` members, record
|
|
17
|
+
/// properties, and union cases in a `#if` clause are never bound and are not reported. An `@Event`
|
|
18
|
+
/// expands on its own declaration, and the compiler reads enum cases itself.
|
|
12
19
|
final class SurfaceVisitor: SyntaxVisitor {
|
|
13
20
|
private let file: String
|
|
21
|
+
private let platformResolver: PlatformResolver
|
|
14
22
|
private(set) var modules: [ExportedModule] = []
|
|
15
23
|
private(set) var sharedObjects: [ExportedSharedObject] = []
|
|
16
24
|
private(set) var records: [ExportedRecord] = []
|
|
17
25
|
private(set) var enums: [ExportedEnum] = []
|
|
18
26
|
private(set) var unions: [ExportedUnion] = []
|
|
19
27
|
|
|
20
|
-
init(file: String) {
|
|
28
|
+
init(file: String, tree: SourceFileSyntax) {
|
|
21
29
|
self.file = file
|
|
30
|
+
self.platformResolver = PlatformResolver(tree: tree)
|
|
22
31
|
super.init(viewMode: .sourceAccurate)
|
|
23
32
|
}
|
|
24
33
|
|
|
@@ -26,8 +35,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
26
35
|
guard let scope = enclosingTypes(of: node) else {
|
|
27
36
|
return .skipChildren
|
|
28
37
|
}
|
|
29
|
-
classify(
|
|
30
|
-
name: node.name.identifierName, scope: scope, attributes: node.attributes, members: node.memberBlock.members)
|
|
38
|
+
classify(node, name: node.name.identifierName, scope: scope)
|
|
31
39
|
// The member walk reads the body itself; the children are visited only to find nested types.
|
|
32
40
|
return .visitChildren
|
|
33
41
|
}
|
|
@@ -36,8 +44,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
36
44
|
guard let scope = enclosingTypes(of: node) else {
|
|
37
45
|
return .skipChildren
|
|
38
46
|
}
|
|
39
|
-
classify(
|
|
40
|
-
name: node.name.identifierName, scope: scope, attributes: node.attributes, members: node.memberBlock.members)
|
|
47
|
+
classify(node, name: node.name.identifierName, scope: scope)
|
|
41
48
|
return .visitChildren
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -78,12 +85,20 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
78
85
|
return .skipChildren
|
|
79
86
|
}
|
|
80
87
|
let name = qualifiedName(node.name.identifierName, in: scope)
|
|
88
|
+
let platforms = platformResolver.platforms(of: node)
|
|
89
|
+
// An enum is often a namespace for other types, so its body is searched too, even when this
|
|
90
|
+
// enum is not reported.
|
|
91
|
+
guard !platforms.isEmpty else {
|
|
92
|
+
return .visitChildren
|
|
93
|
+
}
|
|
81
94
|
|
|
82
95
|
if node.attributes.firstAttribute(named: DetectedMacro.union.rawValue) != nil {
|
|
83
96
|
unions.append(
|
|
84
97
|
ExportedUnion(
|
|
85
98
|
name: name,
|
|
86
99
|
members: collectUnionMembers(node.memberBlock.members),
|
|
100
|
+
accessLevel: spelledAccessLevel(of: node.modifiers),
|
|
101
|
+
platforms: platforms,
|
|
87
102
|
file: file
|
|
88
103
|
))
|
|
89
104
|
} else if inherits(from: enumerableConformanceName, in: node.inheritanceClause) {
|
|
@@ -93,6 +108,8 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
93
108
|
name: name,
|
|
94
109
|
rawType: rawType,
|
|
95
110
|
cases: collectEnumCases(node.memberBlock.members, rawType: rawType),
|
|
111
|
+
accessLevel: spelledAccessLevel(of: node.modifiers),
|
|
112
|
+
platforms: platforms,
|
|
96
113
|
file: file
|
|
97
114
|
))
|
|
98
115
|
}
|
|
@@ -107,10 +124,15 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
107
124
|
/// `name` is the declared name and `scope` the names of the types enclosing it. The reported `name`
|
|
108
125
|
/// is qualified with the scope, while a default `jsName` is the declared name alone, as the macros
|
|
109
126
|
/// spell it.
|
|
110
|
-
private func classify(
|
|
111
|
-
name: String, scope: [String], attributes: AttributeListSyntax, members: MemberBlockItemListSyntax
|
|
112
|
-
) {
|
|
127
|
+
private func classify(_ node: some DeclGroupSyntax, name: String, scope: [String]) {
|
|
113
128
|
let qualified = qualifiedName(name, in: scope)
|
|
129
|
+
let attributes = node.attributes
|
|
130
|
+
let members = node.memberBlock.members
|
|
131
|
+
let accessLevel = spelledAccessLevel(of: node.modifiers)
|
|
132
|
+
let platforms = platformResolver.platforms(of: node)
|
|
133
|
+
guard !platforms.isEmpty else {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
114
136
|
|
|
115
137
|
if let attribute = attributes.firstAttribute(named: DetectedMacro.expoModule.rawValue) {
|
|
116
138
|
// Only a file-scope module is reported. Autolinking registers no other (`DetectionVisitor`
|
|
@@ -131,82 +153,111 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
131
153
|
classes: classListArgument(of: attribute, label: "classes").map {
|
|
132
154
|
ExportedClass(name: $0, jsName: nil)
|
|
133
155
|
},
|
|
156
|
+
accessLevel: accessLevel,
|
|
157
|
+
platforms: platforms,
|
|
134
158
|
file: file
|
|
135
159
|
))
|
|
136
160
|
return
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
if let attribute = attributes.firstAttribute(named: DetectedMacro.sharedObject.rawValue) {
|
|
140
|
-
let (functions, properties, events,
|
|
164
|
+
let (functions, properties, events, initializer) = collectJSMembers(members)
|
|
141
165
|
sharedObjects.append(
|
|
142
166
|
ExportedSharedObject(
|
|
143
167
|
name: qualified,
|
|
144
168
|
jsName: stringArgument(of: attribute) ?? name,
|
|
145
|
-
|
|
169
|
+
initializer: initializer,
|
|
146
170
|
functions: functions,
|
|
147
171
|
properties: properties,
|
|
148
172
|
events: events,
|
|
173
|
+
accessLevel: accessLevel,
|
|
174
|
+
platforms: platforms,
|
|
149
175
|
file: file
|
|
150
176
|
))
|
|
151
177
|
return
|
|
152
178
|
}
|
|
153
179
|
|
|
154
180
|
if attributes.firstAttribute(named: DetectedMacro.record.rawValue) != nil {
|
|
155
|
-
records.append(
|
|
181
|
+
records.append(
|
|
182
|
+
ExportedRecord(
|
|
183
|
+
name: qualified,
|
|
184
|
+
properties: collectRecordProperties(members),
|
|
185
|
+
accessLevel: accessLevel,
|
|
186
|
+
platforms: platforms,
|
|
187
|
+
file: file
|
|
188
|
+
))
|
|
156
189
|
}
|
|
157
190
|
}
|
|
158
191
|
|
|
159
192
|
/// The exported members of a module / shared-object body: `@JS` functions and properties, `@Event`
|
|
160
|
-
/// events, and the single `@JS init`
|
|
193
|
+
/// events, and the single `@JS init` (`nil` when absent).
|
|
161
194
|
private func collectJSMembers(
|
|
162
195
|
_ members: MemberBlockItemListSyntax
|
|
163
196
|
) -> (
|
|
164
197
|
functions: [ExportedFunction], properties: [ExportedProperty], events: [ExportedEvent],
|
|
165
|
-
|
|
198
|
+
initializer: ExportedInitializer?
|
|
166
199
|
) {
|
|
167
200
|
var functions: [ExportedFunction] = []
|
|
168
201
|
var properties: [ExportedProperty] = []
|
|
169
202
|
var events: [ExportedEvent] = []
|
|
170
|
-
var
|
|
203
|
+
var initializer: ExportedInitializer?
|
|
171
204
|
|
|
172
|
-
|
|
205
|
+
// The macros bind only the `@JS` members written directly in the body.
|
|
206
|
+
let directMembers = Set(members.map(\.id))
|
|
207
|
+
|
|
208
|
+
for member in flattened(members) {
|
|
173
209
|
let decl = member.decl
|
|
210
|
+
let platforms = platformResolver.platforms(of: decl)
|
|
211
|
+
guard !platforms.isEmpty else {
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if let varDecl = decl.as(VariableDeclSyntax.self),
|
|
216
|
+
varDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) == nil,
|
|
217
|
+
let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.event.rawValue) {
|
|
218
|
+
events.append(contentsOf: makeEvents(varDecl: varDecl, attribute: attribute, platforms: platforms))
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
guard directMembers.contains(member.id) else {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
174
225
|
|
|
175
226
|
if let initDecl = decl.as(InitializerDeclSyntax.self),
|
|
176
227
|
initDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) != nil {
|
|
177
228
|
// At most one `@JS init`; keep the first if a malformed source has more (the macro errors).
|
|
178
|
-
if
|
|
179
|
-
|
|
229
|
+
if initializer == nil {
|
|
230
|
+
initializer = ExportedInitializer(
|
|
231
|
+
parameters: parameters(of: initDecl.signature.parameterClause),
|
|
232
|
+
accessLevel: spelledAccessLevel(of: initDecl.modifiers),
|
|
233
|
+
platforms: platforms
|
|
234
|
+
)
|
|
180
235
|
}
|
|
181
236
|
continue
|
|
182
237
|
}
|
|
183
238
|
|
|
184
239
|
if let funcDecl = decl.as(FunctionDeclSyntax.self),
|
|
185
240
|
let attribute = funcDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) {
|
|
186
|
-
functions.append(makeFunction(funcDecl: funcDecl, attribute: attribute))
|
|
241
|
+
functions.append(makeFunction(funcDecl: funcDecl, attribute: attribute, platforms: platforms))
|
|
187
242
|
continue
|
|
188
243
|
}
|
|
189
244
|
|
|
190
245
|
if let varDecl = decl.as(VariableDeclSyntax.self),
|
|
191
246
|
let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) {
|
|
192
|
-
properties.append(contentsOf: makeProperties(varDecl: varDecl, attribute: attribute))
|
|
193
|
-
continue
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if let varDecl = decl.as(VariableDeclSyntax.self),
|
|
197
|
-
let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.event.rawValue) {
|
|
198
|
-
events.append(contentsOf: makeEvents(varDecl: varDecl, attribute: attribute))
|
|
247
|
+
properties.append(contentsOf: makeProperties(varDecl: varDecl, attribute: attribute, platforms: platforms))
|
|
199
248
|
}
|
|
200
249
|
}
|
|
201
250
|
|
|
202
|
-
return (functions, properties, events,
|
|
251
|
+
return (functions, properties, events, initializer)
|
|
203
252
|
}
|
|
204
253
|
|
|
205
254
|
/// Builds the `ExportedEvent` entries for an `@Event var`, applying the same checks
|
|
206
255
|
/// `EventMacro.validatedEvent(of:on:)` does. A rejected declaration expands to no event, so
|
|
207
256
|
/// reporting one would describe a surface that does not exist. `@JS` on the same property is
|
|
208
257
|
/// caught by the caller, which reaches the `@JS` branch first.
|
|
209
|
-
private func makeEvents(
|
|
258
|
+
private func makeEvents(
|
|
259
|
+
varDecl: VariableDeclSyntax, attribute: AttributeSyntax, platforms: [ExportedPlatform]
|
|
260
|
+
) -> [ExportedEvent] {
|
|
210
261
|
guard varDecl.bindingSpecifier.tokenKind != .keyword(.let),
|
|
211
262
|
!isTypeLevel(varDecl.modifiers) else {
|
|
212
263
|
return []
|
|
@@ -232,7 +283,9 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
232
283
|
name: name,
|
|
233
284
|
jsName: override ?? defaultEventName(for: name),
|
|
234
285
|
payload: functionType.parameters.first.map { typeNode(from: $0.type) },
|
|
235
|
-
isSync: isSync
|
|
286
|
+
isSync: isSync,
|
|
287
|
+
accessLevel: spelledAccessLevel(of: varDecl.modifiers),
|
|
288
|
+
platforms: platforms
|
|
236
289
|
))
|
|
237
290
|
}
|
|
238
291
|
return result
|
|
@@ -240,7 +293,9 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
240
293
|
|
|
241
294
|
/// Builds an `ExportedFunction` from a `@JS func`: JS-name fallback, parameters, a `Void` return as
|
|
242
295
|
/// `nil`, and the effect/static flags.
|
|
243
|
-
private func makeFunction(
|
|
296
|
+
private func makeFunction(
|
|
297
|
+
funcDecl: FunctionDeclSyntax, attribute: AttributeSyntax, platforms: [ExportedPlatform]
|
|
298
|
+
) -> ExportedFunction {
|
|
244
299
|
let effects = funcDecl.signature.effectSpecifiers
|
|
245
300
|
let returnType = funcDecl.signature.returnClause?.type
|
|
246
301
|
return ExportedFunction(
|
|
@@ -250,14 +305,18 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
250
305
|
returns: isVoidType(returnType) ? nil : returnType.map { typeNode(from: $0) },
|
|
251
306
|
isAsync: effects?.asyncSpecifier != nil,
|
|
252
307
|
isThrowing: effects?.throwsClause?.throwsSpecifier != nil,
|
|
253
|
-
isStatic: isTypeLevel(funcDecl.modifiers)
|
|
308
|
+
isStatic: isTypeLevel(funcDecl.modifiers),
|
|
309
|
+
accessLevel: spelledAccessLevel(of: funcDecl.modifiers),
|
|
310
|
+
platforms: platforms
|
|
254
311
|
)
|
|
255
312
|
}
|
|
256
313
|
|
|
257
314
|
/// Builds the `ExportedProperty` entries for a `@JS var`/`let`. One declaration can introduce several
|
|
258
315
|
/// bindings (`var a, b: Int`), so this returns an array. The value type is the annotation, else the
|
|
259
316
|
/// literal default's inferred type, else `nil`.
|
|
260
|
-
private func makeProperties(
|
|
317
|
+
private func makeProperties(
|
|
318
|
+
varDecl: VariableDeclSyntax, attribute: AttributeSyntax, platforms: [ExportedPlatform]
|
|
319
|
+
) -> [ExportedProperty] {
|
|
261
320
|
let isLet = varDecl.bindingSpecifier.tokenKind == .keyword(.let)
|
|
262
321
|
let isStatic = isTypeLevel(varDecl.modifiers)
|
|
263
322
|
let override = stringArgument(of: attribute)
|
|
@@ -275,7 +334,11 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
275
334
|
jsName: override ?? name,
|
|
276
335
|
type: type,
|
|
277
336
|
isSettable: isSettable(binding: binding, isLet: isLet),
|
|
278
|
-
isStatic: isStatic
|
|
337
|
+
isStatic: isStatic,
|
|
338
|
+
// Only a stored property has an initializer; a computed one has none to report.
|
|
339
|
+
defaultValue: binding.initializer?.value.trimmedDescription,
|
|
340
|
+
accessLevel: spelledAccessLevel(of: varDecl.modifiers),
|
|
341
|
+
platforms: platforms
|
|
279
342
|
))
|
|
280
343
|
}
|
|
281
344
|
return result
|
|
@@ -314,11 +377,16 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
314
377
|
private func collectRecordProperties(_ members: MemberBlockItemListSyntax) -> [ExportedRecordProperty] {
|
|
315
378
|
var properties: [ExportedRecordProperty] = []
|
|
316
379
|
|
|
380
|
+
// `@Record` reads only the properties written directly in the body, so `#if` is not looked into.
|
|
317
381
|
for member in members {
|
|
318
382
|
guard let varDecl = member.decl.as(VariableDeclSyntax.self),
|
|
319
383
|
!isExcludedRecordModifier(varDecl.modifiers) else {
|
|
320
384
|
continue
|
|
321
385
|
}
|
|
386
|
+
let platforms = platformResolver.platforms(of: varDecl)
|
|
387
|
+
guard !platforms.isEmpty else {
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
322
390
|
for binding in varDecl.bindings {
|
|
323
391
|
if binding.accessorBlock != nil {
|
|
324
392
|
continue
|
|
@@ -335,7 +403,9 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
335
403
|
name: ident.identifier.identifierName,
|
|
336
404
|
type: type,
|
|
337
405
|
isOptional: annotation.map { isOptionalType($0) } ?? false,
|
|
338
|
-
|
|
406
|
+
defaultValue: binding.initializer?.value.trimmedDescription,
|
|
407
|
+
accessLevel: spelledAccessLevel(of: varDecl.modifiers),
|
|
408
|
+
platforms: platforms
|
|
339
409
|
))
|
|
340
410
|
}
|
|
341
411
|
}
|
|
@@ -355,16 +425,21 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
355
425
|
) -> [ExportedEnumCase] {
|
|
356
426
|
var cases: [ExportedEnumCase] = []
|
|
357
427
|
|
|
358
|
-
for member in members {
|
|
428
|
+
for member in flattened(members) {
|
|
359
429
|
guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else {
|
|
360
430
|
continue
|
|
361
431
|
}
|
|
432
|
+
let platforms = platformResolver.platforms(of: caseDecl)
|
|
433
|
+
guard !platforms.isEmpty else {
|
|
434
|
+
continue
|
|
435
|
+
}
|
|
362
436
|
for element in caseDecl.elements where element.parameterClause == nil {
|
|
363
437
|
let name = element.name.identifierName
|
|
364
438
|
cases.append(
|
|
365
439
|
ExportedEnumCase(
|
|
366
440
|
name: name,
|
|
367
|
-
rawValue: writtenRawValue(of: element) ?? derivedStringRawValue(for: name, rawType: rawType)
|
|
441
|
+
rawValue: writtenRawValue(of: element) ?? derivedStringRawValue(for: name, rawType: rawType),
|
|
442
|
+
platforms: platforms
|
|
368
443
|
))
|
|
369
444
|
}
|
|
370
445
|
}
|
|
@@ -380,10 +455,15 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
380
455
|
private func collectUnionMembers(_ members: MemberBlockItemListSyntax) -> [ExportedUnionMember] {
|
|
381
456
|
var result: [ExportedUnionMember] = []
|
|
382
457
|
|
|
458
|
+
// `@Union` reads only the cases written directly in the body, so `#if` is not looked into.
|
|
383
459
|
for member in members {
|
|
384
460
|
guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else {
|
|
385
461
|
continue
|
|
386
462
|
}
|
|
463
|
+
let platforms = platformResolver.platforms(of: caseDecl)
|
|
464
|
+
guard !platforms.isEmpty else {
|
|
465
|
+
continue
|
|
466
|
+
}
|
|
387
467
|
for element in caseDecl.elements {
|
|
388
468
|
guard let parameters = element.parameterClause?.parameters,
|
|
389
469
|
parameters.count == 1, let parameter = parameters.first,
|
|
@@ -391,14 +471,15 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
391
471
|
continue
|
|
392
472
|
}
|
|
393
473
|
result.append(
|
|
394
|
-
ExportedUnionMember(
|
|
474
|
+
ExportedUnionMember(
|
|
475
|
+
name: element.name.identifierName, type: typeNode(from: parameter.type), platforms: platforms))
|
|
395
476
|
}
|
|
396
477
|
}
|
|
397
478
|
return result
|
|
398
479
|
}
|
|
399
480
|
|
|
400
481
|
/// Projects a parameter clause into `ExportedParameter`s: label = first name, name = second (else
|
|
401
|
-
/// first),
|
|
482
|
+
/// first), `optional` when it has a default value or an optional type, and the default as written.
|
|
402
483
|
private func parameters(of clause: FunctionParameterClauseSyntax) -> [ExportedParameter] {
|
|
403
484
|
clause.parameters.map { parameter in
|
|
404
485
|
let label = parameter.firstName.identifierName
|
|
@@ -407,25 +488,26 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
407
488
|
label: label,
|
|
408
489
|
name: name,
|
|
409
490
|
type: typeNode(from: parameter.type),
|
|
410
|
-
isOptional: parameter.defaultValue != nil || isOptionalType(parameter.type)
|
|
491
|
+
isOptional: parameter.defaultValue != nil || isOptionalType(parameter.type),
|
|
492
|
+
defaultValue: parameter.defaultValue?.value.trimmedDescription
|
|
411
493
|
)
|
|
412
494
|
}
|
|
413
495
|
}
|
|
414
496
|
|
|
415
497
|
/// The names of the types enclosing a declaration, outermost first: empty at file scope, and `nil`
|
|
416
|
-
/// when the declaration is local to a function body or a closure
|
|
498
|
+
/// when the declaration is local to a function body or a closure.
|
|
417
499
|
///
|
|
418
|
-
/// File scope uses the same rule as `DetectionVisitor.isTopLevel
|
|
419
|
-
///
|
|
420
|
-
///
|
|
421
|
-
/// contributes its extended type as written (without backticks), so its spelling may
|
|
422
|
-
/// qualified.
|
|
500
|
+
/// File scope uses the same rule as `DetectionVisitor.isTopLevel`: the declaration's item sits in
|
|
501
|
+
/// the source file's top-level item list, directly or through `#if` clauses. A member is reached
|
|
502
|
+
/// from its type through `MemberBlockItemSyntax`, also directly or through `#if` clauses; an
|
|
503
|
+
/// `extension` contributes its extended type as written (without backticks), so its spelling may
|
|
504
|
+
/// already be qualified.
|
|
423
505
|
private func enclosingTypes(of node: some SyntaxProtocol) -> [String]? {
|
|
424
506
|
if let item = node.parent?.as(CodeBlockItemSyntax.self) {
|
|
425
|
-
return item
|
|
507
|
+
return isAtFileScope(item) ? [] : nil
|
|
426
508
|
}
|
|
427
509
|
guard let item = node.parent?.as(MemberBlockItemSyntax.self),
|
|
428
|
-
let parent = item
|
|
510
|
+
let parent = declaration(owning: item),
|
|
429
511
|
let scope = enclosingTypes(of: parent) else {
|
|
430
512
|
return nil
|
|
431
513
|
}
|
|
@@ -441,6 +523,57 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
441
523
|
}
|
|
442
524
|
}
|
|
443
525
|
|
|
526
|
+
/// True when a code block item is in the source file's top-level item list, directly or in a clause
|
|
527
|
+
/// of a top-level `#if`.
|
|
528
|
+
private func isAtFileScope(_ item: CodeBlockItemSyntax) -> Bool {
|
|
529
|
+
guard let list = item.parent?.as(CodeBlockItemListSyntax.self) else {
|
|
530
|
+
return false
|
|
531
|
+
}
|
|
532
|
+
if list.parent?.is(SourceFileSyntax.self) == true {
|
|
533
|
+
return true
|
|
534
|
+
}
|
|
535
|
+
guard let ifConfigItem = enclosingIfConfig(of: list)?.parent?.as(CodeBlockItemSyntax.self) else {
|
|
536
|
+
return false
|
|
537
|
+
}
|
|
538
|
+
return isAtFileScope(ifConfigItem)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/// The type or extension whose body holds a member, looking through the `#if` clauses the member may
|
|
542
|
+
/// be written in.
|
|
543
|
+
private func declaration(owning item: MemberBlockItemSyntax) -> Syntax? {
|
|
544
|
+
guard let list = item.parent?.as(MemberBlockItemListSyntax.self) else {
|
|
545
|
+
return nil
|
|
546
|
+
}
|
|
547
|
+
if let block = list.parent?.as(MemberBlockSyntax.self) {
|
|
548
|
+
return block.parent
|
|
549
|
+
}
|
|
550
|
+
guard let ifConfigItem = enclosingIfConfig(of: list)?.parent?.as(MemberBlockItemSyntax.self) else {
|
|
551
|
+
return nil
|
|
552
|
+
}
|
|
553
|
+
return declaration(owning: ifConfigItem)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/// The `#if` declaration whose clause holds `elements`, or `nil` when they are not in a clause.
|
|
557
|
+
private func enclosingIfConfig(of elements: some SyntaxProtocol) -> IfConfigDeclSyntax? {
|
|
558
|
+
return elements.parent?.as(IfConfigClauseSyntax.self)?.parent?.parent?.as(IfConfigDeclSyntax.self)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/// The members of a type body in source order, with the members of every clause of each `#if` in
|
|
562
|
+
/// place of the `#if`. Whether a platform builds a member is left to `PlatformResolver`.
|
|
563
|
+
private func flattened(_ members: MemberBlockItemListSyntax) -> [MemberBlockItemSyntax] {
|
|
564
|
+
return members.flatMap { member -> [MemberBlockItemSyntax] in
|
|
565
|
+
guard let ifConfig = member.decl.as(IfConfigDeclSyntax.self) else {
|
|
566
|
+
return [member]
|
|
567
|
+
}
|
|
568
|
+
return ifConfig.clauses.flatMap { clause -> [MemberBlockItemSyntax] in
|
|
569
|
+
guard case .decls(let decls) = clause.elements else {
|
|
570
|
+
return []
|
|
571
|
+
}
|
|
572
|
+
return flattened(decls)
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
444
577
|
/// A declared name qualified with the names of its enclosing types: `Options.Encoding`.
|
|
445
578
|
private func qualifiedName(_ name: String, in scope: [String]) -> String {
|
|
446
579
|
(scope + [name]).joined(separator: ".")
|
package/build/index.d.ts
CHANGED
|
@@ -6,11 +6,18 @@ export interface ScanModulesOptions {
|
|
|
6
6
|
defines?: string[];
|
|
7
7
|
/** Overrides the scanner binary path. Defaults to the one shipped with this package. */
|
|
8
8
|
binaryPath?: string;
|
|
9
|
+
/** Milliseconds after which the scanner is killed and the scan fails. No limit by default. */
|
|
10
|
+
timeout?: number;
|
|
9
11
|
}
|
|
10
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Options accepted by `scanExports`. The deep scan evaluates `#if` per platform with no flags set, so
|
|
14
|
+
* it takes no define option.
|
|
15
|
+
*/
|
|
11
16
|
export interface ScanExportsOptions {
|
|
12
17
|
/** Overrides the scanner binary path. Defaults to the one shipped with this package. */
|
|
13
18
|
binaryPath?: string;
|
|
19
|
+
/** Milliseconds after which the scanner is killed and the scan fails. No limit by default. */
|
|
20
|
+
timeout?: number;
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Thrown when the scanner exits non-zero. Exit code 2 is a usage error (bad subcommand or flags) and
|
|
@@ -34,9 +41,10 @@ export declare class ScannerSchemaVersionError extends Error {
|
|
|
34
41
|
*
|
|
35
42
|
* - macOS: the universal binary shipped in this package, next to the Swift package rather than in a
|
|
36
43
|
* `bin` directory.
|
|
37
|
-
* - Windows: `ExpoModulesMacros.exe` from the package for the
|
|
38
|
-
* `
|
|
39
|
-
* it falls back to the executable that `npm run build`
|
|
44
|
+
* - Windows and Linux: `ExpoModulesMacros.exe` or `ExpoModulesMacros` from the package for the
|
|
45
|
+
* current platform and architecture (see `platformBinaryPackageName`). When that package isn't
|
|
46
|
+
* installed, for example in this repository, it falls back to the executable that `npm run build`
|
|
47
|
+
* writes to `platforms/<platform>-<arch>`.
|
|
40
48
|
*
|
|
41
49
|
* The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
|
|
42
50
|
* get it.
|
|
@@ -54,7 +62,9 @@ export declare function scanModules(paths: string[], options?: ScanModulesOption
|
|
|
54
62
|
* Deep scan of the full JS-exported surface, for TypeScript type generation. Each path is a `.swift`
|
|
55
63
|
* file or a directory, scanned recursively.
|
|
56
64
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
65
|
+
* Each declaration and member reports the Apple OSes whose `#if` conditions and `@available`
|
|
66
|
+
* attributes include it. `#if` is read only where the runtime sees what it holds: around types,
|
|
67
|
+
* `@Event` members, and enum cases. No define option is taken: a condition on a custom flag never holds, so
|
|
68
|
+
* what it guards is not reported.
|
|
59
69
|
*/
|
|
60
70
|
export declare function scanExports(paths: string[], options?: ScanExportsOptions): Promise<ScanExportsResult>;
|