@expo/expo-modules-macros-plugin 0.10.0 → 0.12.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/resources/expo-modules-macros.svg +23 -0
- package/.github/workflows/publish.yml +4 -0
- package/.github/workflows/swift.yml +6 -0
- package/README.md +119 -0
- package/apple/ExpoModulesMacros-tool +0 -0
- package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +3 -1
- package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +10 -0
- package/apple/Sources/ExpoModulesMacros/ExpoViewMacro.swift +259 -0
- package/apple/Sources/ExpoModulesMacros/JSMacro.swift +2 -2
- package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +24 -2
- package/apple/Sources/ExpoModulesMacros/Plugin.swift +2 -0
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +0 -16
- package/apple/Sources/ExpoModulesMacros/ViewPropsMacro.swift +487 -0
- package/apple/Sources/ExpoModulesScanner/CLI.swift +8 -4
- package/apple/Sources/ExpoModulesScanner/Core/Detection.swift +6 -4
- package/apple/Sources/ExpoModulesScanner/Core/DetectionVisitor.swift +28 -7
- package/apple/Sources/ExpoModulesScanner/Core/ScanBuildConfiguration.swift +3 -9
- package/apple/Sources/ExpoModulesScanner/Core/SourceScan.swift +24 -8
- package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +133 -0
- package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +199 -0
- package/apple/Sources/ExpoModulesScanner/Exports/ScanExports.swift +25 -6
- package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +315 -6
- package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +35 -6
- package/apple/Sources/ExpoModulesScanner/Modules/ScanModules.swift +89 -22
- package/build/index.d.ts +51 -0
- package/build/index.js +153 -0
- package/build/types.d.ts +264 -0
- package/build/types.js +15 -0
- package/package.json +12 -2
|
@@ -2,14 +2,17 @@ import Foundation
|
|
|
2
2
|
import SwiftParser
|
|
3
3
|
import SwiftSyntax
|
|
4
4
|
|
|
5
|
-
/// Walks `paths`, and for each `.swift` file that might contain one of `macros`
|
|
6
|
-
/// it), reads the source and hands it to `process` along with the
|
|
5
|
+
/// Walks `paths`, and for each `.swift` file that might contain one of `macros` or one of
|
|
6
|
+
/// `conformances` (the pre-filter passes it), reads the source and hands it to `process` along with the
|
|
7
|
+
/// file path. Returns the run's stats.
|
|
8
|
+
///
|
|
7
9
|
/// The shared core every scan command builds on: the walk, read, pre-filter, and stats are identical;
|
|
8
10
|
/// only what each command does per parsed file differs (`scan-modules` collects `Detection`s,
|
|
9
11
|
/// `scan-exports` walks a `SurfaceVisitor`), and that lives in `process`.
|
|
10
12
|
func scanFiles(
|
|
11
13
|
paths: [String],
|
|
12
14
|
macros: Set<DetectedMacro>,
|
|
15
|
+
conformances: Set<String> = [],
|
|
13
16
|
process: (_ source: String, _ file: String) -> Void
|
|
14
17
|
) -> ScanStats {
|
|
15
18
|
let clock = ContinuousClock()
|
|
@@ -19,7 +22,7 @@ func scanFiles(
|
|
|
19
22
|
var filesParsed = 0
|
|
20
23
|
|
|
21
24
|
// Compile the pre-filter regex once per run, not once per file.
|
|
22
|
-
let prefilter = macroAttributeRegex(for: macros)
|
|
25
|
+
let prefilter = macroAttributeRegex(for: macros, conformances: conformances)
|
|
23
26
|
|
|
24
27
|
for file in swiftFiles(in: paths) {
|
|
25
28
|
guard let source = try? String(contentsOfFile: file, encoding: .utf8) else {
|
|
@@ -50,7 +53,7 @@ func scanFiles(
|
|
|
50
53
|
func collectDetections(
|
|
51
54
|
paths: [String],
|
|
52
55
|
macros: Set<DetectedMacro>,
|
|
53
|
-
configuration: ScanBuildConfiguration =
|
|
56
|
+
configuration: ScanBuildConfiguration? = nil
|
|
54
57
|
) -> (detections: [Detection], warnings: [ScanWarning], stats: ScanStats) {
|
|
55
58
|
var detections: [Detection] = []
|
|
56
59
|
var warnings: [ScanWarning] = []
|
|
@@ -68,7 +71,7 @@ func detect(
|
|
|
68
71
|
source: String,
|
|
69
72
|
file: String,
|
|
70
73
|
macros: Set<DetectedMacro>,
|
|
71
|
-
configuration: ScanBuildConfiguration
|
|
74
|
+
configuration: ScanBuildConfiguration?
|
|
72
75
|
) -> (detections: [Detection], warnings: [ScanWarning]) {
|
|
73
76
|
let tree = Parser.parse(source: source)
|
|
74
77
|
let visitor = DetectionVisitor(file: file, tree: tree, detectedMacros: macros, configuration: configuration)
|
|
@@ -82,10 +85,23 @@ func detect(
|
|
|
82
85
|
/// `@(ExpoModule|JS|Record|SharedObject)` for an `exports` scan. A precompiled `NSRegularExpression`
|
|
83
86
|
/// benchmarked ~20x faster over a large source tree than calling `String.contains` once per macro
|
|
84
87
|
/// name, because it scans each file in a single pass. Compiled once per run and reused per file.
|
|
85
|
-
|
|
88
|
+
///
|
|
89
|
+
/// `conformances` adds bare (unprefixed) alternatives for types recognized by conformance rather than
|
|
90
|
+
/// by an attribute (`Enumerable` for `scan-exports`). They join the same alternation so the scan stays
|
|
91
|
+
/// one pass; a bare name matches more loosely than an `@`-prefixed one, which costs a wasted parse and
|
|
92
|
+
/// never a miss.
|
|
93
|
+
func macroAttributeRegex(
|
|
94
|
+
for macros: Set<DetectedMacro>,
|
|
95
|
+
conformances: Set<String> = []
|
|
96
|
+
) -> NSRegularExpression {
|
|
86
97
|
// Sort for a stable pattern regardless of the set's iteration order.
|
|
87
|
-
let
|
|
88
|
-
|
|
98
|
+
let attributes = macros.map(\.rawValue).sorted().joined(separator: "|")
|
|
99
|
+
var alternatives: [String] = []
|
|
100
|
+
if !attributes.isEmpty {
|
|
101
|
+
alternatives.append("@(\(attributes))")
|
|
102
|
+
}
|
|
103
|
+
alternatives.append(contentsOf: conformances.sorted())
|
|
104
|
+
return try! NSRegularExpression(pattern: alternatives.joined(separator: "|"))
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
/// True if the source text contains one of the pre-filter's spelled macro attributes, so it's worth
|
|
@@ -128,6 +128,36 @@ struct ExportedRecordProperty: Encodable, Equatable {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/// One `@Event var` on a module or shared object: a typed event JS listens for with
|
|
132
|
+
/// `addListener(jsName, …)` rather than calls.
|
|
133
|
+
struct ExportedEvent: Encodable, Equatable {
|
|
134
|
+
/// The Swift property name, e.g. `onStatusChange`.
|
|
135
|
+
let name: String
|
|
136
|
+
|
|
137
|
+
/// The name JS listens under: the `@Event("x")` override, else `name` with a conventional `on`
|
|
138
|
+
/// prefix stripped and decapitalized (`onStatusChange` -> `statusChange`).
|
|
139
|
+
let jsName: String
|
|
140
|
+
|
|
141
|
+
/// The single payload parameter's type, or `nil` for a no-payload `() -> Void` event.
|
|
142
|
+
let payload: TypeNode?
|
|
143
|
+
|
|
144
|
+
/// `@Event(sync: true)`, dispatching inline on the JS thread instead of scheduling.
|
|
145
|
+
let isSync: Bool
|
|
146
|
+
|
|
147
|
+
private enum CodingKeys: String, CodingKey {
|
|
148
|
+
case name, jsName, payload
|
|
149
|
+
case isSync = "sync"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func encode(to encoder: Encoder) throws {
|
|
153
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
154
|
+
try container.encode(name, forKey: .name)
|
|
155
|
+
try container.encode(jsName, forKey: .jsName)
|
|
156
|
+
try container.encodeIfPresent(payload, forKey: .payload)
|
|
157
|
+
try container.encode(isSync, forKey: .isSync)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
/// A `@ExpoModule` type and its `@JS` surface.
|
|
132
162
|
struct ExportedModule: Encodable, Equatable {
|
|
133
163
|
/// The Swift class name.
|
|
@@ -138,6 +168,7 @@ struct ExportedModule: Encodable, Equatable {
|
|
|
138
168
|
|
|
139
169
|
let functions: [ExportedFunction]
|
|
140
170
|
let properties: [ExportedProperty]
|
|
171
|
+
let events: [ExportedEvent]
|
|
141
172
|
|
|
142
173
|
/// Absolute source path, matching `scan-modules`.
|
|
143
174
|
let file: String
|
|
@@ -156,6 +187,7 @@ struct ExportedSharedObject: Encodable, Equatable {
|
|
|
156
187
|
|
|
157
188
|
let functions: [ExportedFunction]
|
|
158
189
|
let properties: [ExportedProperty]
|
|
190
|
+
let events: [ExportedEvent]
|
|
159
191
|
|
|
160
192
|
let file: String
|
|
161
193
|
}
|
|
@@ -170,17 +202,118 @@ struct ExportedRecord: Encodable, Equatable {
|
|
|
170
202
|
let file: String
|
|
171
203
|
}
|
|
172
204
|
|
|
205
|
+
/// One case of a reported enum.
|
|
206
|
+
///
|
|
207
|
+
/// What `rawValue` carries depends on the enum's raw type, and a consumer reads it that way:
|
|
208
|
+
/// - `String`: always present, and **decoded** (`case active = "act"` reports `act`, unquoted). A case
|
|
209
|
+
/// writing none takes its own name, so nothing is left to derive.
|
|
210
|
+
/// - an integer type: present only where written, as **source text** (`1`, `1 << 3`), since the value
|
|
211
|
+
/// may be an expression a syntactic scan can't evaluate. Swift also continues from the preceding
|
|
212
|
+
/// case's value (`case a = 1; case b` makes `b` 2), and that carry is the consumer's to apply.
|
|
213
|
+
/// - no raw type: always absent, since the enum has no raw values.
|
|
214
|
+
struct ExportedEnumCase: Encodable, Equatable {
|
|
215
|
+
/// The case name as declared.
|
|
216
|
+
let name: String
|
|
217
|
+
|
|
218
|
+
/// The raw value, decoded for a string and verbatim source text otherwise, or `nil` per the rule
|
|
219
|
+
/// above.
|
|
220
|
+
let rawValue: String?
|
|
221
|
+
|
|
222
|
+
private enum CodingKeys: String, CodingKey {
|
|
223
|
+
case name, rawValue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func encode(to encoder: Encoder) throws {
|
|
227
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
228
|
+
try container.encode(name, forKey: .name)
|
|
229
|
+
try container.encodeIfPresent(rawValue, forKey: .rawValue)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// An `Enumerable` enum: a type that crosses the boundary as its raw value rather than as an object.
|
|
234
|
+
/// Detected by conformance, not by a macro attribute, because core converts it through its
|
|
235
|
+
/// `RawRepresentable`/`Enumerable` conformance (`Coding/…+Enumerable`) with no macro involved.
|
|
236
|
+
struct ExportedEnum: Encodable, Equatable {
|
|
237
|
+
/// The Swift enum name. No `jsName`: an enum carries no macro to spell an override on, and it
|
|
238
|
+
/// reaches JS as its raw values, not under a bound name.
|
|
239
|
+
let name: String
|
|
240
|
+
|
|
241
|
+
/// The raw value type as written (`String`, `Int`, …), or `nil` for a bare `Enumerable` conformance
|
|
242
|
+
/// with no raw type. A `nil` raw type is reported as-is rather than dropped: the enum is still
|
|
243
|
+
/// declared convertible, and the consumer decides how to treat it.
|
|
244
|
+
let rawType: TypeNode?
|
|
245
|
+
|
|
246
|
+
let cases: [ExportedEnumCase]
|
|
247
|
+
|
|
248
|
+
let file: String
|
|
249
|
+
|
|
250
|
+
private enum CodingKeys: String, CodingKey {
|
|
251
|
+
case name, rawType, cases, file
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
func encode(to encoder: Encoder) throws {
|
|
255
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
256
|
+
try container.encode(name, forKey: .name)
|
|
257
|
+
try container.encodeIfPresent(rawType, forKey: .rawType)
|
|
258
|
+
try container.encode(cases, forKey: .cases)
|
|
259
|
+
try container.encode(file, forKey: .file)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// One alternative a `@Union` may hold: the Swift case name plus its payload type. Not a member in the
|
|
264
|
+
/// sense a module or shared object has members (functions, properties, events).
|
|
265
|
+
struct ExportedUnionMember: Encodable, Equatable {
|
|
266
|
+
/// The case name as declared. Swift-side only: discrimination is structural, so it never reaches JS,
|
|
267
|
+
/// but it ties an alternative back to the declaration.
|
|
268
|
+
let name: String
|
|
269
|
+
|
|
270
|
+
/// The associated value's type: what this alternative decodes from. Named `type` like every other
|
|
271
|
+
/// type-valued field here; `payload` already means an event's argument type on `ExportedEvent`.
|
|
272
|
+
let type: TypeNode
|
|
273
|
+
|
|
274
|
+
private enum CodingKeys: String, CodingKey {
|
|
275
|
+
case name, type
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// A `@Union` enum: a typed union of its cases' payload types (`A | B | C` in TypeScript).
|
|
280
|
+
///
|
|
281
|
+
/// **`members` is ordered, and the order is part of the contract.** Decode tries each payload in
|
|
282
|
+
/// declaration order and takes the first that succeeds, so where two shapes overlap (`Int` and
|
|
283
|
+
/// `Double`, two compatible records) the earlier case wins. Reordering them describes a different
|
|
284
|
+
/// union than the one the module runs.
|
|
285
|
+
struct ExportedUnion: Encodable, Equatable {
|
|
286
|
+
/// The Swift enum name. No `jsName`: `@Union` takes no arguments, and a union reaches JS as its
|
|
287
|
+
/// payload types rather than under a bound name.
|
|
288
|
+
let name: String
|
|
289
|
+
|
|
290
|
+
let members: [ExportedUnionMember]
|
|
291
|
+
|
|
292
|
+
let file: String
|
|
293
|
+
}
|
|
294
|
+
|
|
173
295
|
/// The exported types grouped by kind, nested under `exports` in the result so the surface is one
|
|
174
296
|
/// self-contained object separate from `stats`.
|
|
175
297
|
struct ExportedSurface: Encodable, Equatable {
|
|
176
298
|
let modules: [ExportedModule]
|
|
177
299
|
let sharedObjects: [ExportedSharedObject]
|
|
178
300
|
let records: [ExportedRecord]
|
|
301
|
+
let enums: [ExportedEnum]
|
|
302
|
+
let unions: [ExportedUnion]
|
|
179
303
|
}
|
|
180
304
|
|
|
305
|
+
/// Version of the `scan-exports` output shape. Bumped on any breaking change to the envelope or to
|
|
306
|
+
/// anything under `exports`, so a consumer can verify it understands the output before trusting it.
|
|
307
|
+
/// Versioned independently of `scanModulesSchemaVersion`: the two commands serve different consumers
|
|
308
|
+
/// and change for different reasons. Version 2 added `events` to modules and shared objects; version 3
|
|
309
|
+
/// added `enums`; version 4 added `unions`; version 5 resolves refs, adding `refKind` and correcting
|
|
310
|
+
/// an enum ref's `typeof`.
|
|
311
|
+
let scanExportsSchemaVersion = 5
|
|
312
|
+
|
|
181
313
|
/// The `scan-exports` result: the surface plus the run's stats. A distinct envelope from
|
|
182
314
|
/// `ScanModulesResult` (different consumer: TS generation vs. autolinking).
|
|
183
315
|
struct ScanExportsResult: Encodable, Equatable {
|
|
316
|
+
let schemaVersion: Int
|
|
184
317
|
let exports: ExportedSurface
|
|
185
318
|
let stats: ScanStats
|
|
186
319
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Fills in each `.ref`'s `refKind` once the whole scan is done.
|
|
4
|
+
///
|
|
5
|
+
/// A `TypeNode` is parsed from one `TypeSyntax` in isolation, so at parse time a name like `Status` is
|
|
6
|
+
/// just a name: the parser has no idea whether the scan will turn up an `@Record`, an `Enumerable`
|
|
7
|
+
/// enum, or nothing at all. Resolution is therefore a second pass over the collected surface, run once
|
|
8
|
+
/// every file has been walked.
|
|
9
|
+
///
|
|
10
|
+
/// Two things change on a resolved ref:
|
|
11
|
+
/// - `refKind` names which kind declares it, so a consumer doesn't repeat this lookup.
|
|
12
|
+
/// - the `typeof` category is corrected for an enum, which crosses as its raw value rather than as an
|
|
13
|
+
/// object. That correction is the reason this can't be left to the consumer: the scanner is the only
|
|
14
|
+
/// side that knows the enum's `rawType`.
|
|
15
|
+
///
|
|
16
|
+
/// A name the scan never declared keeps `refKind: nil` and its `object` category. Not an error: it is
|
|
17
|
+
/// a platform convertible (`CGPoint`, `URL`) or a type from another module, and the consumer's own
|
|
18
|
+
/// catalog decides.
|
|
19
|
+
|
|
20
|
+
/// The declared names of a scanned surface, each mapped to how it should be reported at a use site.
|
|
21
|
+
/// Built once per scan and used for every ref lookup.
|
|
22
|
+
struct RefIndex {
|
|
23
|
+
/// `name` -> the kind declaring it, plus the JS category a ref to it crosses as.
|
|
24
|
+
private var entries: [String: (kind: RefKind, jsType: JSType)] = [:]
|
|
25
|
+
|
|
26
|
+
init(surface: ExportedSurface) {
|
|
27
|
+
for record in surface.records {
|
|
28
|
+
entries[record.name] = (.record, .object)
|
|
29
|
+
}
|
|
30
|
+
for sharedObject in surface.sharedObjects {
|
|
31
|
+
entries[sharedObject.name] = (.sharedObject, .object)
|
|
32
|
+
}
|
|
33
|
+
for union in surface.unions {
|
|
34
|
+
// A union crosses as whichever alternative matched, so its category is only meaningful per
|
|
35
|
+
// member. `object` is the honest coarse answer; the consumer reads `members` for the real shape.
|
|
36
|
+
entries[union.name] = (.union, .object)
|
|
37
|
+
}
|
|
38
|
+
for enumeration in surface.enums {
|
|
39
|
+
entries[enumeration.name] = (.enum, jsType(ofRawType: enumeration.rawType))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// How a ref to `name` should be reported, or `nil` when the scan declares no such type.
|
|
44
|
+
func lookup(_ name: String) -> (kind: RefKind, jsType: JSType)? {
|
|
45
|
+
if let entry = entries[name] {
|
|
46
|
+
return entry
|
|
47
|
+
}
|
|
48
|
+
// A qualified use site (`Media.Status`) names the same type a bare declaration did. Fall back to
|
|
49
|
+
// the trailing component, which is how the conformance and raw-type checks already match names.
|
|
50
|
+
guard let trailing = name.split(separator: ".").last.map(String.init), trailing != name else {
|
|
51
|
+
return nil
|
|
52
|
+
}
|
|
53
|
+
return entries[trailing]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// The JS category a raw-value enum crosses as: its raw value's. A bare `Enumerable` conformance with
|
|
58
|
+
/// no raw type has nothing to go on, so it stays an object.
|
|
59
|
+
private func jsType(ofRawType rawType: TypeNode?) -> JSType {
|
|
60
|
+
guard let rawType, let jsType = rawType.jsType else {
|
|
61
|
+
return .object
|
|
62
|
+
}
|
|
63
|
+
return jsType
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
extension TypeNode {
|
|
67
|
+
/// This node with every `.ref` in it resolved against `index`, recursively. Returns an unchanged
|
|
68
|
+
/// node when nothing in it resolves.
|
|
69
|
+
func resolvingRefs(using index: RefIndex) -> TypeNode {
|
|
70
|
+
switch self {
|
|
71
|
+
case .primitive, .unknown:
|
|
72
|
+
return self
|
|
73
|
+
case .ref(let name, _, _):
|
|
74
|
+
guard let entry = index.lookup(name) else {
|
|
75
|
+
return self
|
|
76
|
+
}
|
|
77
|
+
// Only an enum crosses as something other than an object, so only it carries an override.
|
|
78
|
+
return .ref(
|
|
79
|
+
name: name, refKind: entry.kind, jsTypeOverride: entry.jsType == .object ? nil : entry.jsType)
|
|
80
|
+
case .optional(let wrapped):
|
|
81
|
+
return .optional(wrapped: wrapped.resolvingRefs(using: index))
|
|
82
|
+
case .array(let element):
|
|
83
|
+
return .array(element: element.resolvingRefs(using: index))
|
|
84
|
+
case .dictionary(let key, let value):
|
|
85
|
+
return .dictionary(
|
|
86
|
+
key: key.resolvingRefs(using: index), value: value.resolvingRefs(using: index))
|
|
87
|
+
case .promise(let value):
|
|
88
|
+
return .promise(value: value.resolvingRefs(using: index))
|
|
89
|
+
case .function(let parameters, let returns, let isAsync, let isThrowing):
|
|
90
|
+
return .function(
|
|
91
|
+
parameters: parameters.map { $0.resolvingRefs(using: index) },
|
|
92
|
+
returns: returns?.resolvingRefs(using: index),
|
|
93
|
+
isAsync: isAsync,
|
|
94
|
+
isThrowing: isThrowing
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
extension ExportedSurface {
|
|
101
|
+
/// This surface with every `.ref` in every reported type resolved against its own declarations. The
|
|
102
|
+
/// second pass `scanExports` runs once the walk is done.
|
|
103
|
+
///
|
|
104
|
+
/// Only the boundary types are rewritten; names, flags, and ordering are untouched. The declared
|
|
105
|
+
/// types (a record's own name, a union's members) are not themselves refs, so they carry no
|
|
106
|
+
/// `refKind`: it is the *use sites* that gain one.
|
|
107
|
+
func resolvingRefs() -> ExportedSurface {
|
|
108
|
+
let index = RefIndex(surface: self)
|
|
109
|
+
return ExportedSurface(
|
|
110
|
+
modules: modules.map { module in
|
|
111
|
+
ExportedModule(
|
|
112
|
+
name: module.name,
|
|
113
|
+
jsName: module.jsName,
|
|
114
|
+
functions: module.functions.map { $0.resolvingRefs(using: index) },
|
|
115
|
+
properties: module.properties.map { $0.resolvingRefs(using: index) },
|
|
116
|
+
events: module.events.map { $0.resolvingRefs(using: index) },
|
|
117
|
+
file: module.file
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
sharedObjects: sharedObjects.map { sharedObject in
|
|
121
|
+
ExportedSharedObject(
|
|
122
|
+
name: sharedObject.name,
|
|
123
|
+
jsName: sharedObject.jsName,
|
|
124
|
+
constructorParameters: sharedObject.constructorParameters?.map { $0.resolvingRefs(using: index) },
|
|
125
|
+
functions: sharedObject.functions.map { $0.resolvingRefs(using: index) },
|
|
126
|
+
properties: sharedObject.properties.map { $0.resolvingRefs(using: index) },
|
|
127
|
+
events: sharedObject.events.map { $0.resolvingRefs(using: index) },
|
|
128
|
+
file: sharedObject.file
|
|
129
|
+
)
|
|
130
|
+
},
|
|
131
|
+
records: records.map { record in
|
|
132
|
+
ExportedRecord(
|
|
133
|
+
name: record.name,
|
|
134
|
+
properties: record.properties.map { property in
|
|
135
|
+
ExportedRecordProperty(
|
|
136
|
+
name: property.name,
|
|
137
|
+
type: property.type.resolvingRefs(using: index),
|
|
138
|
+
isOptional: property.isOptional,
|
|
139
|
+
hasDefault: property.hasDefault
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
file: record.file
|
|
143
|
+
)
|
|
144
|
+
},
|
|
145
|
+
// An enum's raw type is a primitive or an unscanned spelling, never a ref to another scanned
|
|
146
|
+
// type, so there is nothing in one to resolve.
|
|
147
|
+
enums: enums,
|
|
148
|
+
unions: unions.map { union in
|
|
149
|
+
ExportedUnion(
|
|
150
|
+
name: union.name,
|
|
151
|
+
members: union.members.map { member in
|
|
152
|
+
ExportedUnionMember(name: member.name, type: member.type.resolvingRefs(using: index))
|
|
153
|
+
},
|
|
154
|
+
file: union.file
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
extension ExportedParameter {
|
|
162
|
+
fileprivate func resolvingRefs(using index: RefIndex) -> ExportedParameter {
|
|
163
|
+
return ExportedParameter(
|
|
164
|
+
label: label, name: name, type: type.resolvingRefs(using: index), isOptional: isOptional)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
extension ExportedFunction {
|
|
169
|
+
fileprivate func resolvingRefs(using index: RefIndex) -> ExportedFunction {
|
|
170
|
+
return ExportedFunction(
|
|
171
|
+
name: name,
|
|
172
|
+
jsName: jsName,
|
|
173
|
+
parameters: parameters.map { $0.resolvingRefs(using: index) },
|
|
174
|
+
returns: returns?.resolvingRefs(using: index),
|
|
175
|
+
isAsync: isAsync,
|
|
176
|
+
isThrowing: isThrowing,
|
|
177
|
+
isStatic: isStatic
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
extension ExportedProperty {
|
|
183
|
+
fileprivate func resolvingRefs(using index: RefIndex) -> ExportedProperty {
|
|
184
|
+
return ExportedProperty(
|
|
185
|
+
name: name,
|
|
186
|
+
jsName: jsName,
|
|
187
|
+
type: type?.resolvingRefs(using: index),
|
|
188
|
+
isSettable: isSettable,
|
|
189
|
+
isStatic: isStatic
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
extension ExportedEvent {
|
|
195
|
+
fileprivate func resolvingRefs(using index: RefIndex) -> ExportedEvent {
|
|
196
|
+
return ExportedEvent(
|
|
197
|
+
name: name, jsName: jsName, payload: payload?.resolvingRefs(using: index), isSync: isSync)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -22,26 +22,45 @@ extension Scanner {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/// Scans `paths` for `@ExpoModule`, `@SharedObject`, and `@
|
|
26
|
-
/// surface plus the run's stats. Separate from the public entry so
|
|
27
|
-
///
|
|
28
|
-
///
|
|
25
|
+
/// Scans `paths` for `@ExpoModule`, `@SharedObject`, `@Record`, and `@Union` types plus `Enumerable`
|
|
26
|
+
/// enums, and returns their exported surface plus the run's stats. Separate from the public entry so
|
|
27
|
+
/// tests can drive it without argv/stdout.
|
|
28
|
+
///
|
|
29
|
+
/// The pre-filter omits `@Event`: an event only declares a member of one of these three types, so a
|
|
30
|
+
/// file containing one already matches on its enclosing type. It does include `Enumerable`, which is a
|
|
31
|
+
/// conformance rather than an attribute: an enum is commonly declared in a file of its own, which no
|
|
32
|
+
/// macro attribute would match.
|
|
29
33
|
func scanExports(paths: [String]) -> ScanExportsResult {
|
|
30
34
|
var modules: [ExportedModule] = []
|
|
31
35
|
var sharedObjects: [ExportedSharedObject] = []
|
|
32
36
|
var records: [ExportedRecord] = []
|
|
37
|
+
var enums: [ExportedEnum] = []
|
|
38
|
+
var unions: [ExportedUnion] = []
|
|
33
39
|
|
|
34
|
-
let stats = scanFiles(
|
|
40
|
+
let stats = scanFiles(
|
|
41
|
+
paths: paths,
|
|
42
|
+
macros: [.expoModule, .sharedObject, .record, .union],
|
|
43
|
+
conformances: [enumerableConformanceName]
|
|
44
|
+
) { source, file in
|
|
35
45
|
let tree = Parser.parse(source: source)
|
|
36
46
|
let visitor = SurfaceVisitor(file: file)
|
|
37
47
|
visitor.walk(tree)
|
|
38
48
|
modules.append(contentsOf: visitor.modules)
|
|
39
49
|
sharedObjects.append(contentsOf: visitor.sharedObjects)
|
|
40
50
|
records.append(contentsOf: visitor.records)
|
|
51
|
+
enums.append(contentsOf: visitor.enums)
|
|
52
|
+
unions.append(contentsOf: visitor.unions)
|
|
41
53
|
}
|
|
42
54
|
|
|
55
|
+
// Refs resolve only once every file has been walked: a type parsed in one file may name a type
|
|
56
|
+
// declared in another, so the full set of declarations has to exist before any lookup is valid.
|
|
57
|
+
let surface = ExportedSurface(
|
|
58
|
+
modules: modules, sharedObjects: sharedObjects, records: records, enums: enums, unions: unions
|
|
59
|
+
).resolvingRefs()
|
|
60
|
+
|
|
43
61
|
return ScanExportsResult(
|
|
44
|
-
|
|
62
|
+
schemaVersion: scanExportsSchemaVersion,
|
|
63
|
+
exports: surface,
|
|
45
64
|
stats: stats
|
|
46
65
|
)
|
|
47
66
|
}
|