expo-modules-macros 0.32.0 → 0.34.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/macos.yml +16 -3
- package/AGENTS.md +7 -3
- package/Package.swift +10 -1
- package/README.md +3 -3
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/ExportedSurface.swift +124 -5
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/InheritedMembers.swift +2 -1
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/ResolveRefs.swift +76 -17
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/ScanExports.swift +15 -8
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/SurfaceVisitor.swift +206 -21
- package/apple/Sources/ExpoModulesCLI/Commands/ScanModules/ScanModules.swift +14 -6
- package/apple/Sources/ExpoModulesCLI/Core/Detection.swift +6 -0
- package/apple/Sources/ExpoModulesCLI/Core/DetectionVisitor.swift +41 -2
- package/apple/Sources/ExpoModulesCLI/Core/SourceScan.swift +3 -2
- package/apple/Sources/ExpoModulesMacrosCore/Macros/ExpoViewMacro.swift +64 -30
- package/apple/Sources/ExpoModulesMacrosCore/Macros/JSMacro.swift +174 -2
- package/apple/Sources/ExpoModulesMacrosCore/Macros/ViewPropsMacro.swift +176 -46
- package/apple/Sources/ExpoModulesMacrosCore/ProvidedMacros.swift +1 -1
- package/apple/Sources/ExpoModulesMacrosCore/Support/DecorateModuleBuilder.swift +17 -3
- package/apple/Sources/ExpoModulesMacrosCore/Support/JSConstructor.swift +11 -2
- package/apple/Sources/ExpoModulesMacrosCore/Support/MacroHelpers.swift +36 -5
- package/apple/Sources/ExpoModulesMacrosPlugin/ExpoModulesMacrosPlugin.swift +1 -0
- package/build/types.d.ts +58 -3
- package/package.json +5 -5
|
@@ -3,11 +3,11 @@ import SwiftSyntax
|
|
|
3
3
|
import SwiftSyntaxBuilder
|
|
4
4
|
import SwiftSyntaxMacros
|
|
5
5
|
|
|
6
|
-
/// Member macro applied to an `
|
|
6
|
+
/// Member and extension macro applied to an `ExpoNativeView` subclass, binding it to its props type:
|
|
7
7
|
///
|
|
8
8
|
/// @ExpoView<CardProps>
|
|
9
|
-
/// final class CardView:
|
|
10
|
-
///
|
|
9
|
+
/// final class CardView: ExpoNativeView {
|
|
10
|
+
/// func didUpdateProps(_ diff: CardProps.Diff) {
|
|
11
11
|
/// if diff.changed(.color) {
|
|
12
12
|
/// backgroundColor = props.color
|
|
13
13
|
/// }
|
|
@@ -21,23 +21,25 @@ import SwiftSyntaxMacros
|
|
|
21
21
|
/// compile error at the author's own line ("requires that 'X' conform to 'AnyViewProps'")
|
|
22
22
|
/// rather than a macro diagnostic. This macro therefore validates nothing about the props type.
|
|
23
23
|
///
|
|
24
|
-
/// The
|
|
24
|
+
/// The expansion is a typealias and a conformance:
|
|
25
25
|
///
|
|
26
26
|
/// public typealias Props = CardProps
|
|
27
|
+
/// extension CardView: AnyNativeView {}
|
|
27
28
|
///
|
|
28
|
-
///
|
|
29
|
-
/// `
|
|
30
|
-
///
|
|
31
|
-
///
|
|
32
|
-
///
|
|
33
|
-
/// static, neither needing a builder to be evaluated.
|
|
29
|
+
/// There is **no view definition**: no `ViewDefinition`, no `View(…) { }` builder, no `Props(_:)` or
|
|
30
|
+
/// `Events(_:)` element. Core reaches the view through the `AnyNativeView` conformance: it reads the
|
|
31
|
+
/// props type from the `Props` typealias, which satisfies the protocol's associated type, and the
|
|
32
|
+
/// event names from the props type's own `_eventNames` (synthesized by `@ViewProps`). Both are
|
|
33
|
+
/// static, so no builder has to be evaluated.
|
|
34
34
|
///
|
|
35
|
-
/// Views also get no `_decorateView` hook
|
|
36
|
-
///
|
|
37
|
-
///
|
|
35
|
+
/// Views also get no `_decorateView` hook: `_decorateModule` and `_decorateSharedObject` exist to bind
|
|
36
|
+
/// members into a JS object, and a view has no such object. Props reach a view through Fabric's
|
|
37
|
+
/// mounting layer, not a JS call, so a view's runtime entry point is the props update hook.
|
|
38
38
|
///
|
|
39
|
-
/// `didUpdateProps` is a core
|
|
40
|
-
|
|
39
|
+
/// `didUpdateProps` is an `AnyNativeView` requirement with a default in core, not synthesized here.
|
|
40
|
+
/// It isn't an `override`: `ExpoNativeView` has no such method, because its parameter type depends on
|
|
41
|
+
/// the props type.
|
|
42
|
+
public struct ExpoViewMacro: MemberMacro, ExtensionMacro {
|
|
41
43
|
public static func expansion(
|
|
42
44
|
of node: AttributeSyntax,
|
|
43
45
|
providingMembersOf declaration: some DeclGroupSyntax,
|
|
@@ -87,38 +89,66 @@ public struct ExpoViewMacro: MemberMacro {
|
|
|
87
89
|
"""
|
|
88
90
|
]
|
|
89
91
|
}
|
|
92
|
+
|
|
93
|
+
public static func expansion(
|
|
94
|
+
of node: AttributeSyntax,
|
|
95
|
+
attachedTo declaration: some DeclGroupSyntax,
|
|
96
|
+
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
97
|
+
conformingTo protocols: [TypeSyntax],
|
|
98
|
+
in context: some MacroExpansionContext
|
|
99
|
+
) throws -> [ExtensionDeclSyntax] {
|
|
100
|
+
// A class that the member macro rejects gets no conformance either: the member macro has already
|
|
101
|
+
// reported the error, and a conformance without the `Props` typealias would only add a second
|
|
102
|
+
// error about the associated type.
|
|
103
|
+
guard let classDecl = declaration.as(ClassDeclSyntax.self),
|
|
104
|
+
inheritsFromAny(classDecl, names: expoViewBaseClassNames),
|
|
105
|
+
classDecl.genericParameterClause == nil,
|
|
106
|
+
existingPropsMemberKind(in: classDecl) == nil,
|
|
107
|
+
genericArgument(of: node) != nil,
|
|
108
|
+
!inheritsProtocol(named: nativeViewProtocolName, in: declaration)
|
|
109
|
+
else {
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
let ext: DeclSyntax = """
|
|
113
|
+
extension \(type.trimmed): \(raw: nativeViewProtocolName) {}
|
|
114
|
+
"""
|
|
115
|
+
guard let extDecl = ext.as(ExtensionDeclSyntax.self) else {
|
|
116
|
+
return []
|
|
117
|
+
}
|
|
118
|
+
return [extDecl]
|
|
119
|
+
}
|
|
90
120
|
}
|
|
91
121
|
|
|
92
122
|
// MARK: - Diagnostics
|
|
93
123
|
|
|
94
|
-
/// The error for a class that doesn't name `
|
|
124
|
+
/// The error for a class that doesn't name `ExpoNativeView` in its inheritance clause, carrying a fix-it
|
|
95
125
|
/// that adds it.
|
|
96
126
|
///
|
|
97
127
|
/// The fix-it only applies where the edit is unambiguous. With no inheritance clause at all it
|
|
98
|
-
/// inserts `:
|
|
128
|
+
/// inserts `: ExpoNativeView`; with an existing clause it prepends `ExpoNativeView` as the first entry, since
|
|
99
129
|
/// Swift requires the superclass to come before any protocol. A class that already names some other
|
|
100
130
|
/// superclass gets the diagnostic without a fix-it: replacing that superclass is a decision the macro
|
|
101
131
|
/// can't make (see the indirect-inheritance note on `expoViewBaseClassNames`).
|
|
102
132
|
private func missingBaseClassDiagnostic(for classDecl: ClassDeclSyntax) -> Diagnostic {
|
|
103
133
|
let message = ExpoViewDiagnosticMessage(
|
|
104
|
-
"@ExpoView class must inherit from
|
|
134
|
+
"@ExpoView class must inherit from ExpoNativeView. Add `: ExpoNativeView` to the class declaration.",
|
|
105
135
|
id: "expoview-missing-base-class"
|
|
106
136
|
)
|
|
107
137
|
var fixIts: [FixIt] = []
|
|
108
138
|
|
|
109
139
|
if classDecl.inheritanceClause == nil {
|
|
110
|
-
// `class CardView {` → `class CardView:
|
|
140
|
+
// `class CardView {` → `class CardView: ExpoNativeView {`. The name carries the space before `{` as
|
|
111
141
|
// trailing trivia, so move it onto the clause to keep the spacing.
|
|
112
142
|
let name = classDecl.name
|
|
113
143
|
let clause = InheritanceClauseSyntax(
|
|
114
144
|
colon: .colonToken(trailingTrivia: .space),
|
|
115
145
|
inheritedTypes: InheritedTypeListSyntax([
|
|
116
|
-
InheritedTypeSyntax(type: TypeSyntax(IdentifierTypeSyntax(name: .identifier("
|
|
146
|
+
InheritedTypeSyntax(type: TypeSyntax(IdentifierTypeSyntax(name: .identifier("ExpoNativeView"))))
|
|
117
147
|
])
|
|
118
148
|
)
|
|
119
149
|
fixIts.append(
|
|
120
150
|
FixIt(
|
|
121
|
-
message: ExpoViewFixItMessage("Inherit from '
|
|
151
|
+
message: ExpoViewFixItMessage("Inherit from 'ExpoNativeView'", id: "expoview-add-base-class"),
|
|
122
152
|
changes: [
|
|
123
153
|
.replace(
|
|
124
154
|
oldNode: Syntax(classDecl),
|
|
@@ -132,16 +162,16 @@ private func missingBaseClassDiagnostic(for classDecl: ClassDeclSyntax) -> Diagn
|
|
|
132
162
|
)
|
|
133
163
|
)
|
|
134
164
|
} else if let clause = classDecl.inheritanceClause, inheritsOnlyProtocolsByConvention(clause) {
|
|
135
|
-
// `class CardView: Identifiable {` → `class CardView:
|
|
165
|
+
// `class CardView: Identifiable {` → `class CardView: ExpoNativeView, Identifiable {`.
|
|
136
166
|
var inherited = clause.inheritedTypes
|
|
137
167
|
if var first = inherited.first {
|
|
138
168
|
first.trailingComma = .commaToken(trailingTrivia: .space)
|
|
139
|
-
first.type = TypeSyntax(IdentifierTypeSyntax(name: .identifier("
|
|
169
|
+
first.type = TypeSyntax(IdentifierTypeSyntax(name: .identifier("ExpoNativeView")))
|
|
140
170
|
inherited.insert(first, at: inherited.startIndex)
|
|
141
171
|
}
|
|
142
172
|
fixIts.append(
|
|
143
173
|
FixIt(
|
|
144
|
-
message: ExpoViewFixItMessage("Inherit from '
|
|
174
|
+
message: ExpoViewFixItMessage("Inherit from 'ExpoNativeView'", id: "expoview-add-base-class"),
|
|
145
175
|
changes: [
|
|
146
176
|
.replace(
|
|
147
177
|
oldNode: Syntax(clause),
|
|
@@ -157,7 +187,7 @@ private func missingBaseClassDiagnostic(for classDecl: ClassDeclSyntax) -> Diagn
|
|
|
157
187
|
|
|
158
188
|
/// Whether every entry in the clause looks like a protocol rather than a superclass, by the
|
|
159
189
|
/// capitalization-free heuristic that a Swift superclass must come first: if the first entry is one
|
|
160
|
-
/// the macro would be replacing, prepending `
|
|
190
|
+
/// the macro would be replacing, prepending `ExpoNativeView` would produce two superclasses. A macro can't
|
|
161
191
|
/// resolve names, so this only reports true when the clause is empty of anything that could be a
|
|
162
192
|
/// base class the author chose deliberately. Conservative by design: a wrong guess here would emit a
|
|
163
193
|
/// fix-it that doesn't compile.
|
|
@@ -223,14 +253,14 @@ private func existingPropsMemberKind(in classDecl: ClassDeclSyntax) -> String? {
|
|
|
223
253
|
}
|
|
224
254
|
|
|
225
255
|
/// Base classes an `@ExpoView` may inherit from. The check is syntactic, matching the name in the
|
|
226
|
-
/// class's own inheritance clause: both `:
|
|
256
|
+
/// class's own inheritance clause: both `: ExpoNativeView` and a qualified `: ExpoModulesCore.ExpoNativeView`
|
|
227
257
|
/// pass, since `baseIdentifier` reads the trailing name.
|
|
228
258
|
///
|
|
229
|
-
/// Indirect inheritance does not: a view whose superclass is itself an `
|
|
230
|
-
/// (`class Base:
|
|
259
|
+
/// Indirect inheritance does not: a view whose superclass is itself an `ExpoNativeView` subclass
|
|
260
|
+
/// (`class Base: ExpoNativeView` then `class CardView: Base`) is rejected, because a macro can't resolve
|
|
231
261
|
/// the superclass to check. `@SharedObject` has the same limitation, through the same helper. The
|
|
232
|
-
/// workaround is to apply `@ExpoView` to the class that names `
|
|
233
|
-
private let expoViewBaseClassNames: Set<String> = ["
|
|
262
|
+
/// workaround is to apply `@ExpoView` to the class that names `ExpoNativeView` directly.
|
|
263
|
+
private let expoViewBaseClassNames: Set<String> = ["ExpoNativeView"]
|
|
234
264
|
|
|
235
265
|
/// The attribute's single generic argument, verbatim (`@ExpoView<CardProps>` → `"CardProps"`), or
|
|
236
266
|
/// `nil` when the attribute carries no generic argument clause.
|
|
@@ -257,3 +287,7 @@ private func genericArgument(of attribute: AttributeSyntax) -> String? {
|
|
|
257
287
|
}
|
|
258
288
|
return type.trimmedDescription
|
|
259
289
|
}
|
|
290
|
+
|
|
291
|
+
/// The core protocol that `@ExpoView` conforms the class to. Core registers the view and decodes its
|
|
292
|
+
/// props through it.
|
|
293
|
+
private let nativeViewProtocolName = "AnyNativeView"
|
|
@@ -51,6 +51,7 @@ public struct JSMacro: PeerMacro {
|
|
|
51
51
|
diagnoseConcurrentOption(of: node, on: declaration, in: context)
|
|
52
52
|
diagnoseAsyncVariantOption(of: node, on: declaration, in: context)
|
|
53
53
|
diagnoseJavaScriptValuesOffThread(of: node, on: declaration, in: context)
|
|
54
|
+
diagnoseRuntimeParameter(of: node, on: declaration, in: context)
|
|
54
55
|
|
|
55
56
|
// In an `@ExpoModule` or `@SharedObject` class, the class macro emits the assertion instead: it sees
|
|
56
57
|
// the type aliases declared in the class body, which this macro can't (see `TupleTypeAliases`).
|
|
@@ -425,6 +426,175 @@ private func diagnoseJavaScriptValuesOffThread(
|
|
|
425
426
|
}
|
|
426
427
|
}
|
|
427
428
|
|
|
429
|
+
/// Emits the diagnostics for a parameter that takes the JavaScript runtime (see `isRuntimeParameter`).
|
|
430
|
+
/// The binding passes the runtime of the call, which can be used only on the JavaScript thread. So
|
|
431
|
+
/// the parameter must be:
|
|
432
|
+
/// - in a synchronous function or an initializer. An `async` function can resume on another thread
|
|
433
|
+
/// after an `await`: `@JavaScriptActor` runs a job on the thread that enqueues it, and doesn't move
|
|
434
|
+
/// it to the JavaScript thread. A `.concurrent` function and the variant that `.withAsync` adds run
|
|
435
|
+
/// on the concurrent thread pool;
|
|
436
|
+
/// - `borrowing`, so the body can't keep the runtime for later without an explicit `copy`;
|
|
437
|
+
/// - not optional, since the binding always has a runtime to pass;
|
|
438
|
+
/// - the last parameter, labeled `in` (or `runtime`), so every declaration reads the same and the JavaScript
|
|
439
|
+
/// arguments keep their order.
|
|
440
|
+
private func diagnoseRuntimeParameter(
|
|
441
|
+
of node: AttributeSyntax,
|
|
442
|
+
on declaration: some DeclSyntaxProtocol,
|
|
443
|
+
in context: some MacroExpansionContext
|
|
444
|
+
) {
|
|
445
|
+
let parameters: FunctionParameterListSyntax
|
|
446
|
+
if let funcDecl = declaration.as(FunctionDeclSyntax.self) {
|
|
447
|
+
parameters = funcDecl.signature.parameterClause.parameters
|
|
448
|
+
// `.concurrent` is checked first: its body never runs on the JavaScript thread, so making the
|
|
449
|
+
// function synchronous doesn't help (a synchronous `.concurrent` function is an error too).
|
|
450
|
+
let offThread: (reason: String, remedy: String)?
|
|
451
|
+
if hasOption(node, named: "concurrent") {
|
|
452
|
+
offThread = ("'.concurrent' runs the body on the concurrent thread pool", "Remove the parameter.")
|
|
453
|
+
} else if funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil {
|
|
454
|
+
offThread = (
|
|
455
|
+
"an 'async' function can resume on another thread after an 'await'",
|
|
456
|
+
"Remove the parameter, or make the function synchronous."
|
|
457
|
+
)
|
|
458
|
+
} else if asyncVariantOption(of: node) != nil {
|
|
459
|
+
offThread = (
|
|
460
|
+
"the variant that '.withAsync' adds runs the function on the concurrent thread pool",
|
|
461
|
+
"Remove the parameter, or remove '.withAsync'."
|
|
462
|
+
)
|
|
463
|
+
} else {
|
|
464
|
+
offThread = nil
|
|
465
|
+
}
|
|
466
|
+
if let offThread {
|
|
467
|
+
for parameter in parameters where isRuntimeParameter(parameter) {
|
|
468
|
+
context.diagnose(
|
|
469
|
+
Diagnostic(
|
|
470
|
+
node: parameter.type,
|
|
471
|
+
message: JSDiagnosticMessage(
|
|
472
|
+
"A @JS function can take the JavaScript runtime only when it's synchronous: \(offThread.reason), and the runtime can be used only on the JavaScript thread. \(offThread.remedy)",
|
|
473
|
+
id: "js-runtime-parameter-async",
|
|
474
|
+
severity: .error
|
|
475
|
+
)
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
} else if let initDecl = declaration.as(InitializerDeclSyntax.self) {
|
|
482
|
+
parameters = initDecl.signature.parameterClause.parameters
|
|
483
|
+
} else {
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
for (index, parameter) in parameters.enumerated() where isRuntimeParameter(parameter) {
|
|
487
|
+
guard index == parameters.count - 1 else {
|
|
488
|
+
context.diagnose(
|
|
489
|
+
Diagnostic(
|
|
490
|
+
node: parameter,
|
|
491
|
+
message: JSDiagnosticMessage(
|
|
492
|
+
"The JavaScript runtime must be the last parameter of a @JS function or initializer, and only one parameter can take it. Move it to the end as 'in runtime: borrowing JavaScriptRuntime'.",
|
|
493
|
+
id: "js-runtime-parameter-position",
|
|
494
|
+
severity: .error
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
continue
|
|
499
|
+
}
|
|
500
|
+
// The errors and the docs name only `in`, which matches the JSI API, such as `encode(_:into:in:)`.
|
|
501
|
+
// An escaped label (`` `in` ``) is the same label.
|
|
502
|
+
if !runtimeParameterLabels.contains(withoutBackticks(parameter.firstName.text)) {
|
|
503
|
+
context.diagnose(
|
|
504
|
+
Diagnostic(
|
|
505
|
+
node: parameter.firstName,
|
|
506
|
+
message: JSDiagnosticMessage(
|
|
507
|
+
"The parameter that takes the JavaScript runtime must be labeled 'in', as in 'in runtime: borrowing JavaScriptRuntime'.",
|
|
508
|
+
id: "js-runtime-parameter-label",
|
|
509
|
+
severity: .error
|
|
510
|
+
),
|
|
511
|
+
fixIts: [runtimeLabelFixIt(for: parameter)]
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
let specified = SpecifiedType(parameter.type)
|
|
516
|
+
let wrapped = optionalWrappedType(specified.type)
|
|
517
|
+
if let wrapped {
|
|
518
|
+
context.diagnose(
|
|
519
|
+
Diagnostic(
|
|
520
|
+
node: specified.type,
|
|
521
|
+
message: JSDiagnosticMessage(
|
|
522
|
+
"The parameter that takes the JavaScript runtime can't be optional: the binding always passes the runtime of the call.",
|
|
523
|
+
id: "js-runtime-parameter-optional",
|
|
524
|
+
severity: .error
|
|
525
|
+
),
|
|
526
|
+
fixIts: [
|
|
527
|
+
FixIt(
|
|
528
|
+
message: JSFixItMessage("Remove the optional", id: "js-runtime-parameter-remove-optional"),
|
|
529
|
+
changes: [
|
|
530
|
+
.replace(
|
|
531
|
+
oldNode: Syntax(specified.type),
|
|
532
|
+
newNode: Syntax(
|
|
533
|
+
wrapped.trimmed
|
|
534
|
+
.with(\.leadingTrivia, specified.type.leadingTrivia)
|
|
535
|
+
.with(\.trailingTrivia, specified.type.trailingTrivia)
|
|
536
|
+
)
|
|
537
|
+
)
|
|
538
|
+
]
|
|
539
|
+
)
|
|
540
|
+
]
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
if specified.specifiers != ["borrowing"] {
|
|
545
|
+
context.diagnose(
|
|
546
|
+
Diagnostic(
|
|
547
|
+
node: parameter.type,
|
|
548
|
+
message: JSDiagnosticMessage(
|
|
549
|
+
"The parameter that takes the JavaScript runtime must be 'borrowing': the runtime can be used only on the JavaScript thread, and a borrowed parameter can't be kept for later without an explicit 'copy'.",
|
|
550
|
+
id: "js-runtime-parameter-borrowing",
|
|
551
|
+
severity: .error
|
|
552
|
+
),
|
|
553
|
+
fixIts: [
|
|
554
|
+
FixIt(
|
|
555
|
+
message: JSFixItMessage("Mark the parameter 'borrowing'", id: "js-runtime-parameter-insert-borrowing"),
|
|
556
|
+
changes: [
|
|
557
|
+
.replace(
|
|
558
|
+
oldNode: Syntax(parameter.type),
|
|
559
|
+
newNode: Syntax(borrowingType(wrapped ?? specified.type, like: parameter.type))
|
|
560
|
+
)
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
]
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/// The labels that the runtime parameter can have.
|
|
571
|
+
private let runtimeParameterLabels: Set<String> = ["in", "runtime"]
|
|
572
|
+
|
|
573
|
+
/// The fix-it that labels the runtime parameter `in`. It keeps the parameter name that the body uses, or
|
|
574
|
+
/// names the parameter `runtime` when it has no name.
|
|
575
|
+
private func runtimeLabelFixIt(for parameter: FunctionParameterSyntax) -> FixIt {
|
|
576
|
+
var newParameter = parameter
|
|
577
|
+
let name = parameter.secondName ?? (parameter.firstName.tokenKind == .wildcard ? nil : parameter.firstName)
|
|
578
|
+
newParameter.firstName = .keyword(.in, leadingTrivia: parameter.firstName.leadingTrivia, trailingTrivia: .space)
|
|
579
|
+
newParameter.secondName = name?.trimmed ?? .identifier("runtime")
|
|
580
|
+
return FixIt(
|
|
581
|
+
message: JSFixItMessage("Label the parameter 'in'", id: "js-runtime-parameter-label"),
|
|
582
|
+
changes: [.replace(oldNode: Syntax(parameter), newNode: Syntax(newParameter))]
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/// The type `type` with the single specifier `borrowing`, with the trivia of `original`.
|
|
587
|
+
private func borrowingType(_ type: TypeSyntax, like original: TypeSyntax) -> TypeSyntax {
|
|
588
|
+
let specifier = SimpleTypeSpecifierSyntax(specifier: .keyword(.borrowing, trailingTrivia: .space))
|
|
589
|
+
let attributed = AttributedTypeSyntax(
|
|
590
|
+
specifiers: [.simpleTypeSpecifier(specifier)],
|
|
591
|
+
baseType: type.trimmed
|
|
592
|
+
)
|
|
593
|
+
return TypeSyntax(attributed)
|
|
594
|
+
.with(\.leadingTrivia, original.leadingTrivia)
|
|
595
|
+
.with(\.trailingTrivia, original.trailingTrivia)
|
|
596
|
+
}
|
|
597
|
+
|
|
428
598
|
private func diagnoseParameterSpecifiers(
|
|
429
599
|
in declaration: some DeclSyntaxProtocol,
|
|
430
600
|
in context: some MacroExpansionContext
|
|
@@ -437,7 +607,8 @@ private func diagnoseParameterSpecifiers(
|
|
|
437
607
|
} else {
|
|
438
608
|
return
|
|
439
609
|
}
|
|
440
|
-
|
|
610
|
+
// The runtime parameter isn't decoded, and `diagnoseRuntimeParameter` requires it to be `borrowing`.
|
|
611
|
+
for parameter in parameters where !isRuntimeParameter(parameter) {
|
|
441
612
|
guard let specifier = SpecifiedType(parameter.type).unsupportedSpecifier else {
|
|
442
613
|
continue
|
|
443
614
|
}
|
|
@@ -608,7 +779,8 @@ private func argumentBoundaryTypes(of parameters: FunctionParameterListSyntax) -
|
|
|
608
779
|
// A closure in any other position is diagnosed by `diagnoseClosureTypes` and left out here, since
|
|
609
780
|
// its metatype isn't a valid expression in the assertion.
|
|
610
781
|
var types: [AssertedType] = []
|
|
611
|
-
|
|
782
|
+
// The runtime parameter takes no JavaScript argument, so nothing decodes into it.
|
|
783
|
+
for parameter in parameters where !isRuntimeParameter(parameter) {
|
|
612
784
|
let label = parameter.firstName.tokenKind == .wildcard ? nil : parameter.firstName.text
|
|
613
785
|
// The specifier isn't part of the asserted type, and a parameter with one the binding can't honor
|
|
614
786
|
// is reported by `diagnoseParameterSpecifiers` and not asserted.
|