@@ -22,6 +22,25 @@ struct RedundantDiscardableLetRule: Rule {
2222 return Text( " Hello, World! " )
2323 }
2424 """ , configuration: [ " ignore_swiftui_view_bodies " : true ] ) ,
25+ Example ( """
26+ @ViewBuilder
27+ func bar() -> some View {
28+ let _ = foo()
29+ Text( " Hello, World! " )
30+ }
31+ """ , configuration: [ " ignore_swiftui_view_bodies " : true ] ) ,
32+ Example ( """
33+ #Preview {
34+ let _ = foo()
35+ Text( " Hello, World! " )
36+ }
37+ """ , configuration: [ " ignore_swiftui_view_bodies " : true ] ) ,
38+ Example ( """
39+ static var previews: some View {
40+ let _ = foo()
41+ Text( " Hello, World! " )
42+ }
43+ """ , configuration: [ " ignore_swiftui_view_bodies " : true ] ) ,
2544 ] ,
2645 triggeringExamples: [
2746 Example ( " ↓let _ = foo() " ) ,
@@ -32,10 +51,74 @@ struct RedundantDiscardableLetRule: Rule {
3251 Text( " Hello, World! " )
3352 }
3453 """ ) ,
54+ Example ( """
55+ @ViewBuilder
56+ func bar() -> some View {
57+ ↓let _ = foo()
58+ return Text( " Hello, World! " )
59+ }
60+ """ ) ,
61+ Example ( """
62+ #Preview {
63+ ↓let _ = foo()
64+ return Text( " Hello, World! " )
65+ }
66+ """ ) ,
67+ Example ( """
68+ static var previews: some View {
69+ ↓let _ = foo()
70+ Text( " Hello, World! " )
71+ }
72+ """ ) ,
73+ Example ( """
74+ var notBody: some View {
75+ ↓let _ = foo()
76+ Text( " Hello, World! " )
77+ }
78+ """ , configuration: [ " ignore_swiftui_view_bodies " : true ] , excludeFromDocumentation: true ) ,
79+ Example ( """
80+ var body: some NotView {
81+ ↓let _ = foo()
82+ Text( " Hello, World! " )
83+ }
84+ """ , configuration: [ " ignore_swiftui_view_bodies " : true ] , excludeFromDocumentation: true ) ,
3585 ] ,
3686 corrections: [
3787 Example ( " ↓let _ = foo() " ) : Example ( " _ = foo() " ) ,
3888 Example ( " if _ = foo() { ↓let _ = bar() } " ) : Example ( " if _ = foo() { _ = bar() } " ) ,
89+ Example ( """
90+ var body: some View {
91+ ↓let _ = foo()
92+ Text( " Hello, World! " )
93+ }
94+ """ ) : Example ( """
95+ var body: some View {
96+ _ = foo()
97+ Text( " Hello, World! " )
98+ }
99+ """ ) ,
100+ Example ( """
101+ #Preview {
102+ ↓let _ = foo()
103+ return Text( " Hello, World! " )
104+ }
105+ """ ) : Example ( """
106+ #Preview {
107+ _ = foo()
108+ return Text( " Hello, World! " )
109+ }
110+ """ ) ,
111+ Example ( """
112+ var body: some View {
113+ let _ = foo()
114+ return Text( " Hello, World! " )
115+ }
116+ """ , configuration: [ " ignore_swiftui_view_bodies " : true ] ) : Example ( """
117+ var body: some View {
118+ let _ = foo()
119+ return Text( " Hello, World! " )
120+ }
121+ """ ) ,
39122 ]
40123 )
41124}
@@ -50,23 +133,32 @@ private extension RedundantDiscardableLetRule {
50133 private var codeBlockScopes = Stack < CodeBlockKind > ( )
51134
52135 override func visit( _ node: AccessorBlockSyntax ) -> SyntaxVisitorContinueKind {
53- codeBlockScopes. push ( node. isViewBody ? . view : . normal)
136+ codeBlockScopes. push ( node. isViewBody || node . isPreviewProviderBody ? . view : . normal)
54137 return . visitChildren
55138 }
56139
57140 override func visitPost( _: AccessorBlockSyntax ) {
58141 codeBlockScopes. pop ( )
59142 }
60143
61- override func visit( _: CodeBlockSyntax ) -> SyntaxVisitorContinueKind {
62- codeBlockScopes. push ( . normal)
144+ override func visit( _ node : CodeBlockSyntax ) -> SyntaxVisitorContinueKind {
145+ codeBlockScopes. push ( node . isViewBuilderFunctionBody ? . view : . normal)
63146 return . visitChildren
64147 }
65148
66149 override func visitPost( _: CodeBlockSyntax ) {
67150 codeBlockScopes. pop ( )
68151 }
69152
153+ override func visit( _ node: ClosureExprSyntax ) -> SyntaxVisitorContinueKind {
154+ codeBlockScopes. push ( node. isPreviewMacroBody ? . view : . normal)
155+ return . visitChildren
156+ }
157+
158+ override func visitPost( _: ClosureExprSyntax ) {
159+ codeBlockScopes. pop ( )
160+ }
161+
70162 override func visitPost( _ node: VariableDeclSyntax ) {
71163 if codeBlockScopes. peek ( ) != . view || !configuration. ignoreSwiftUIViewBodies,
72164 node. bindingSpecifier. tokenKind == . keyword( . let) ,
@@ -94,10 +186,45 @@ private extension AccessorBlockSyntax {
94186 if let binding = parent? . as ( PatternBindingSyntax . self) ,
95187 binding. pattern. as ( IdentifierPatternSyntax . self) ? . identifier. text == " body " ,
96188 let type = binding. typeAnnotation? . type. as ( SomeOrAnyTypeSyntax . self) {
97- return type. someOrAnySpecifier. text == " some "
98- && type. constraint. as ( IdentifierTypeSyntax . self) ? . name. text == " View "
99- && binding. parent? . parent? . is ( VariableDeclSyntax . self) == true
189+ return type. isView && binding. parent? . parent? . is ( VariableDeclSyntax . self) == true
100190 }
101191 return false
102192 }
193+
194+ var isPreviewProviderBody : Bool {
195+ guard let binding = parent? . as ( PatternBindingSyntax . self) ,
196+ binding. pattern. as ( IdentifierPatternSyntax . self) ? . identifier. text == " previews " ,
197+ let bindingList = binding. parent? . as ( PatternBindingListSyntax . self) ,
198+ let variableDecl = bindingList. parent? . as ( VariableDeclSyntax . self) ,
199+ variableDecl. modifiers. contains ( keyword: . static) ,
200+ variableDecl. bindingSpecifier. tokenKind == . keyword( . var) ,
201+ let type = binding. typeAnnotation? . type. as ( SomeOrAnyTypeSyntax . self) else {
202+ return false
203+ }
204+
205+ return type. isView
206+ }
207+ }
208+
209+ private extension CodeBlockSyntax {
210+ var isViewBuilderFunctionBody : Bool {
211+ guard let functionDecl = parent? . as ( FunctionDeclSyntax . self) ,
212+ functionDecl. attributes. contains ( attributeNamed: " ViewBuilder " ) else {
213+ return false
214+ }
215+ return functionDecl. signature. returnClause? . type. as ( SomeOrAnyTypeSyntax . self) ? . isView ?? false
216+ }
217+ }
218+
219+ private extension ClosureExprSyntax {
220+ var isPreviewMacroBody : Bool {
221+ parent? . as ( MacroExpansionExprSyntax . self) ? . macroName. text == " Preview "
222+ }
223+ }
224+
225+ private extension SomeOrAnyTypeSyntax {
226+ var isView : Bool {
227+ someOrAnySpecifier. text == " some " &&
228+ constraint. as ( IdentifierTypeSyntax . self) ? . name. text == " View "
229+ }
103230}
0 commit comments