Skip to content

Commit 79479ca

Browse files
authored
Include deprecated modifiers (#1554)
* Include deprecated modifiers * Fix macOS build
1 parent f17b76b commit 79479ca

File tree

9 files changed

+198
-12
lines changed

9 files changed

+198
-12
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// MenuButtonStyle.swift
3+
// LiveViewNative
4+
//
5+
// Created by Carson Katri on 3/11/25.
6+
//
7+
8+
#if os(macOS)
9+
import SwiftUI
10+
import LiveViewNativeCore
11+
import LiveViewNativeStylesheet
12+
13+
@ASTDecodable("MenuButtonStyle")
14+
enum StylesheetResolvableMenuButtonStyle: StylesheetResolvable, @preconcurrency Decodable {
15+
case `default`
16+
17+
func resolve<R>(on element: ElementNode, in context: LiveContext<R>) -> DefaultMenuButtonStyle where R : RootRegistry {
18+
return DefaultMenuButtonStyle()
19+
}
20+
}
21+
22+
extension StylesheetResolvableMenuButtonStyle: AttributeDecodable {
23+
nonisolated init(from attribute: Attribute?, on element: ElementNode) throws {
24+
self = .default
25+
}
26+
}
27+
#endif
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// NavigationViewStyle.swift
3+
// LiveViewNative
4+
//
5+
// Created by Carson Katri on 3/6/25.
6+
//
7+
8+
import SwiftUI
9+
import LiveViewNativeStylesheet
10+
import LiveViewNativeCore
11+
12+
struct StylesheetResolvableNavigationViewStyle: StylesheetResolvable, @preconcurrency Decodable, @preconcurrency AttributeDecodable {
13+
init(from decoder: any Decoder) throws {
14+
fatalError("`NavigationViewStyle` is deprecated")
15+
}
16+
17+
init(from attribute: Attribute?, on element: ElementNode) throws {
18+
fatalError("`NavigationViewStyle` is deprecated")
19+
}
20+
21+
func resolve<R>(on element: ElementNode, in context: LiveContext<R>) -> DefaultNavigationViewStyle where R : RootRegistry {
22+
fatalError("`NavigationViewStyle` is deprecated")
23+
}
24+
}

Sources/LiveViewNative/Stylesheets/ResolvableTypes/UIKit.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,29 @@ extension UIKeyboardType: @preconcurrency AttributeDecodable {
400400
}
401401
}
402402
}
403+
404+
extension UITextAutocapitalizationType {
405+
@ASTDecodable("UITextAutocapitalizationType")
406+
enum Resolvable: StylesheetResolvable, @preconcurrency Decodable {
407+
case none
408+
case words
409+
case sentences
410+
case allCharacters
411+
412+
func resolve<R>(on element: ElementNode, in context: LiveContext<R>) -> UITextAutocapitalizationType where R : RootRegistry {
413+
switch self {
414+
case .none:
415+
return .none
416+
case .words:
417+
return .words
418+
case .sentences:
419+
return .sentences
420+
case .allCharacters:
421+
return .allCharacters
422+
}
423+
}
424+
}
425+
}
403426
#endif
404427

405428
#endif

Sources/ModifierGenerator/ModifierGenerator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ struct ModifierGenerator: ParsableCommand {
1717
import Spatial
1818
import Symbols
1919
import LiveViewNativeStylesheet
20+
import OSLog
21+
22+
private let __generatedModifierLogger = Logger(subsystem: "LiveViewNative", category: "Stylesheet")
2023
2124
"""#
2225

Sources/ModifierGenerator/StyleDefinitionGenerator/StyleDefinitionGenerator.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public final class StyleDefinitionGenerator: SyntaxVisitor {
6161
guard node.modifiers.isAccessible
6262
else { return .visitChildren }
6363

64-
guard !node.attributes.isDeprecated
64+
guard !node.attributes.isObsoleted
6565
else { return .visitChildren }
6666

6767
// replicate all of the public symbols with `AttributeDecodable` wrapping
@@ -126,7 +126,7 @@ public final class StyleDefinitionGenerator: SyntaxVisitor {
126126
}) ?? false)
127127
else { return .visitChildren }
128128

129-
guard !node.attributes.isDeprecated
129+
guard !node.attributes.isObsoleted
130130
else { return .visitChildren }
131131

132132
// replicate all of the public symbols with `AttributeDecodable` wrapping
@@ -176,7 +176,11 @@ public final class StyleDefinitionGenerator: SyntaxVisitor {
176176
guard node.isViewExtension
177177
else { return .visitChildren }
178178

179-
guard !node.attributes.isDeprecated
179+
// if the extension has a generic constraint (such as `Self: Equatable`), skip it
180+
guard node.genericWhereClause == nil
181+
else { return .skipChildren }
182+
183+
guard !node.attributes.isObsoleted
180184
else { return .visitChildren }
181185

182186
let extensionAvailability = node.attributes.filter(\.isAvailability)
@@ -255,6 +259,23 @@ public final class StyleDefinitionGenerator: SyntaxVisitor {
255259
attributes: availability,
256260
signature: modifier.signature.with(\.returnClause, nil)
257261
) {
262+
// log a warning when the modifier is deprecated
263+
if modifier.attributes.isDeprecated {
264+
FunctionCallExprSyntax(
265+
callee: MemberAccessExprSyntax(
266+
base: DeclReferenceExprSyntax(baseName: .identifier("__generatedModifierLogger")),
267+
name: .identifier("warning")
268+
)
269+
) {
270+
if let deprecationMessage = modifier.attributes.deprecationMessage {
271+
LabeledExprSyntax(expression: StringLiteralExprSyntax(
272+
content: "'\(modifier.name.text)' is deprecated: \(deprecationMessage.segments.map(\.content.text).joined())"
273+
))
274+
} else {
275+
LabeledExprSyntax(expression: StringLiteralExprSyntax(content: "'\(modifier.name.text)' is deprecated and will be removed in a future version."))
276+
}
277+
}
278+
}
258279
InfixOperatorExprSyntax(
259280
leftOperand: MemberAccessExprSyntax(base: DeclReferenceExprSyntax(baseName: .identifier("self")), name: .identifier("__clause")),
260281
operator: AssignmentExprSyntax(),

Sources/ModifierGenerator/StyleDefinitionGenerator/makeResolvableType.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ extension StyleDefinitionGenerator {
7575
.filter {
7676
$0.isValidModifier
7777
&& $0.genericParameterClause == nil && $0.genericWhereClause == nil
78-
&& !$0.attributes.isDeprecated
78+
&& !$0.attributes.isObsoleted
7979
&& $0.optionalMark == nil
8080
}
8181
.map { $0.normalizedParameterTypes() }
@@ -84,7 +84,7 @@ extension StyleDefinitionGenerator {
8484
.filter(\.modifiers.isAccessible)
8585
.filter {
8686
$0.isValidModifier
87-
&& !$0.attributes.isDeprecated
87+
&& !$0.attributes.isObsoleted
8888
&& !$0.modifiers.contains(where: { $0.name.tokenKind == .keyword(.static) })
8989
&& (
9090
$0.signature.returnClause?.type.as(IdentifierTypeSyntax.self)?.name.tokenKind == .keyword(.Self)
@@ -99,7 +99,7 @@ extension StyleDefinitionGenerator {
9999
.filter {
100100
$0.isValidModifier
101101
&& $0.genericParameterClause == nil && $0.genericWhereClause == nil
102-
&& !$0.attributes.isDeprecated
102+
&& !$0.attributes.isObsoleted
103103
&& !$0.name.isOperatorToken
104104
&& $0.modifiers.contains(where: { $0.name.tokenKind == .keyword(.static) })
105105
&& (
@@ -112,7 +112,7 @@ extension StyleDefinitionGenerator {
112112
let staticMembers = members
113113
.compactMap({ $0.decl.as(VariableDeclSyntax.self) })
114114
.filter({
115-
!$0.attributes.isDeprecated
115+
!$0.attributes.isObsoleted
116116
&& !$0.bindings.contains(where: { $0.pattern.as(IdentifierPatternSyntax.self)?.identifier.text.starts(with: "_") ?? false })
117117
&& $0.modifiers.contains(where: { $0.name.tokenKind == .keyword(.static) })
118118
&& $0.modifiers.isAccessible

Sources/SwiftSyntaxExtensions/Availability.swift

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,40 @@ public extension AttributeListSyntax.Element {
3838
}
3939
}
4040

41+
extension AttributeSyntax {
42+
var deprecationMessage: SimpleStringLiteralExprSyntax? {
43+
var isDeprecated = false
44+
var message: SimpleStringLiteralExprSyntax?
45+
46+
for argument in arguments?.as(AvailabilityArgumentListSyntax.self) ?? [] {
47+
switch argument.argument {
48+
case let .token(token):
49+
if token.tokenKind == .keyword(.deprecated) {
50+
isDeprecated = true
51+
}
52+
case let .availabilityLabeledArgument(argument):
53+
if argument.label.tokenKind == .keyword(.deprecated) {
54+
isDeprecated = true
55+
}
56+
if argument.label.tokenKind == .keyword(.message) {
57+
message = argument.value.as(SimpleStringLiteralExprSyntax.self)
58+
if isDeprecated {
59+
return message
60+
}
61+
}
62+
default:
63+
continue
64+
}
65+
}
66+
67+
if !isDeprecated {
68+
return nil
69+
} else {
70+
return message
71+
}
72+
}
73+
}
74+
4175
public extension AttributeListSyntax {
4276
/// Checks if this symbol is marked `@MainActor`.
4377
var isActorIsolated: Bool {
@@ -56,18 +90,43 @@ public extension AttributeListSyntax {
5690
})
5791
}
5892

59-
/// Checks if this symbol is marked `@available(deprecated)` or `@available(obsoleted)`.
93+
/// Checks if this symbol is marked `@available(deprecated)`
6094
var isDeprecated: Bool {
6195
contains(where: {
6296
guard case let .attribute(attribute) = $0 else { return false }
6397
return attribute.arguments?.as(AvailabilityArgumentListSyntax.self)?.contains(where: {
6498
switch $0.argument {
6599
case let .token(token):
66100
return token.tokenKind == .keyword(.deprecated)
67-
|| token.tokenKind == .keyword(.obsoleted)
68101
case let .availabilityLabeledArgument(argument):
69102
return argument.label.tokenKind == .keyword(.deprecated)
70-
|| argument.label.tokenKind == .keyword(.obsoleted)
103+
default:
104+
return false
105+
}
106+
})
107+
?? false
108+
})
109+
}
110+
111+
var deprecationMessage: SimpleStringLiteralExprSyntax? {
112+
for case let .attribute(attribute) in self {
113+
if let message = attribute.deprecationMessage {
114+
return message
115+
}
116+
}
117+
return nil
118+
}
119+
120+
/// Checks if this symbol is marked `@available(obsoleted)`
121+
var isObsoleted: Bool {
122+
contains(where: {
123+
guard case let .attribute(attribute) = $0 else { return false }
124+
return attribute.arguments?.as(AvailabilityArgumentListSyntax.self)?.contains(where: {
125+
switch $0.argument {
126+
case let .token(token):
127+
return token.tokenKind == .keyword(.obsoleted)
128+
case let .availabilityLabeledArgument(argument):
129+
return argument.label.tokenKind == .keyword(.obsoleted)
71130
default:
72131
return false
73132
}

Sources/SwiftSyntaxExtensions/NormalizableDeclSyntax.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public extension NormalizableDeclSyntax {
2727
/// Check if the modifier decl has valid parameters.
2828
var isValidModifier: Bool {
2929
// deprecated/obsoleted symbols
30-
guard !attributes.isDeprecated
30+
guard !attributes.isObsoleted
3131
else { return false }
3232

3333
for parameter in signature.parameterClause.parameters {
@@ -225,6 +225,7 @@ public extension NormalizableDeclSyntax {
225225
else { fatalError("Unsupported parameter '\(functionType)'. Function parameters must have a 'Void' return value.") }
226226
return parameter
227227
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("Event"))))
228+
.with(\.defaultValue, nil)
228229
}
229230
if let optionalType = parameter.type.as(OptionalTypeSyntax.self),
230231
let tupleType = optionalType.wrappedType.as(TupleTypeSyntax.self),
@@ -237,6 +238,7 @@ public extension NormalizableDeclSyntax {
237238
else { fatalError("Unsupported parameter '\(functionType)'. Function parameters must have a 'Void' return value.") }
238239
return parameter
239240
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("Event"))))
241+
.with(\.defaultValue, nil)
240242
}
241243
if let attributedType = parameter.type.as(AttributedTypeSyntax.self),
242244
let functionType = attributedType.baseType.as(FunctionTypeSyntax.self)
@@ -247,6 +249,7 @@ public extension NormalizableDeclSyntax {
247249
else { fatalError("Unsupported parameter '\(functionType)'. Function parameters must have a 'Void' return value.") }
248250
return parameter
249251
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("Event"))))
252+
.with(\.defaultValue, nil)
250253
}
251254
if let attributedType = parameter.type.as(AttributedTypeSyntax.self),
252255
let tupleType = attributedType.baseType.as(TupleTypeSyntax.self),
@@ -259,6 +262,7 @@ public extension NormalizableDeclSyntax {
259262
else { fatalError("Unsupported parameter '\(functionType)'. Function parameters must have a 'Void' return value.") }
260263
return parameter
261264
.with(\.type, TypeSyntax(IdentifierTypeSyntax(name: .identifier("Event"))))
265+
.with(\.defaultValue, nil)
262266
}
263267
// [S] where S == String -> [String]
264268
// [S] where S: Protocol -> [StylesheetResolvableProtocol]
@@ -429,8 +433,17 @@ public extension NormalizableDeclSyntax {
429433
}))
430434
.makeAttributeReference()
431435
} else if let optionalType = parameter.type.as(OptionalTypeSyntax.self) { // T? -> T.Resolvable?
436+
// [T]? -> [T.Resolvable]?
437+
if let arrayType = optionalType.wrappedType.as(ArrayTypeSyntax.self) {
438+
return parameter
439+
.with(\.type, TypeSyntax(
440+
optionalType.with(\.wrappedType, TypeSyntax(
441+
arrayType.with(\.element, TypeSyntax(MemberTypeSyntax(baseType: arrayType.element, name: .identifier("Resolvable"))))
442+
))
443+
))
444+
}
432445
// Text? -> TextReference?
433-
if let memberType = optionalType.wrappedType.as(MemberTypeSyntax.self),
446+
else if let memberType = optionalType.wrappedType.as(MemberTypeSyntax.self),
434447
memberType.baseType.as(IdentifierTypeSyntax.self)?.name.text == "SwiftUICore",
435448
memberType.name.text == "Text"
436449
{

Sources/SwiftSyntaxExtensions/Types.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ public extension TypeSyntaxProtocol {
8383
return false
8484
}
8585

86+
// SwiftUI.ContextMenu<MenuItems> is not allowed
87+
if let memberType = self.as(MemberTypeSyntax.self),
88+
memberType.baseType.as(IdentifierTypeSyntax.self)?.name.text == "SwiftUI",
89+
memberType.name.text == "ContextMenu"
90+
{
91+
return false
92+
}
93+
94+
// SwiftUI.Alert is not allowed
95+
if let memberType = self.as(MemberTypeSyntax.self),
96+
memberType.baseType.as(IdentifierTypeSyntax.self)?.name.text == "SwiftUI",
97+
memberType.name.text == "Alert"
98+
{
99+
return false
100+
}
101+
86102
// Swift.Optional<() -> ()> with a function wrapped type is not allowed
87103
if let memberType = self.as(MemberTypeSyntax.self),
88104
memberType.baseType.as(MemberTypeSyntax.self)?.name.text == "Swift",

0 commit comments

Comments
 (0)