From f6e5f778fce83a2664171b407e5f1183c04fc0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danny=20M=C3=B6sch?= Date: Tue, 3 Oct 2023 13:16:33 +0200 Subject: [PATCH] Configure expression folding by argument of @SwiftSyntaxRule macro (#5255) --- .../Rules/Idiomatic/LegacyMultipleRule.swift | 3 +- .../Rules/Lint/IdenticalOperandsRule.swift | 3 +- .../ContainsOverFirstNotNilRule.swift | 3 +- .../ContainsOverRangeNilComparisonRule.swift | 3 +- .../Rules/Performance/EmptyCountRule.swift | 5 +- .../Rules/Style/ShorthandOperatorRule.swift | 3 +- Source/SwiftLintCore/Helpers/Macros.swift | 14 +-- Source/SwiftLintCoreMacros/Fold.swift | 22 ----- .../SwiftLintCoreMacros.swift | 1 - .../SwiftLintCoreMacros/SwiftSyntaxRule.swift | 34 +++++++ Tests/MacroTests/MacroTests.swift | 47 --------- Tests/MacroTests/SwiftSyntaxRuleTests.swift | 97 +++++++++++++++++++ 12 files changed, 144 insertions(+), 91 deletions(-) delete mode 100644 Source/SwiftLintCoreMacros/Fold.swift delete mode 100644 Tests/MacroTests/MacroTests.swift create mode 100644 Tests/MacroTests/SwiftSyntaxRuleTests.swift diff --git a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/LegacyMultipleRule.swift b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/LegacyMultipleRule.swift index 8d67ed8bfd..f4b94467e0 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/LegacyMultipleRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/LegacyMultipleRule.swift @@ -1,7 +1,6 @@ import SwiftSyntax -@Fold -@SwiftSyntaxRule +@SwiftSyntaxRule(foldExpressions: true) struct LegacyMultipleRule: OptInRule, ConfigurationProviderRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/IdenticalOperandsRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/IdenticalOperandsRule.swift index 831f9371a1..501d2add24 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/IdenticalOperandsRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/IdenticalOperandsRule.swift @@ -1,7 +1,6 @@ import SwiftSyntax -@Fold -@SwiftSyntaxRule +@SwiftSyntaxRule(foldExpressions: true) struct IdenticalOperandsRule: ConfigurationProviderRule, OptInRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverFirstNotNilRule.swift b/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverFirstNotNilRule.swift index 22ba24783a..adfabfcd71 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverFirstNotNilRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverFirstNotNilRule.swift @@ -1,7 +1,6 @@ import SwiftSyntax -@Fold -@SwiftSyntaxRule +@SwiftSyntaxRule(foldExpressions: true) struct ContainsOverFirstNotNilRule: OptInRule, ConfigurationProviderRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverRangeNilComparisonRule.swift b/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverRangeNilComparisonRule.swift index f74d88e28f..5307de1760 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverRangeNilComparisonRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Performance/ContainsOverRangeNilComparisonRule.swift @@ -1,7 +1,6 @@ import SwiftSyntax -@Fold -@SwiftSyntaxRule +@SwiftSyntaxRule(foldExpressions: true) struct ContainsOverRangeNilComparisonRule: OptInRule, ConfigurationProviderRule { var configuration = SeverityConfiguration(.warning) diff --git a/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift b/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift index 61be58c99b..37f568e5dc 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift @@ -1,6 +1,5 @@ import SwiftSyntax -@Fold struct EmptyCountRule: ConfigurationProviderRule, OptInRule, SwiftSyntaxRule { var configuration = EmptyCountConfiguration() @@ -37,6 +36,10 @@ struct EmptyCountRule: ConfigurationProviderRule, OptInRule, SwiftSyntaxRule { func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { Visitor(onlyAfterDot: configuration.onlyAfterDot) } + + func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { + file.foldedSyntaxTree + } } private extension EmptyCountRule { diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/ShorthandOperatorRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/ShorthandOperatorRule.swift index 184bc39d98..e785699d37 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/ShorthandOperatorRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/ShorthandOperatorRule.swift @@ -1,7 +1,6 @@ import SwiftSyntax -@Fold -@SwiftSyntaxRule +@SwiftSyntaxRule(foldExpressions: true) struct ShorthandOperatorRule: ConfigurationProviderRule { var configuration = SeverityConfiguration(.error) diff --git a/Source/SwiftLintCore/Helpers/Macros.swift b/Source/SwiftLintCore/Helpers/Macros.swift index c10e2c11e1..caa8e43487 100644 --- a/Source/SwiftLintCore/Helpers/Macros.swift +++ b/Source/SwiftLintCore/Helpers/Macros.swift @@ -14,16 +14,10 @@ public macro MakeAcceptableByConfigurationElement() = #externalMacro( ) /// Macro that adds a conformance to the `SwiftSyntaxRule` protocol and a default `makeVisitor(file:)` implementation -/// that creates a visitor defined in the same file. -@attached(extension, conformances: SwiftSyntaxRule, names: named(makeVisitor(file:))) -public macro SwiftSyntaxRule() = #externalMacro( +/// that creates a visitor defined in the same file. It also adds an implementation of `preprocess(file:)` which folds +/// expressions if the `foldExpressions` argument is set. +@attached(extension, conformances: SwiftSyntaxRule, names: named(makeVisitor(file:)), named(preprocess(file:))) +public macro SwiftSyntaxRule(foldExpressions: Bool = false) = #externalMacro( module: "SwiftLintCoreMacros", type: "SwiftSyntaxRule" ) - -/// Macro that preprocesses the file by folding its operators before processing it for rule violations. -@attached(extension, names: named(preprocess(file:))) -public macro Fold() = #externalMacro( - module: "SwiftLintCoreMacros", - type: "Fold" -) diff --git a/Source/SwiftLintCoreMacros/Fold.swift b/Source/SwiftLintCoreMacros/Fold.swift deleted file mode 100644 index 32e531bd8c..0000000000 --- a/Source/SwiftLintCoreMacros/Fold.swift +++ /dev/null @@ -1,22 +0,0 @@ -import SwiftSyntax -import SwiftSyntaxMacros - -struct Fold: ExtensionMacro { - static func expansion( - of node: AttributeSyntax, - attachedTo declaration: some DeclGroupSyntax, - providingExtensionsOf type: some TypeSyntaxProtocol, - conformingTo protocols: [TypeSyntax], - in context: some MacroExpansionContext - ) throws -> [ExtensionDeclSyntax] { - return [ - try ExtensionDeclSyntax(""" - extension \(type) { - func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { - file.foldedSyntaxTree - } - } - """) - ] - } -} diff --git a/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift b/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift index 47b465d6b1..36edcc48bb 100644 --- a/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift +++ b/Source/SwiftLintCoreMacros/SwiftLintCoreMacros.swift @@ -5,7 +5,6 @@ import SwiftSyntaxMacros struct SwiftLintCoreMacros: CompilerPlugin { let providingMacros: [Macro.Type] = [ AutoApply.self, - Fold.self, MakeAcceptableByConfigurationElement.self, SwiftSyntaxRule.self ] diff --git a/Source/SwiftLintCoreMacros/SwiftSyntaxRule.swift b/Source/SwiftLintCoreMacros/SwiftSyntaxRule.swift index 7a26576afc..2340f7b1ad 100644 --- a/Source/SwiftLintCoreMacros/SwiftSyntaxRule.swift +++ b/Source/SwiftLintCoreMacros/SwiftSyntaxRule.swift @@ -15,8 +15,42 @@ struct SwiftSyntaxRule: ExtensionMacro { func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { Visitor(viewMode: .sourceAccurate) } + \(createFoldingPreprocessor(from: node.foldArgument)) } """) ] } + + private static func createFoldingPreprocessor(from foldArgument: ExprSyntax?) -> DeclSyntax { + guard let foldArgument else { + return "" + } + if let booleanLiteral = foldArgument.as(BooleanLiteralExprSyntax.self)?.literal { + if booleanLiteral.text == "true" { + return """ + func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { + file.foldedSyntaxTree + } + """ + } + if booleanLiteral.text == "false" { + return "" + } + } + return """ + func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { + if \(foldArgument) { file.foldedSyntaxTree } else { nil } + } + """ + } +} + +private extension AttributeSyntax { + var foldArgument: ExprSyntax? { + if case let .argumentList(args) = arguments, let first = args.first, first.label?.text == "foldExpressions" { + first.expression + } else { + nil + } + } } diff --git a/Tests/MacroTests/MacroTests.swift b/Tests/MacroTests/MacroTests.swift deleted file mode 100644 index 06fa0c1c34..0000000000 --- a/Tests/MacroTests/MacroTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -@testable import SwiftLintCoreMacros -import SwiftSyntaxMacrosTestSupport -import XCTest - -final class MacroTests: XCTestCase { - func testFold() { - assertMacroExpansion( - """ - @Fold - struct Hello {} - """, - expandedSource: """ - struct Hello {} - - extension Hello { - func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { - file.foldedSyntaxTree - } - } - """, - macros: [ - "Fold": Fold.self - ] - ) - } - - func testSwiftSyntaxRule() { - assertMacroExpansion( - """ - @SwiftSyntaxRule - struct Hello {} - """, - expandedSource: """ - struct Hello {} - - extension Hello: SwiftSyntaxRule { - func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { - Visitor(viewMode: .sourceAccurate) - } - } - """, - macros: [ - "SwiftSyntaxRule": SwiftSyntaxRule.self - ] - ) - } -} diff --git a/Tests/MacroTests/SwiftSyntaxRuleTests.swift b/Tests/MacroTests/SwiftSyntaxRuleTests.swift new file mode 100644 index 0000000000..6dcdb14b0f --- /dev/null +++ b/Tests/MacroTests/SwiftSyntaxRuleTests.swift @@ -0,0 +1,97 @@ +@testable import SwiftLintCoreMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +private let macros = [ + "SwiftSyntaxRule": SwiftSyntaxRule.self +] + +final class SwiftSyntaxRuleTests: XCTestCase { + func testNoFoldArgument() { + assertMacroExpansion( + """ + @SwiftSyntaxRule + struct Hello {} + """, + expandedSource: """ + struct Hello {} + + extension Hello: SwiftSyntaxRule { + func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { + Visitor(viewMode: .sourceAccurate) + } + + } + """, + macros: macros + ) + } + + func testFalseFoldArgument() { + assertMacroExpansion( + """ + @SwiftSyntaxRule(foldExpressions: false) + struct Hello {} + """, + expandedSource: """ + struct Hello {} + + extension Hello: SwiftSyntaxRule { + func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { + Visitor(viewMode: .sourceAccurate) + } + + } + """, + macros: macros + ) + } + + func testTrueFoldArgument() { + assertMacroExpansion( + """ + @SwiftSyntaxRule(foldExpressions: true) + struct Hello {} + """, + expandedSource: """ + struct Hello {} + + extension Hello: SwiftSyntaxRule { + func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { + Visitor(viewMode: .sourceAccurate) + } + func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { + file.foldedSyntaxTree + } + } + """, + macros: macros + ) + } + + func testArbitraryFoldArgument() { + assertMacroExpansion( + """ + @SwiftSyntaxRule(foldExpressions: variable) + struct Hello {} + """, + expandedSource: """ + struct Hello {} + + extension Hello: SwiftSyntaxRule { + func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor { + Visitor(viewMode: .sourceAccurate) + } + func preprocess(file: SwiftLintFile) -> SourceFileSyntax? { + if variable { + file.foldedSyntaxTree + } else { + nil + } + } + } + """, + macros: macros + ) + } +}