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.
@@ -0,0 +1,259 @@
1
+ import SwiftIfConfig
2
+ import SwiftSyntax
3
+
4
+ /// The Apple OSes `scan-exports` attributes declarations to, in the order they are reported. Spelled
5
+ /// as `os(...)` spells them.
6
+ let applePlatforms = ["iOS", "macOS", "tvOS", "watchOS", "visionOS"]
7
+
8
+ /// The platform names `@available` accepts for each reported OS, including the legacy `OSX`. Other
9
+ /// names (`iOSApplicationExtension`, `macCatalyst`, `swift`, …) do not change where a declaration is
10
+ /// built for an app, so they are ignored.
11
+ private let availabilityPlatformNames: [String: String] = [
12
+ "iOS": "iOS",
13
+ "macOS": "macOS",
14
+ "OSX": "macOS",
15
+ "tvOS": "tvOS",
16
+ "watchOS": "watchOS",
17
+ "visionOS": "visionOS",
18
+ ]
19
+
20
+ /// Answers which platforms build a declaration in one file: those whose `#if` evaluation keeps it,
21
+ /// less those an `@available` attribute on it or on an enclosing type makes unavailable. The `#if`
22
+ /// regions are evaluated once per platform for the whole file. Each platform also carries the
23
+ /// deprecation the declaration's own `@available` attributes give it there.
24
+ ///
25
+ /// A condition the scan cannot answer (`#if DEBUG`, `targetEnvironment`, a non-SDK `canImport`)
26
+ /// makes its region inactive on every platform, so a declaration under one is built nowhere.
27
+ struct PlatformResolver {
28
+ private let regions: [(platform: String, regions: ConfiguredRegions)]
29
+
30
+ init(tree: SourceFileSyntax) {
31
+ regions = applePlatforms.map { platform in
32
+ (platform, tree.configuredRegions(in: ScanBuildConfiguration(platform: platform, defines: [])))
33
+ }
34
+ }
35
+
36
+ /// The platforms that build `node`, in `applePlatforms` order. Empty when none does.
37
+ func platforms(of node: some SyntaxProtocol) -> [ExportedPlatform] {
38
+ let availability = Availability(of: node)
39
+ return regions.compactMap { platform, regions in
40
+ guard regions.isActive(node) == .active, !availability.isUnavailable(on: platform) else {
41
+ return nil
42
+ }
43
+ return ExportedPlatform(
44
+ name: platform,
45
+ introduced: availability.introduced[platform],
46
+ deprecated: availability.deprecation(on: platform)
47
+ )
48
+ }
49
+ }
50
+ }
51
+
52
+ /// What the `@available` attributes on a declaration and its enclosing declarations say. A member
53
+ /// cannot be more available than the type it is declared in, so the attributes combine: a platform
54
+ /// any of them makes unavailable is unavailable, and the latest introduced version wins.
55
+ /// Deprecations are read from the declaration's own attributes only.
56
+ private struct Availability {
57
+ private var unavailable: Set<String> = []
58
+ private var isUnavailableEverywhere = false
59
+ private(set) var introduced: [String: String] = [:]
60
+ /// The deprecation from `@available(*, deprecated, ...)`, which applies on every platform.
61
+ private var deprecatedEverywhere: ExportedDeprecation?
62
+ /// The deprecations from attributes that name a platform.
63
+ private var deprecated: [String: ExportedDeprecation] = [:]
64
+
65
+ init(of node: some SyntaxProtocol) {
66
+ var current: Syntax? = Syntax(node)
67
+ var isOwn = true
68
+ while let decl = current {
69
+ if let attributes = decl.asProtocol(WithAttributesSyntax.self)?.attributes {
70
+ for element in attributes {
71
+ if let attribute = element.as(AttributeSyntax.self),
72
+ attribute.attributeName.trimmedDescription == "available",
73
+ case .availability(let arguments) = attribute.arguments {
74
+ read(arguments, readsDeprecation: isOwn)
75
+ }
76
+ }
77
+ }
78
+ isOwn = false
79
+ current = decl.parent
80
+ }
81
+ }
82
+
83
+ func isUnavailable(on platform: String) -> Bool {
84
+ return isUnavailableEverywhere || unavailable.contains(platform)
85
+ }
86
+
87
+ /// The deprecation on `platform`: the platform's own, with any field it leaves out taken from the
88
+ /// `*` one.
89
+ func deprecation(on platform: String) -> ExportedDeprecation? {
90
+ switch (deprecated[platform], deprecatedEverywhere) {
91
+ case (nil, let everywhere):
92
+ return everywhere
93
+ case (let own?, nil):
94
+ return own
95
+ case (let own?, let everywhere?):
96
+ return own.filling(from: everywhere)
97
+ }
98
+ }
99
+
100
+ /// Reads one attribute. The long form names one platform (or `*`) first and then what applies to
101
+ /// it: `@available(iOS, introduced: 16)`, `@available(tvOS, unavailable)`,
102
+ /// `@available(*, deprecated, renamed: "other")`. The shorthand lists platforms with versions:
103
+ /// `@available(iOS 17, macOS 14, *)`.
104
+ ///
105
+ /// A long-form attribute deprecates its platform when it writes `deprecated` (bare or with a
106
+ /// version) or `obsoleted:`. `message:` and `renamed:` describe that deprecation, and are ignored
107
+ /// when the attribute deprecates nothing.
108
+ private mutating func read(_ arguments: AvailabilityArgumentListSyntax, readsDeprecation: Bool) {
109
+ guard case .token(let first) = arguments.first?.argument else {
110
+ for argument in arguments {
111
+ if case .availabilityVersionRestriction(let restriction) = argument.argument,
112
+ let platform = availabilityPlatformNames[restriction.platform.text],
113
+ let version = restriction.version {
114
+ raiseIntroduced(on: platform, to: version.trimmedDescription)
115
+ }
116
+ }
117
+ return
118
+ }
119
+ let isWildcard = first.tokenKind == .binaryOperator("*")
120
+ let platform = availabilityPlatformNames[first.text]
121
+ guard isWildcard || platform != nil else {
122
+ return
123
+ }
124
+ var deprecation = ExportedDeprecation()
125
+ var isDeprecated = false
126
+ for argument in arguments.dropFirst() {
127
+ switch argument.argument {
128
+ case .token(let token) where token.text == "unavailable":
129
+ if let platform {
130
+ unavailable.insert(platform)
131
+ } else {
132
+ isUnavailableEverywhere = true
133
+ }
134
+ case .token(let token) where token.text == "deprecated":
135
+ isDeprecated = true
136
+ case .availabilityLabeledArgument(let labeled):
137
+ switch (labeled.label.text, labeled.value) {
138
+ case ("introduced", .version(let version)):
139
+ if let platform {
140
+ raiseIntroduced(on: platform, to: version.trimmedDescription)
141
+ }
142
+ case ("deprecated", .version(let version)):
143
+ isDeprecated = true
144
+ deprecation.since = version.trimmedDescription
145
+ case ("obsoleted", .version(let version)):
146
+ isDeprecated = true
147
+ deprecation.obsoleted = version.trimmedDescription
148
+ case ("message", .string(let literal)):
149
+ deprecation.message = decodedText(of: literal)
150
+ case ("renamed", .string(let literal)):
151
+ deprecation.renamed = decodedText(of: literal)
152
+ default:
153
+ continue
154
+ }
155
+ default:
156
+ continue
157
+ }
158
+ }
159
+ guard readsDeprecation, isDeprecated else {
160
+ return
161
+ }
162
+ if let platform {
163
+ deprecated[platform] = deprecated[platform].map { deprecation.filling(from: $0) } ?? deprecation
164
+ } else {
165
+ deprecatedEverywhere = deprecatedEverywhere.map { deprecation.filling(from: $0) } ?? deprecation
166
+ }
167
+ }
168
+
169
+ private mutating func raiseIntroduced(on platform: String, to version: String) {
170
+ if let current = introduced[platform], !isVersion(version, laterThan: current) {
171
+ return
172
+ }
173
+ introduced[platform] = version
174
+ }
175
+ }
176
+
177
+ extension ExportedDeprecation {
178
+ /// This deprecation with each field it leaves out taken from `other`.
179
+ fileprivate func filling(from other: ExportedDeprecation) -> ExportedDeprecation {
180
+ return ExportedDeprecation(
181
+ since: since ?? other.since,
182
+ obsoleted: obsoleted ?? other.obsoleted,
183
+ message: message ?? other.message,
184
+ renamed: renamed ?? other.renamed
185
+ )
186
+ }
187
+ }
188
+
189
+ /// The value of a string literal in an attribute, with its escape sequences decoded: `\"` reads as
190
+ /// `"`, `\n` as a line break, and `\u{2192}` as `→`. An escaped line break in a multi-line literal
191
+ /// joins the lines, as in Swift. An escape that isn't valid is kept as written.
192
+ private func decodedText(of literal: SimpleStringLiteralExprSyntax) -> String {
193
+ let text = literal.segments.map(\.content.text).joined()
194
+ var result = ""
195
+ var index = text.startIndex
196
+ while index < text.endIndex {
197
+ let character = text[index]
198
+ index = text.index(after: index)
199
+ guard character == "\\", index < text.endIndex else {
200
+ result.append(character)
201
+ continue
202
+ }
203
+ let escaped = text[index]
204
+ index = text.index(after: index)
205
+ switch escaped {
206
+ case "0":
207
+ result.append("\0")
208
+ case "n":
209
+ result.append("\n")
210
+ case "r":
211
+ result.append("\r")
212
+ case "t":
213
+ result.append("\t")
214
+ case "\"", "'", "\\":
215
+ result.append(escaped)
216
+ case "\n", "\r\n":
217
+ continue
218
+ case "u":
219
+ if text[index...].hasPrefix("{"), let close = text[index...].firstIndex(of: "}"),
220
+ let scalarValue = UInt32(text[text.index(after: index)..<close], radix: 16),
221
+ let scalar = Unicode.Scalar(scalarValue) {
222
+ result.unicodeScalars.append(scalar)
223
+ index = text.index(after: close)
224
+ } else {
225
+ result.append("\\u")
226
+ }
227
+ default:
228
+ result.append("\\")
229
+ result.append(escaped)
230
+ }
231
+ }
232
+ return result
233
+ }
234
+
235
+ /// Compares two dotted version spellings component by component, treating a missing component as
236
+ /// `0`, so `17` and `17.0` are equal.
237
+ private func isVersion(_ lhs: String, laterThan rhs: String) -> Bool {
238
+ let left = lhs.split(separator: ".").map { Int($0) ?? 0 }
239
+ let right = rhs.split(separator: ".").map { Int($0) ?? 0 }
240
+ for index in 0..<max(left.count, right.count) {
241
+ let l = index < left.count ? left[index] : 0
242
+ let r = index < right.count ? right[index] : 0
243
+ if l != r {
244
+ return l > r
245
+ }
246
+ }
247
+ return false
248
+ }
249
+
250
+ /// The spelled access modifier of a declaration (`open`, `public`, `package`, `internal`,
251
+ /// `fileprivate`, `private`), or `nil` when none is written. A modifier with a detail, like the
252
+ /// `private` in `private(set)`, limits only the setter, so it is not the declaration's.
253
+ func spelledAccessLevel(of modifiers: DeclModifierListSyntax) -> String? {
254
+ return modifiers.first { modifier in
255
+ modifier.detail == nil && accessModifierNames.contains(modifier.name.text)
256
+ }?.name.text
257
+ }
258
+
259
+ private let accessModifierNames: Set<String> = ["open", "public", "package", "internal", "fileprivate", "private"]
@@ -2,6 +2,44 @@
2
2
  /// `@Record` type, with the per-member detail a TypeScript type generator needs. Read syntactically
3
3
  /// (like the macros): each boundary type becomes a structured `TypeNode` tree so the consumer walks a
4
4
  /// tagged tree rather than re-parsing Swift type syntax.
5
+ ///
6
+ /// Every declaration and member carries the `platforms` that build it, and the ones that can be
7
+ /// written with an access modifier carry the `accessLevel` they spell. A declaration no platform
8
+ /// builds is not reported.
9
+
10
+ /// One Apple OS that builds a declaration.
11
+ struct ExportedPlatform: Encodable, Equatable {
12
+ /// The OS, spelled as `os(...)` spells it: `iOS`, `macOS`, `tvOS`, `watchOS`, or `visionOS`.
13
+ let name: String
14
+
15
+ /// The version spelled in `@available` (`17.0` in `@available(iOS 17.0, *)`), or `nil` when no
16
+ /// attribute on the declaration or its enclosing types names one. The latest version wins when
17
+ /// several do.
18
+ let introduced: String?
19
+
20
+ /// How the declaration's own `@available` attributes deprecate it on this OS, or `nil` when they
21
+ /// don't. Enclosing types are not read: a member of a deprecated type is not itself reported as
22
+ /// deprecated.
23
+ var deprecated: ExportedDeprecation? = nil
24
+ }
25
+
26
+ /// A deprecation from `@available`: `@available(*, deprecated, ...)` on every OS, or
27
+ /// `@available(iOS, deprecated: 16, obsoleted: 18, ...)` on one. Each field is `nil` when not written.
28
+ struct ExportedDeprecation: Encodable, Equatable {
29
+ /// The version from `deprecated: 16`; `nil` for a bare `deprecated`.
30
+ var since: String? = nil
31
+
32
+ /// The version from `obsoleted: 18`, from which the declaration is no longer available. A
33
+ /// declaration that is only obsoleted is still reported as deprecated.
34
+ var obsoleted: String? = nil
35
+
36
+ /// The `message:` text.
37
+ var message: String? = nil
38
+
39
+ /// The `renamed:` text: the Swift name of the replacement, as written (`newName`,
40
+ /// `other(value:)`). It is not resolved to a JS name.
41
+ var renamed: String? = nil
42
+ }
5
43
 
6
44
  /// One parameter of a `@JS` function or `@JS init`.
7
45
  struct ExportedParameter: Encodable, Equatable {
@@ -11,14 +49,18 @@ struct ExportedParameter: Encodable, Equatable {
11
49
  /// The internal parameter name (the `second` name, else the same as `label`): `point` above.
12
50
  let name: String
13
51
 
14
- let type: TypeNode
52
+ var type: TypeNode
15
53
 
16
54
  /// True when the caller may omit it: a default value or an optional type. Distinct from the type
17
55
  /// being `.optional` (a defaulted non-optional is also omittable). Encoded as `optional`.
18
56
  let isOptional: Bool
19
57
 
58
+ /// The default value expression as source text (`1`, `"x"`, `.fast`), or `nil` when none is
59
+ /// written. Not evaluated: it may be any expression.
60
+ let defaultValue: String?
61
+
20
62
  private enum CodingKeys: String, CodingKey {
21
- case label, name, type
63
+ case label, name, type, defaultValue
22
64
  case isOptional = "optional"
23
65
  }
24
66
  }
@@ -31,10 +73,10 @@ struct ExportedFunction: Encodable, Equatable {
31
73
  /// The JS name it binds under: the `@JS("x")` override, else `name`.
32
74
  let jsName: String
33
75
 
34
- let parameters: [ExportedParameter]
76
+ var parameters: [ExportedParameter]
35
77
 
36
78
  /// The return type, or `nil` for `Void`. Named `returns` to pair with `parameters`.
37
- let returns: TypeNode?
79
+ var returns: TypeNode?
38
80
 
39
81
  /// `async` (an async function is promise-returning in JS).
40
82
  let isAsync: Bool
@@ -45,9 +87,14 @@ struct ExportedFunction: Encodable, Equatable {
45
87
  /// `static`/`class` member.
46
88
  let isStatic: Bool
47
89
 
90
+ /// The spelled access modifier, or `nil` when none is written.
91
+ let accessLevel: String?
92
+
93
+ let platforms: [ExportedPlatform]
94
+
48
95
  /// The flags are encoded under their TS-keyword spellings.
49
96
  private enum CodingKeys: String, CodingKey {
50
- case name, jsName, parameters, returns
97
+ case name, jsName, parameters, returns, accessLevel, platforms
51
98
  case isAsync = "async"
52
99
  case isThrowing = "throws"
53
100
  case isStatic = "static"
@@ -64,7 +111,7 @@ struct ExportedProperty: Encodable, Equatable {
64
111
 
65
112
  /// The value type, or `nil` when undeterminable syntactically (no annotation and no literal
66
113
  /// default), the case the macro binds getter-only.
67
- let type: TypeNode?
114
+ var type: TypeNode?
68
115
 
69
116
  /// True when JS can assign to it: a stored `var` or a computed `var` with a `set`. Encoded as its
70
117
  /// inverse, `readonly`.
@@ -73,8 +120,17 @@ struct ExportedProperty: Encodable, Equatable {
73
120
  /// `static`/`class` member. Encoded as `static`.
74
121
  let isStatic: Bool
75
122
 
123
+ /// The initial value of a stored property as source text, or `nil` when none is written.
124
+ let defaultValue: String?
125
+
126
+ /// The spelled access modifier, or `nil` when none is written. A setter-only modifier like
127
+ /// `private(set)` is not the property's access level.
128
+ let accessLevel: String?
129
+
130
+ let platforms: [ExportedPlatform]
131
+
76
132
  private enum CodingKeys: String, CodingKey {
77
- case name, jsName, type
133
+ case name, jsName, type, defaultValue, accessLevel, platforms
78
134
  case isReadonly = "readonly"
79
135
  case isStatic = "static"
80
136
  }
@@ -86,6 +142,9 @@ struct ExportedProperty: Encodable, Equatable {
86
142
  try container.encodeIfPresent(type, forKey: .type)
87
143
  try container.encode(!isSettable, forKey: .isReadonly)
88
144
  try container.encode(isStatic, forKey: .isStatic)
145
+ try container.encodeIfPresent(defaultValue, forKey: .defaultValue)
146
+ try container.encodeIfPresent(accessLevel, forKey: .accessLevel)
147
+ try container.encode(platforms, forKey: .platforms)
89
148
  }
90
149
  }
91
150
 
@@ -96,23 +155,28 @@ struct ExportedRecordProperty: Encodable, Equatable {
96
155
  let name: String
97
156
 
98
157
  /// `@Record` requires a determinable type on every property, so this is never `nil`.
99
- let type: TypeNode
158
+ var type: TypeNode
100
159
 
101
160
  /// Optional-typed. Encoded as `optional`.
102
161
  let isOptional: Bool
103
162
 
104
- /// Has a default value. Not encoded (derivable as `!isOptional && !isRequired`, and a Swift default
105
- /// never reaches JS); kept only to derive `isRequired`.
106
- let hasDefault: Bool
163
+ /// The default value as source text, or `nil` when none is written. A field JS leaves out takes it.
164
+ let defaultValue: String?
165
+
166
+ /// The spelled access modifier, or `nil` when none is written. Never `private` or `fileprivate`,
167
+ /// since those exclude a property from the record.
168
+ let accessLevel: String?
169
+
170
+ let platforms: [ExportedPlatform]
107
171
 
108
172
  /// Whether JS must supply this property, matching the macro's `RecordProperty.isRequired`. Encoded
109
173
  /// as `required`.
110
174
  var isRequired: Bool {
111
- return !hasDefault && !isOptional
175
+ return defaultValue == nil && !isOptional
112
176
  }
113
177
 
114
178
  private enum CodingKeys: String, CodingKey {
115
- case name, type
179
+ case name, type, defaultValue, accessLevel, platforms
116
180
  case isOptional = "optional"
117
181
  case isRequired = "required"
118
182
  }
@@ -123,6 +187,9 @@ struct ExportedRecordProperty: Encodable, Equatable {
123
187
  try container.encode(type, forKey: .type)
124
188
  try container.encode(isOptional, forKey: .isOptional)
125
189
  try container.encode(isRequired, forKey: .isRequired)
190
+ try container.encodeIfPresent(defaultValue, forKey: .defaultValue)
191
+ try container.encodeIfPresent(accessLevel, forKey: .accessLevel)
192
+ try container.encode(platforms, forKey: .platforms)
126
193
  }
127
194
  }
128
195
 
@@ -137,13 +204,18 @@ struct ExportedEvent: Encodable, Equatable {
137
204
  let jsName: String
138
205
 
139
206
  /// The single payload parameter's type, or `nil` for a no-payload `() -> Void` event.
140
- let payload: TypeNode?
207
+ var payload: TypeNode?
141
208
 
142
209
  /// `@Event(sync: true)`, dispatching inline on the JS thread instead of scheduling.
143
210
  let isSync: Bool
144
211
 
212
+ /// The spelled access modifier, or `nil` when none is written.
213
+ let accessLevel: String?
214
+
215
+ let platforms: [ExportedPlatform]
216
+
145
217
  private enum CodingKeys: String, CodingKey {
146
- case name, jsName, payload
218
+ case name, jsName, payload, accessLevel, platforms
147
219
  case isSync = "sync"
148
220
  }
149
221
 
@@ -153,6 +225,8 @@ struct ExportedEvent: Encodable, Equatable {
153
225
  try container.encode(jsName, forKey: .jsName)
154
226
  try container.encodeIfPresent(payload, forKey: .payload)
155
227
  try container.encode(isSync, forKey: .isSync)
228
+ try container.encodeIfPresent(accessLevel, forKey: .accessLevel)
229
+ try container.encode(platforms, forKey: .platforms)
156
230
  }
157
231
  }
158
232
 
@@ -165,13 +239,18 @@ struct ExportedModule: Encodable, Equatable {
165
239
  /// The JS module name: `@ExpoModule("Foo")` override, else the class name.
166
240
  let jsName: String
167
241
 
168
- let functions: [ExportedFunction]
169
- let properties: [ExportedProperty]
170
- let events: [ExportedEvent]
242
+ var functions: [ExportedFunction]
243
+ var properties: [ExportedProperty]
244
+ var events: [ExportedEvent]
171
245
 
172
246
  /// The classes listed in `@ExpoModule(classes: [...])`, in source order. Each is reachable from JS
173
247
  /// as a member of the module.
174
- let classes: [ExportedClass]
248
+ var classes: [ExportedClass]
249
+
250
+ /// The spelled access modifier, or `nil` when none is written.
251
+ let accessLevel: String?
252
+
253
+ let platforms: [ExportedPlatform]
175
254
 
176
255
  /// Absolute source path, matching `scan-modules`.
177
256
  let file: String
@@ -190,6 +269,16 @@ struct ExportedClass: Encodable, Equatable {
190
269
  let jsName: String?
191
270
  }
192
271
 
272
+ /// The `@JS init` of a shared object: the constructor JS calls with `new`.
273
+ struct ExportedInitializer: Encodable, Equatable {
274
+ var parameters: [ExportedParameter]
275
+
276
+ /// The spelled access modifier, or `nil` when none is written.
277
+ let accessLevel: String?
278
+
279
+ let platforms: [ExportedPlatform]
280
+ }
281
+
193
282
  /// A `@SharedObject` type: a JS class with an optional `@JS init` constructor plus its `@JS` members.
194
283
  struct ExportedSharedObject: Encodable, Equatable {
195
284
  /// The Swift class name, qualified with the types it is nested in (`Outer.Player`).
@@ -199,12 +288,18 @@ struct ExportedSharedObject: Encodable, Equatable {
199
288
  /// types.
200
289
  let jsName: String
201
290
 
202
- /// The `@JS init` parameters, or `nil` when there's none. A shared object has at most one.
203
- let constructorParameters: [ExportedParameter]?
291
+ /// The `@JS init`, or `nil` when there's none. A shared object has at most one; the first is kept
292
+ /// if a malformed source has more. Not named `constructor`, which every JS object inherits.
293
+ var initializer: ExportedInitializer?
294
+
295
+ var functions: [ExportedFunction]
296
+ var properties: [ExportedProperty]
297
+ var events: [ExportedEvent]
204
298
 
205
- let functions: [ExportedFunction]
206
- let properties: [ExportedProperty]
207
- let events: [ExportedEvent]
299
+ /// The spelled access modifier, or `nil` when none is written.
300
+ let accessLevel: String?
301
+
302
+ let platforms: [ExportedPlatform]
208
303
 
209
304
  let file: String
210
305
  }
@@ -215,7 +310,12 @@ struct ExportedRecord: Encodable, Equatable {
215
310
  /// (`Outer.Options`).
216
311
  let name: String
217
312
 
218
- let properties: [ExportedRecordProperty]
313
+ var properties: [ExportedRecordProperty]
314
+
315
+ /// The spelled access modifier, or `nil` when none is written.
316
+ let accessLevel: String?
317
+
318
+ let platforms: [ExportedPlatform]
219
319
 
220
320
  let file: String
221
321
  }
@@ -237,14 +337,17 @@ struct ExportedEnumCase: Encodable, Equatable {
237
337
  /// above.
238
338
  let rawValue: String?
239
339
 
340
+ let platforms: [ExportedPlatform]
341
+
240
342
  private enum CodingKeys: String, CodingKey {
241
- case name, rawValue
343
+ case name, rawValue, platforms
242
344
  }
243
345
 
244
346
  func encode(to encoder: Encoder) throws {
245
347
  var container = encoder.container(keyedBy: CodingKeys.self)
246
348
  try container.encode(name, forKey: .name)
247
349
  try container.encodeIfPresent(rawValue, forKey: .rawValue)
350
+ try container.encode(platforms, forKey: .platforms)
248
351
  }
249
352
  }
250
353
 
@@ -264,10 +367,15 @@ struct ExportedEnum: Encodable, Equatable {
264
367
 
265
368
  let cases: [ExportedEnumCase]
266
369
 
370
+ /// The spelled access modifier, or `nil` when none is written.
371
+ let accessLevel: String?
372
+
373
+ let platforms: [ExportedPlatform]
374
+
267
375
  let file: String
268
376
 
269
377
  private enum CodingKeys: String, CodingKey {
270
- case name, rawType, cases, file
378
+ case name, rawType, cases, accessLevel, platforms, file
271
379
  }
272
380
 
273
381
  func encode(to encoder: Encoder) throws {
@@ -275,6 +383,8 @@ struct ExportedEnum: Encodable, Equatable {
275
383
  try container.encode(name, forKey: .name)
276
384
  try container.encodeIfPresent(rawType, forKey: .rawType)
277
385
  try container.encode(cases, forKey: .cases)
386
+ try container.encodeIfPresent(accessLevel, forKey: .accessLevel)
387
+ try container.encode(platforms, forKey: .platforms)
278
388
  try container.encode(file, forKey: .file)
279
389
  }
280
390
  }
@@ -288,10 +398,12 @@ struct ExportedUnionMember: Encodable, Equatable {
288
398
 
289
399
  /// The associated value's type: what this alternative decodes from. Named `type` like every other
290
400
  /// type-valued field here; `payload` already means an event's argument type on `ExportedEvent`.
291
- let type: TypeNode
401
+ var type: TypeNode
402
+
403
+ let platforms: [ExportedPlatform]
292
404
 
293
405
  private enum CodingKeys: String, CodingKey {
294
- case name, type
406
+ case name, type, platforms
295
407
  }
296
408
  }
297
409
 
@@ -307,7 +419,12 @@ struct ExportedUnion: Encodable, Equatable {
307
419
  /// name.
308
420
  let name: String
309
421
 
310
- let members: [ExportedUnionMember]
422
+ var members: [ExportedUnionMember]
423
+
424
+ /// The spelled access modifier, or `nil` when none is written.
425
+ let accessLevel: String?
426
+
427
+ let platforms: [ExportedPlatform]
311
428
 
312
429
  let file: String
313
430
  }
@@ -328,8 +445,10 @@ struct ExportedSurface: Encodable, Equatable {
328
445
  /// and change for different reasons. Version 2 added `events` to modules and shared objects; version 3
329
446
  /// added `enums`; version 4 added `unions`; version 5 resolves refs, adding `refKind` and correcting
330
447
  /// an enum ref's `typeof`; version 6 added `classes` to modules; version 7 reports nested types under
331
- /// qualified names and gives a resolved ref the name of its declaration.
332
- let scanExportsSchemaVersion = 7
448
+ /// qualified names and gives a resolved ref the name of its declaration; version 8 reports
449
+ /// declarations in `#if` blocks, adds `platforms` (with deprecations), `accessLevel`, and
450
+ /// `defaultValue`, and replaces `constructorParameters` with `initializer`.
451
+ let scanExportsSchemaVersion = 8
333
452
 
334
453
  /// The `scan-exports` result: the surface plus the run's stats. A distinct envelope from
335
454
  /// `ScanModulesResult` (different consumer: TS generation vs. autolinking).