Make use of macros to generate configuration parsing code (#5250)

This commit is contained in:
Danny Mösch
2023-10-02 22:35:24 +02:00
committed by GitHub
parent 696e19df33
commit 2433e7b5bf
85 changed files with 738 additions and 1215 deletions
+1
View File
@@ -69,6 +69,7 @@ file_name:
excluded:
- Exports.swift
- GeneratedTests.swift
- RuleConfigurationMacros.swift
- SwiftSyntax+SwiftLint.swift
- TestHelpers.swift
+13
View File
@@ -3,10 +3,20 @@ load(
"@build_bazel_rules_swift//swift:swift.bzl",
"swift_binary",
"swift_library",
"swift_compiler_plugin"
)
# Targets
swift_compiler_plugin(
name = "SwiftLintCoreMacros",
srcs = glob(["Source/SwiftLintCoreMacros/*.swift"]),
deps = [
"@SwiftSyntax//:SwiftCompilerPlugin_opt",
"@SwiftSyntax//:SwiftSyntaxMacros_opt",
],
)
swift_library(
name = "SwiftLintCore",
srcs = glob(["Source/SwiftLintCore/**/*.swift"]),
@@ -25,6 +35,9 @@ swift_library(
"@platforms//os:linux": ["@com_github_krzyzanowskim_cryptoswift//:CryptoSwift"],
"//conditions:default": [":DyldWarningWorkaround"],
}),
plugins = [
":SwiftLintCoreMacros",
],
)
swift_library(
+3
View File
@@ -133,6 +133,9 @@ package: build
--version "$(VERSION_STRING)" \
"$(OUTPUT_PACKAGE)"
bazel_test:
bazel test --test_output=errors //Tests/...
bazel_release:
bazel build :release
mv bazel-bin/bazel.tar.gz bazel-bin/bazel.tar.gz.sha256 .
+13 -2
View File
@@ -1,4 +1,5 @@
// swift-tools-version:5.9
import CompilerPluginSupport
import PackageDescription
let package = Package(
@@ -55,6 +56,7 @@ let package = Package(
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "SwiftyTextTable", package: "SwiftyTextTable"),
.product(name: "Yams", package: "Yams"),
"SwiftLintCoreMacros"
]
),
.target(
@@ -88,7 +90,8 @@ let package = Package(
name: "SwiftLintFrameworkTests",
dependencies: [
"SwiftLintFramework",
"SwiftLintTestHelpers"
"SwiftLintTestHelpers",
"SwiftLintCoreMacros"
],
exclude: [
"Resources",
@@ -119,6 +122,14 @@ let package = Package(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.53.0/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "03416a4f75f023e10f9a76945806ddfe70ca06129b895455cc773c5c7d86b73e"
)
),
.macro(
name: "SwiftLintCoreMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
],
path: "Source/SwiftLintCoreMacros"
),
]
)
@@ -229,7 +229,7 @@ private extension ClassDeclSyntax {
func hasParent(configuredIn config: PrivateUnitTestConfiguration) -> Bool {
inheritanceClause?.inheritedTypes.contains { type in
if let name = type.type.as(IdentifierTypeSyntax.self)?.name.text {
return config.regex.numberOfMatches(in: name, range: name.fullNSRange) > 0
return config.regex.regex.numberOfMatches(in: name, range: name.fullNSRange) > 0
|| config.testParentClasses.contains(name)
}
return false
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct AttributesConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = AttributesRule
@@ -11,27 +12,4 @@ struct AttributesConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var alwaysOnSameLine = Set<String>(["@IBAction", "@NSManaged"])
@ConfigurationElement(key: "always_on_line_above")
private(set) var alwaysOnNewLine = Set<String>()
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let attributesWithArgumentsAlwaysOnNewLine
= configuration[$attributesWithArgumentsAlwaysOnNewLine] as? Bool {
self.attributesWithArgumentsAlwaysOnNewLine = attributesWithArgumentsAlwaysOnNewLine
}
if let alwaysOnSameLine = configuration[$alwaysOnSameLine] as? [String] {
self.alwaysOnSameLine = Set(alwaysOnSameLine)
}
if let alwaysOnNewLine = configuration[$alwaysOnNewLine] as? [String] {
self.alwaysOnNewLine = Set(alwaysOnNewLine)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct BlanketDisableCommandConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = BlanketDisableCommandRule
@@ -15,22 +16,4 @@ struct BlanketDisableCommandConfiguration: SeverityBasedRuleConfiguration, Equat
]
@ConfigurationElement(key: "always_blanket_disable")
private(set) var alwaysBlanketDisableRuleIdentifiers: Set<String> = []
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let allowedRuleIdentifiers = configuration[$allowedRuleIdentifiers] as? [String] {
self.allowedRuleIdentifiers = Set(allowedRuleIdentifiers)
}
if let alwaysBlanketDisableRuleIdentifiers = configuration[$alwaysBlanketDisableRuleIdentifiers] as? [String] {
self.alwaysBlanketDisableRuleIdentifiers = Set(alwaysBlanketDisableRuleIdentifiers)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct CollectionAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = CollectionAlignmentRule
@@ -7,16 +8,4 @@ struct CollectionAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatab
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "align_colons")
private(set) var alignColons = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
alignColons = configuration[$alignColons] as? Bool ?? false
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct ColonConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ColonRule
@@ -9,17 +10,4 @@ struct ColonConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var flexibleRightSpacing = false
@ConfigurationElement(key: "apply_to_dictionaries")
private(set) var applyToDictionaries = true
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
flexibleRightSpacing = configuration[$flexibleRightSpacing] as? Bool == true
applyToDictionaries = configuration[$applyToDictionaries] as? Bool ?? true
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,32 +1,17 @@
import SwiftLintCore
@AutoApply
struct ComputedAccessorsOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ComputedAccessorsOrderRule
enum Order: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum Order: String {
case getSet = "get_set"
case setGet = "set_get"
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "order")
private(set) var order = Order.getSet
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let orderString = configuration[$order] as? String,
let order = Order(rawValue: orderString) {
self.order = order
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct ConditionalReturnsOnNewlineConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ConditionalReturnsOnNewlineRule
@@ -7,16 +8,4 @@ struct ConditionalReturnsOnNewlineConfiguration: SeverityBasedRuleConfiguration,
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "if_only")
private(set) var ifOnly = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
ifOnly = configuration[$ifOnly] as? Bool ?? false
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,6 +1,7 @@
import SourceKittenFramework
import SwiftLintCore
@AutoApply
struct CyclomaticComplexityConfiguration: RuleConfiguration, Equatable {
typealias Parent = CyclomaticComplexityRule
@@ -10,50 +11,19 @@ struct CyclomaticComplexityConfiguration: RuleConfiguration, Equatable {
.guard,
.for,
.repeatWhile,
.while,
.case
.while
]
@ConfigurationElement
private(set) var length = SeverityLevelsConfiguration<Parent>(warning: 10, error: 20)
private(set) var complexityStatements = Self.defaultComplexityStatements
@ConfigurationElement(key: "ignores_case_statements")
private(set) var ignoresCaseStatements = false {
didSet {
if ignoresCaseStatements {
complexityStatements.remove(.case)
} else {
complexityStatements.insert(.case)
}
}
}
private(set) var ignoresCaseStatements = false
var params: [RuleParameter<Int>] {
return length.params
}
mutating func apply(configuration: Any) throws {
if let configurationArray = [Int].array(of: configuration),
configurationArray.isNotEmpty {
let warning = configurationArray[0]
let error = (configurationArray.count > 1) ? configurationArray[1] : nil
length = SeverityLevelsConfiguration<Parent>(warning: warning, error: error)
} else if let configDict = configuration as? [String: Any], configDict.isNotEmpty {
for (string, value) in configDict {
switch (string, value) {
case ("error", let intValue as Int):
length.error = intValue
case ("warning", let intValue as Int):
length.warning = intValue
case ($ignoresCaseStatements, let boolValue as Bool):
ignoresCaseStatements = boolValue
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
var complexityStatements: Set<StatementKind> {
Self.defaultComplexityStatements.union(ignoresCaseStatements ? [] : [.case])
}
}
@@ -1,37 +1,22 @@
import SwiftLintCore
private func toExplicitInitMethod(typeName: String) -> String {
return "\(typeName).init"
}
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable let_var_whitespace
@AutoApply
struct DiscouragedDirectInitConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = DiscouragedDirectInitRule
@ConfigurationElement(key: "severity")
var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private static let defaultDiscouragedInits = [
@ConfigurationElement(
key: "types",
postprocessor: { $0.formUnion($0.map { name in "\(name).init" }) }
)
private(set) var discouragedInits: Set = [
"Bundle",
"NSError",
"UIDevice"
]
@ConfigurationElement(key: "types")
private(set) var discouragedInits = Set(
Self.defaultDiscouragedInits + Self.defaultDiscouragedInits.map(toExplicitInitMethod)
)
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let types = [String].array(of: configuration[$discouragedInits]) {
discouragedInits = Set(types + types.map(toExplicitInitMethod))
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct EmptyCountConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = EmptyCountRule
@@ -7,16 +8,4 @@ struct EmptyCountConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.error)
@ConfigurationElement(key: "only_after_dot")
private(set) var onlyAfterDot = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
onlyAfterDot = configuration[$onlyAfterDot] as? Bool ?? false
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct ExpiringTodoConfiguration: RuleConfiguration, Equatable {
typealias Parent = ExpiringTodoRule
typealias Severity = SeverityConfiguration<Parent>
@@ -10,6 +11,20 @@ struct ExpiringTodoConfiguration: RuleConfiguration, Equatable {
fileprivate(set) var opening: String
fileprivate(set) var closing: String
init(fromAny value: Any, context ruleID: String) throws {
guard let dateDelimiters = value as? [String: String],
let openingDelimiter = dateDelimiters["opening"],
let closingDelimiter = dateDelimiters["closing"] else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self.init(opening: openingDelimiter, closing: closingDelimiter)
}
init(opening: String, closing: String) {
self.opening = opening
self.closing = closing
}
func asOption() -> OptionType {
.nest {
"opening" => .string(opening)
@@ -38,37 +53,4 @@ struct ExpiringTodoConfiguration: RuleConfiguration, Equatable {
/// The separator used for regex detection of the expiry-date string
@ConfigurationElement(key: "date_separator")
private(set) var dateSeparator = "/"
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let approachingExpiryConfiguration = configurationDict[$approachingExpirySeverity] {
try approachingExpirySeverity.apply(configuration: approachingExpiryConfiguration)
}
if let expiredConfiguration = configurationDict[$expiredSeverity] {
try expiredSeverity.apply(configuration: expiredConfiguration)
}
if let badFormattingConfiguration = configurationDict[$badFormattingSeverity] {
try badFormattingSeverity.apply(configuration: badFormattingConfiguration)
}
if let approachingExpiryThreshold = configurationDict[$approachingExpiryThreshold] as? Int {
self.approachingExpiryThreshold = approachingExpiryThreshold
}
if let dateFormat = configurationDict[$dateFormat] as? String {
self.dateFormat = dateFormat
}
if let dateDelimiters = configurationDict[$dateDelimiters] as? [String: String] {
if let openingDelimiter = dateDelimiters["opening"] {
self.dateDelimiters.opening = openingDelimiter
}
if let closingDelimiter = dateDelimiters["closing"] {
self.dateDelimiters.closing = closingDelimiter
}
}
if let dateSeparator = configurationDict[$dateSeparator] as? String {
self.dateSeparator = dateSeparator
}
}
}
@@ -1,3 +1,4 @@
@AutoApply
struct ExplicitInitConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ExplicitInitRule
@@ -5,18 +6,4 @@ struct ExplicitInitConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "include_bare_init")
private(set) var includeBareInit = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let includeBareInit = configuration[$includeBareInit] as? Bool {
self.includeBareInit = includeBareInit
}
}
}
@@ -1,17 +1,17 @@
import SwiftLintCore
@AutoApply
struct ExplicitTypeInterfaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ExplicitTypeInterfaceRule
enum VariableKind: String, CaseIterable, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum VariableKind: String, CaseIterable {
case instance
case local
case `static`
case `class`
static let all = Set(allCases)
func asOption() -> SwiftLintCore.OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
@@ -24,22 +24,4 @@ struct ExplicitTypeInterfaceConfiguration: SeverityBasedRuleConfiguration, Equat
var allowedKinds: Set<VariableKind> {
VariableKind.all.subtracting(excluded)
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
for (key, value) in configuration {
switch (key, value) {
case ($severityConfiguration, let severityString as String):
try severityConfiguration.apply(configuration: severityString)
case ($excluded, let excludedStrings as [String]):
self.excluded = excludedStrings.compactMap(VariableKind.init).unique
case ($allowRedundancy, let allowRedundancy as Bool):
self.allowRedundancy = allowRedundancy
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
}
@@ -34,33 +34,33 @@ struct FileHeaderConfiguration: SeverityBasedRuleConfiguration, Equatable {
// Cache the created regexes if possible.
// If the pattern contains the SWIFTLINT_CURRENT_FILENAME placeholder,
// the regex will be recompiled for each validated file.
if let requiredString = configuration[$requiredString] {
if let requiredString = configuration[$requiredString.key] {
self.requiredString = requiredString
if !requiredString.contains(Self.fileNamePlaceholder) {
_requiredRegex = try NSRegularExpression(pattern: requiredString,
options: Self.stringRegexOptions)
}
} else if let requiredPattern = configuration[$requiredPattern] {
} else if let requiredPattern = configuration[$requiredPattern.key] {
self.requiredPattern = requiredPattern
if !requiredPattern.contains(Self.fileNamePlaceholder) {
_requiredRegex = try .cached(pattern: requiredPattern)
}
}
if let forbiddenString = configuration[$forbiddenString] {
if let forbiddenString = configuration[$forbiddenString.key] {
self.forbiddenString = forbiddenString
if !forbiddenString.contains(Self.fileNamePlaceholder) {
_forbiddenRegex = try NSRegularExpression(pattern: forbiddenString,
options: Self.stringRegexOptions)
}
} else if let forbiddenPattern = configuration[$forbiddenPattern] {
} else if let forbiddenPattern = configuration[$forbiddenPattern.key] {
self.forbiddenPattern = forbiddenPattern
if !forbiddenPattern.contains(Self.fileNamePlaceholder) {
_forbiddenRegex = try .cached(pattern: forbiddenPattern)
}
}
if let severityString = configuration[$severityConfiguration] {
if let severityString = configuration[$severityConfiguration.key] {
try severityConfiguration.apply(configuration: severityString)
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct FileLengthConfiguration: RuleConfiguration, Equatable {
typealias Parent = FileLengthRule
@@ -7,28 +8,4 @@ struct FileLengthConfiguration: RuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityLevelsConfiguration<Parent>(warning: 400, error: 1000)
@ConfigurationElement(key: "ignore_comment_only_lines")
private(set) var ignoreCommentOnlyLines = false
mutating func apply(configuration: Any) throws {
if let configurationArray = [Int].array(of: configuration),
configurationArray.isNotEmpty {
let warning = configurationArray[0]
let error = (configurationArray.count > 1) ? configurationArray[1] : nil
severityConfiguration = SeverityLevelsConfiguration(warning: warning, error: error)
} else if let configDict = configuration as? [String: Any], configDict.isNotEmpty {
for (string, value) in configDict {
switch (string, value) {
case ("error", let intValue as Int):
severityConfiguration.error = intValue
case ("warning", let intValue as Int):
severityConfiguration.warning = intValue
case ($ignoreCommentOnlyLines, let boolValue as Bool):
ignoreCommentOnlyLines = boolValue
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
@@ -1,38 +1,17 @@
import SwiftLintCore
@AutoApply
struct FileNameConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = FileNameRule
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "excluded")
private(set) var excluded = Set<String>(["main.swift", "LinuxMain.swift"])
private(set) var excluded: Set = ["main.swift", "LinuxMain.swift"]
@ConfigurationElement(key: "prefix_pattern")
private(set) var prefixPattern = ""
@ConfigurationElement(key: "suffix_pattern")
private(set) var suffixPattern = "\\+.*"
@ConfigurationElement(key: "nested_type_separator")
private(set) var nestedTypeSeparator = "."
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severity = configurationDict[$severityConfiguration] {
try severityConfiguration.apply(configuration: severity)
}
if let excluded = [String].array(of: configurationDict[$excluded]) {
self.excluded = Set(excluded)
}
if let prefixPattern = configurationDict[$prefixPattern] as? String {
self.prefixPattern = prefixPattern
}
if let suffixPattern = configurationDict[$suffixPattern] as? String {
self.suffixPattern = suffixPattern
}
if let nestedTypeSeparator = configurationDict[$nestedTypeSeparator] as? String {
self.nestedTypeSeparator = nestedTypeSeparator
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct FileNameNoSpaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = FileNameNoSpaceRule
@@ -7,17 +8,4 @@ struct FileNameNoSpaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
@ConfigurationElement(key: "excluded")
private(set) var excluded = Set<String>()
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severity = configurationDict[$severityConfiguration] {
try severityConfiguration.apply(configuration: severity)
}
if let excluded = [String].array(of: configurationDict[$excluded]) {
self.excluded = Set(excluded)
}
}
}
@@ -1,18 +1,18 @@
import SwiftLintCore
enum FileType: String, AcceptableByConfigurationElement {
case supportingType = "supporting_type"
case mainType = "main_type"
case `extension` = "extension"
case previewProvider = "preview_provider"
case libraryContentProvider = "library_content_provider"
func asOption() -> OptionType { .symbol(rawValue) }
}
@AutoApply
struct FileTypesOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = FileTypesOrderRule
@MakeAcceptableByConfigurationElement
enum FileType: String {
case supportingType = "supporting_type"
case mainType = "main_type"
case `extension` = "extension"
case previewProvider = "preview_provider"
case libraryContentProvider = "library_content_provider"
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "order")
@@ -23,28 +23,4 @@ struct FileTypesOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
[.previewProvider],
[.libraryContentProvider]
]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
var customOrder = [[FileType]]()
if let custom = configuration[$order] as? [Any] {
for entry in custom {
if let singleEntry = entry as? String {
if let fileType = FileType(rawValue: singleEntry) {
customOrder.append([fileType])
}
} else if let arrayEntry = entry as? [String] {
let fileTypes = arrayEntry.compactMap { FileType(rawValue: $0) }
customOrder.append(fileTypes)
}
}
}
if customOrder.isNotEmpty {
self.order = customOrder
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct ForWhereConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ForWhereRule
@@ -7,16 +8,4 @@ struct ForWhereConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "allow_for_as_filter")
private(set) var allowForAsFilter = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
allowForAsFilter = configuration[$allowForAsFilter] as? Bool ?? false
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct FunctionParameterCountConfiguration: RuleConfiguration, Equatable {
typealias Parent = FunctionParameterCountRule
@@ -7,28 +8,4 @@ struct FunctionParameterCountConfiguration: RuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityLevelsConfiguration<Parent>(warning: 5, error: 8)
@ConfigurationElement(key: "ignores_default_parameters")
private(set) var ignoresDefaultParameters = true
mutating func apply(configuration: Any) throws {
if let configurationArray = [Int].array(of: configuration),
configurationArray.isNotEmpty {
let warning = configurationArray[0]
let error = (configurationArray.count > 1) ? configurationArray[1] : nil
severityConfiguration = SeverityLevelsConfiguration(warning: warning, error: error)
} else if let configDict = configuration as? [String: Any], configDict.isNotEmpty {
for (string, value) in configDict {
switch (string, value) {
case ("error", let intValue as Int):
severityConfiguration.error = intValue
case ("warning", let intValue as Int):
severityConfiguration.warning = intValue
case ($ignoresDefaultParameters, let boolValue as Bool):
ignoresDefaultParameters = boolValue
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
@@ -1,17 +1,17 @@
import SwiftLintCore
@AutoApply
struct ImplicitReturnConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ImplicitReturnRule
enum ReturnKind: String, CaseIterable, AcceptableByConfigurationElement, Comparable {
@MakeAcceptableByConfigurationElement
enum ReturnKind: String, CaseIterable, Comparable {
case closure
case function
case getter
case `subscript`
case initializer
func asOption() -> OptionType { .symbol(rawValue) }
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
@@ -28,26 +28,6 @@ struct ImplicitReturnConfiguration: SeverityBasedRuleConfiguration, Equatable {
self.includedKinds = includedKinds
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let includedKinds = configuration[$includedKinds] as? [String] {
self.includedKinds = try Set(includedKinds.map {
guard let kind = ReturnKind(rawValue: $0) else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
return kind
})
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
func isKindIncluded(_ kind: ReturnKind) -> Bool {
return self.includedKinds.contains(kind)
}
@@ -1,42 +1,18 @@
import SwiftLintCore
@AutoApply
struct ImplicitlyUnwrappedOptionalConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ImplicitlyUnwrappedOptionalRule
// swiftlint:disable:next type_name
enum ImplicitlyUnwrappedOptionalModeConfiguration: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum ImplicitlyUnwrappedOptionalModeConfiguration: String { // swiftlint:disable:this type_name
case all = "all"
case allExceptIBOutlets = "all_except_iboutlets"
case weakExceptIBOutlets = "weak_except_iboutlets"
init(value: Any) throws {
if let string = (value as? String)?.lowercased(),
let value = Self(rawValue: string) {
self = value
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
@ConfigurationElement(key: "mode")
private(set) var mode = ImplicitlyUnwrappedOptionalModeConfiguration.allExceptIBOutlets
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let modeString = configuration[$mode] {
try mode = ImplicitlyUnwrappedOptionalModeConfiguration(value: modeString)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,8 +1,20 @@
import SwiftLintCore
@AutoApply
struct InclusiveLanguageConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = InclusiveLanguageRule
private static let defaultTerms: Set<String> = [
"whitelist",
"blacklist",
"master",
"slave"
]
private static let defaultAllowedTerms: Set<String> = [
"mastercard"
]
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "additional_terms")
@@ -11,46 +23,16 @@ struct InclusiveLanguageConfiguration: SeverityBasedRuleConfiguration, Equatable
private(set) var overrideTerms: Set<String>?
@ConfigurationElement(key: "override_allowed_terms")
private(set) var overrideAllowedTerms: Set<String>?
private(set) var allTerms: [String]
private(set) var allAllowedTerms: Set<String>
private let defaultTerms: Set<String> = [
"whitelist",
"blacklist",
"master",
"slave"
]
private let defaultAllowedTerms: Set<String> = [
"mastercard"
]
init() {
self.allTerms = defaultTerms.sorted()
self.allAllowedTerms = defaultAllowedTerms
var allTerms: [String] {
let allTerms = overrideTerms ?? Self.defaultTerms
return allTerms.union(additionalTerms ?? [])
.map { $0.lowercased() }
.unique
.sorted()
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] {
try severityConfiguration.apply(configuration: severityString)
}
additionalTerms = lowercasedSet(for: $additionalTerms, from: configuration)
overrideTerms = lowercasedSet(for: $overrideTerms, from: configuration)
overrideAllowedTerms = lowercasedSet(for: $overrideAllowedTerms, from: configuration)
var allTerms = overrideTerms ?? defaultTerms
allTerms.formUnion(additionalTerms ?? [])
self.allTerms = allTerms.sorted()
allAllowedTerms = overrideAllowedTerms ?? defaultAllowedTerms
}
private func lowercasedSet(for key: String, from config: [String: Any]) -> Set<String>? {
guard let list = config[key] as? [String] else { return nil }
return Set(list.map { $0.lowercased() })
var allAllowedTerms: Set<String> {
Set((overrideAllowedTerms ?? Self.defaultAllowedTerms).map { $0.lowercased() })
}
}
@@ -1,11 +1,18 @@
import SwiftLintCore
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable let_var_whitespace
@AutoApply
struct IndentationWidthConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = IndentationWidthRule
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
@ConfigurationElement(key: "indentation_width")
@ConfigurationElement(
key: "indentation_width",
postprocessor: { if $0 < 1 { throw Issue.invalidConfiguration(ruleID: Parent.identifier) } }
)
private(set) var indentationWidth = 4
@ConfigurationElement(key: "include_comments")
private(set) var includeComments = true
@@ -13,30 +20,4 @@ struct IndentationWidthConfiguration: SeverityBasedRuleConfiguration, Equatable
private(set) var includeCompilerDirectives = true
@ConfigurationElement(key: "include_multiline_strings")
private(set) var includeMultilineStrings = true
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let config = configurationDict[$severityConfiguration] {
try severityConfiguration.apply(configuration: config)
}
if let indentationWidth = configurationDict[$indentationWidth] as? Int, indentationWidth >= 1 {
self.indentationWidth = indentationWidth
}
if let includeComments = configurationDict[$includeComments] as? Bool {
self.includeComments = includeComments
}
if let includeCompilerDirectives = configurationDict[$includeCompilerDirectives] as? Bool {
self.includeCompilerDirectives = includeCompilerDirectives
}
if let includeMultilineStrings = configurationDict[$includeMultilineStrings] as? Bool {
self.includeMultilineStrings = includeMultilineStrings
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct LineLengthConfiguration: RuleConfiguration, Equatable {
typealias Parent = LineLengthRule
@@ -17,60 +18,4 @@ struct LineLengthConfiguration: RuleConfiguration, Equatable {
var params: [RuleParameter<Int>] {
return length.params
}
mutating func apply(configuration: Any) throws {
if applyArray(configuration: configuration) {
return
}
try applyDictionary(configuration: configuration)
}
/// Applies configuration as an array of integers. Returns true if did apply.
///
/// - parameter configuration: The untyped configuration value to apply.
///
/// - returns: True if the configuration was successfuly applied.
private mutating func applyArray(configuration: Any) -> Bool {
guard let configurationArray = [Int].array(of: configuration),
configurationArray.isNotEmpty else {
return false
}
let warning = configurationArray[0]
let error = (configurationArray.count > 1) ? configurationArray[1] : nil
length = SeverityLevelsConfiguration(warning: warning, error: error)
return true
}
/// Applies configuration as a dictionary. Throws if configuration couldn't be applied.
///
/// - parameter configuration: The untyped configuration value to apply.
///
/// - throws: Throws if the configuration value isn't properly formatted.
private mutating func applyDictionary(configuration: Any) throws {
let error = Issue.unknownConfiguration(ruleID: Parent.identifier)
guard let configDict = configuration as? [String: Any],
configDict.isNotEmpty else {
throw error
}
for (string, value) in configDict {
switch (string, value) {
case ("error", let intValue as Int):
length.error = intValue
case ("warning", let intValue as Int):
length.warning = intValue
case ($ignoresFunctionDeclarations, let boolValue as Bool):
ignoresFunctionDeclarations = boolValue
case ($ignoresComments, let boolValue as Bool):
ignoresComments = boolValue
case ($ignoresURLs, let boolValue as Bool):
ignoresURLs = boolValue
case ($ignoresInterpolatedStrings, let boolValue as Bool):
ignoresInterpolatedStrings = boolValue
default:
throw error
}
}
}
}
@@ -23,9 +23,9 @@ struct MissingDocsConfiguration: RuleConfiguration, Equatable {
severity.rawValue => .list(values.map(\.value.description).sorted().map { .symbol($0) })
}
}
$excludesExtensions => .flag(excludesExtensions)
$excludesInheritedTypes => .flag(excludesInheritedTypes)
$excludesTrivialInit => .flag(excludesTrivialInit)
$excludesExtensions.key => .flag(excludesExtensions)
$excludesInheritedTypes.key => .flag(excludesInheritedTypes)
$excludesTrivialInit.key => .flag(excludesTrivialInit)
}
mutating func apply(configuration: Any) throws {
@@ -33,15 +33,15 @@ struct MissingDocsConfiguration: RuleConfiguration, Equatable {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let shouldExcludeExtensions = dict[$excludesExtensions] as? Bool {
if let shouldExcludeExtensions = dict[$excludesExtensions.key] as? Bool {
excludesExtensions = shouldExcludeExtensions
}
if let shouldExcludeInheritedTypes = dict[$excludesInheritedTypes] as? Bool {
if let shouldExcludeInheritedTypes = dict[$excludesInheritedTypes.key] as? Bool {
excludesInheritedTypes = shouldExcludeInheritedTypes
}
if let excludesTrivialInit = dict[$excludesTrivialInit] as? Bool {
if let excludesTrivialInit = dict[$excludesTrivialInit.key] as? Bool {
self.excludesTrivialInit = excludesTrivialInit
}
@@ -1,6 +1,7 @@
import SourceKittenFramework
import SwiftLintCore
@AutoApply
struct ModifierOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ModifierOrderRule
@@ -20,29 +21,16 @@ struct ModifierOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
.typeMethods,
.owned
]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let preferredModifierOrder = configuration[$preferredModifierOrder] as? [String] {
self.preferredModifierOrder = try preferredModifierOrder.map {
guard let modifierGroup = SwiftDeclarationAttributeKind.ModifierGroup(rawValue: $0),
modifierGroup != .atPrefixed else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
return modifierGroup
}
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
extension SwiftDeclarationAttributeKind.ModifierGroup: AcceptableByConfigurationElement {
public init(fromAny value: Any, context ruleID: String) throws {
if let value = value as? String, let newSelf = Self(rawValue: value), newSelf != .atPrefixed {
self = newSelf
} else {
throw Issue.unknownConfiguration(ruleID: ruleID)
}
}
public func asOption() -> OptionType { .symbol(rawValue) }
}
@@ -1,24 +1,14 @@
import SwiftLintCore
@AutoApply
struct MultilineArgumentsConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = MultilineArgumentsRule
enum FirstArgumentLocation: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum FirstArgumentLocation: String {
case anyLine = "any_line"
case sameLine = "same_line"
case nextLine = "next_line"
init(value: Any) throws {
guard
let string = (value as? String)?.lowercased(),
let value = Self(rawValue: string) else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
self = value
}
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
@@ -27,25 +17,4 @@ struct MultilineArgumentsConfiguration: SeverityBasedRuleConfiguration, Equatabl
private(set) var firstArgumentLocation = FirstArgumentLocation.anyLine
@ConfigurationElement(key: "only_enforce_after_first_closure_on_first_line")
private(set) var onlyEnforceAfterFirstClosureOnFirstLine = false
mutating func apply(configuration: Any) throws {
let error = Issue.unknownConfiguration(ruleID: Parent.identifier)
guard let configuration = configuration as? [String: Any] else {
throw error
}
for (string, value) in configuration {
switch (string, value) {
case ($firstArgumentLocation, _):
try firstArgumentLocation = FirstArgumentLocation(value: value)
case ($severityConfiguration, let stringValue as String):
try severityConfiguration.apply(configuration: stringValue)
case ($onlyEnforceAfterFirstClosureOnFirstLine, let boolValue as Bool):
onlyEnforceAfterFirstClosureOnFirstLine = boolValue
default:
throw error
}
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct MultilineParametersConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = MultilineParametersRule
@@ -7,16 +8,4 @@ struct MultilineParametersConfiguration: SeverityBasedRuleConfiguration, Equatab
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "allows_single_line")
private(set) var allowsSingleLine = true
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
allowsSingleLine = configuration[$allowsSingleLine] as? Bool ?? true
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -11,7 +11,7 @@ struct NameConfiguration<Parent: Rule>: RuleConfiguration, Equatable {
@ConfigurationElement(key: "max_length")
private(set) var maxLength = SeverityLevels(warning: 0, error: 0)
@ConfigurationElement(key: "excluded")
private(set) var excludedRegularExpressions = Set<NSRegularExpression>()
private(set) var excludedRegularExpressions = Set<RegularExpression>()
@ConfigurationElement(key: "allowed_symbols")
private(set) var allowedSymbols = Set<String>()
@ConfigurationElement(key: "unallowed_symbols_severity")
@@ -42,7 +42,7 @@ struct NameConfiguration<Parent: Rule>: RuleConfiguration, Equatable {
minLength = SeverityLevels(warning: minLengthWarning, error: minLengthError)
maxLength = SeverityLevels(warning: maxLengthWarning, error: maxLengthError)
self.excludedRegularExpressions = Set(excluded.compactMap {
try? NSRegularExpression.cached(pattern: "^\($0)$")
try? RegularExpression(pattern: "^\($0)$")
})
self.allowedSymbols = Set(allowedSymbols)
self.unallowedSymbolsSeverity = unallowedSymbolsSeverity
@@ -54,26 +54,26 @@ struct NameConfiguration<Parent: Rule>: RuleConfiguration, Equatable {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let minLengthConfiguration = configurationDict[$minLength] {
if let minLengthConfiguration = configurationDict[$minLength.key] {
try minLength.apply(configuration: minLengthConfiguration)
}
if let maxLengthConfiguration = configurationDict[$maxLength] {
if let maxLengthConfiguration = configurationDict[$maxLength.key] {
try maxLength.apply(configuration: maxLengthConfiguration)
}
if let excluded = [String].array(of: configurationDict[$excludedRegularExpressions]) {
if let excluded = [String].array(of: configurationDict[$excludedRegularExpressions.key]) {
self.excludedRegularExpressions = Set(excluded.compactMap {
try? NSRegularExpression.cached(pattern: "^\($0)$")
try? RegularExpression(pattern: "^\($0)$")
})
}
if let allowedSymbols = [String].array(of: configurationDict[$allowedSymbols]) {
if let allowedSymbols = [String].array(of: configurationDict[$allowedSymbols.key]) {
self.allowedSymbols = Set(allowedSymbols)
}
if let unallowedSymbolsSeverity = configurationDict[$unallowedSymbolsSeverity] {
if let unallowedSymbolsSeverity = configurationDict[$unallowedSymbolsSeverity.key] {
try self.unallowedSymbolsSeverity.apply(configuration: unallowedSymbolsSeverity)
}
if let validatesStartWithLowercase = configurationDict[$validatesStartWithLowercase] as? String {
if let validatesStartWithLowercase = configurationDict[$validatesStartWithLowercase.key] as? String {
try self.validatesStartWithLowercase.apply(configuration: validatesStartWithLowercase)
} else if let validatesStartWithLowercase = configurationDict[$validatesStartWithLowercase] as? Bool {
} else if let validatesStartWithLowercase = configurationDict[$validatesStartWithLowercase.key] as? Bool {
// TODO: [05/10/2025] Remove deprecation warning after ~2 years.
self.validatesStartWithLowercase = validatesStartWithLowercase ? .error : .off
Issue.genericWarning(
@@ -106,7 +106,7 @@ extension NameConfiguration {
extension NameConfiguration {
func shouldExclude(name: String) -> Bool {
excludedRegularExpressions.contains {
!$0.matches(in: name, options: [], range: NSRange(name.startIndex..., in: name)).isEmpty
!$0.regex.matches(in: name, options: [], range: NSRange(name.startIndex..., in: name)).isEmpty
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct NestingConfiguration: RuleConfiguration, Equatable {
typealias Parent = NestingRule
typealias Severity = SeverityLevelsConfiguration<Parent>
@@ -13,23 +14,6 @@ struct NestingConfiguration: RuleConfiguration, Equatable {
@ConfigurationElement(key: "always_allow_one_type_in_functions")
private(set) var alwaysAllowOneTypeInFunctions = false
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let typeLevelConfiguration = configurationDict[$typeLevel] {
try typeLevel.apply(configuration: typeLevelConfiguration)
}
if let functionLevelConfiguration = configurationDict[$functionLevel] {
try functionLevel.apply(configuration: functionLevelConfiguration)
}
checkNestingInClosuresAndStatements =
configurationDict[$checkNestingInClosuresAndStatements] as? Bool ?? true
alwaysAllowOneTypeInFunctions =
configurationDict[$alwaysAllowOneTypeInFunctions] as? Bool ?? false
}
func severity(with config: Severity, for level: Int) -> ViolationSeverity? {
if let error = config.error, level > error {
return .error
@@ -1,34 +1,17 @@
import SwiftLintCore
// swiftlint:disable:next type_name
@AutoApply // swiftlint:disable:next type_name
struct NonOverridableClassDeclarationConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = NonOverridableClassDeclarationRule
enum FinalClassModifier: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum FinalClassModifier: String {
case finalClass = "final class"
case `static` = "static"
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
@ConfigurationElement(key: "final_class_modifier")
private(set) var finalClassModifier = FinalClassModifier.finalClass
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let value = configuration[$finalClassModifier] as? String {
if let modifier = FinalClassModifier(rawValue: value) {
finalClassModifier = modifier
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
}
@@ -1,8 +1,32 @@
import SwiftLintCore
@AutoApply
struct NumberSeparatorConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = NumberSeparatorRule
struct ExcludeRange: AcceptableByConfigurationElement, Equatable {
private let min: Double
private let max: Double
func asOption() -> OptionType {
.symbol("\(min) ..< \(max)")
}
init(fromAny value: Any, context ruleID: String) throws {
guard let values = value as? [String: Any],
let min = values["min"] as? Double,
let max = values["max"] as? Double else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self.min = min
self.max = max
}
func contains(_ value: Double) -> Bool {
min <= value && value < max
}
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "minimum_length")
@@ -10,32 +34,5 @@ struct NumberSeparatorConfiguration: SeverityBasedRuleConfiguration, Equatable {
@ConfigurationElement(key: "minimum_fraction_length")
private(set) var minimumFractionLength: Int?
@ConfigurationElement(key: "exclude_ranges")
private(set) var excludeRanges = [Range<Double>]()
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let minimumLength = configuration[$minimumLength] as? Int {
self.minimumLength = minimumLength
}
if let minimumFractionLength = configuration[$minimumFractionLength] as? Int {
self.minimumFractionLength = minimumFractionLength
}
if let excludeRanges = configuration[$excludeRanges] as? [[String: Any]] {
self.excludeRanges = excludeRanges.compactMap { dict in
guard let min = dict["min"] as? Double, let max = dict["max"] as? Double else {
return nil
}
return min ..< max
}
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
private(set) var excludeRanges = [ExcludeRange]()
}
@@ -2,6 +2,7 @@ import SwiftLintCore
typealias DiscouragedObjectLiteralConfiguration = ObjectLiteralConfiguration<DiscouragedObjectLiteralRule>
@AutoApply
struct ObjectLiteralConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Equatable {
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@@ -9,17 +10,4 @@ struct ObjectLiteralConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
private(set) var imageLiteral = true
@ConfigurationElement(key: "color_literal")
private(set) var colorLiteral = true
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
imageLiteral = configuration[$imageLiteral] as? Bool ?? true
colorLiteral = configuration[$colorLiteral] as? Bool ?? true
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct OpeningBraceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = OpeningBraceRule
@@ -7,16 +8,4 @@ struct OpeningBraceConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "allow_multiline_func")
private(set) var allowMultilineFunc = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
allowMultilineFunc = configuration[$allowMultilineFunc] as? Bool ?? false
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct OperatorUsageWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = OperatorUsageWhitespaceRule
@@ -11,19 +12,4 @@ struct OperatorUsageWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equ
private(set) var skipAlignedConstants = true
@ConfigurationElement(key: "allowed_no_space_operators")
private(set) var allowedNoSpaceOperators = ["...", "..<"]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
linesLookAround = configuration[$linesLookAround] as? Int ?? 2
skipAlignedConstants = configuration[$skipAlignedConstants] as? Bool ?? true
allowedNoSpaceOperators =
configuration[$allowedNoSpaceOperators] as? [String] ?? ["...", "..<"]
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,9 +1,10 @@
import SwiftLintCore
@AutoApply
struct OverriddenSuperCallConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = OverriddenSuperCallRule
private let defaultIncluded = [
private static let defaultIncluded = [
// NSObject
"awakeFromNib()",
"prepareForInterfaceBuilder()",
@@ -42,38 +43,12 @@ struct OverriddenSuperCallConfiguration: SeverityBasedRuleConfiguration, Equatab
@ConfigurationElement(key: "included")
private(set) var included = ["*"]
private(set) var resolvedMethodNames: [String]
init() {
resolvedMethodNames = defaultIncluded
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let excluded = [String].array(of: configuration[$excluded]) {
self.excluded = excluded
}
if let included = [String].array(of: configuration[$included]) {
self.included = included
}
resolvedMethodNames = calculateResolvedMethodNames()
}
private func calculateResolvedMethodNames() -> [String] {
var resolvedMethodNames: [String] {
var names: [String] = []
if included.contains("*") && !excluded.contains("*") {
names += defaultIncluded
names += Self.defaultIncluded
}
names += included.filter({ $0 != "*" })
names += included.filter { $0 != "*" }
names = names.filter { !excluded.contains($0) }
return names
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct PrefixedTopLevelConstantConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = PrefixedTopLevelConstantRule
@@ -7,16 +8,4 @@ struct PrefixedTopLevelConstantConfiguration: SeverityBasedRuleConfiguration, Eq
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "only_private")
private(set) var onlyPrivateMembers = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
onlyPrivateMembers = (configuration[$onlyPrivateMembers] as? Bool == true)
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct PrivateOutletConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = PrivateOutletRule
@@ -7,16 +8,4 @@ struct PrivateOutletConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "allow_private_set")
private(set) var allowPrivateSet = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
allowPrivateSet = (configuration[$allowPrivateSet] as? Bool == true)
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct PrivateOverFilePrivateConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = PrivateOverFilePrivateRule
@@ -7,16 +8,4 @@ struct PrivateOverFilePrivateConfiguration: SeverityBasedRuleConfiguration, Equa
var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "validate_extensions")
var validateExtensions = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
validateExtensions = configuration[$validateExtensions] as? Bool ?? false
}
}
@@ -10,26 +10,26 @@ struct PrivateUnitTestConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
@ConfigurationElement(key: "regex")
private(set) var regex = SwiftLintCore.regex("XCTestCase")
private(set) var regex: RegularExpression = "XCTestCase"
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let extraTestParentClasses = configurationDict[$testParentClasses] as? [String] {
if let extraTestParentClasses = configurationDict[$testParentClasses.key] as? [String] {
self.testParentClasses.formUnion(extraTestParentClasses)
}
if let regexString = configurationDict[$regex] as? String {
if let regexString = configurationDict[$regex.key] as? String {
// TODO: [01/09/2025] Remove deprecation warning after ~2 years and use `UnitTestConfiguration`
// instead of this configuration.
queuedPrintError(
"""
warning: '\($regex)' has been replaced by a list of explicit parent class names. They can be \
configured in the '\($testParentClasses)' option. '\($regex)' will be completely removed \
warning: '\($regex.key)' has been replaced by a list of explicit parent class names. They can be \
configured in the '\($testParentClasses.key)' option. '\($regex.key)' will be completely removed \
in a future release.
"""
)
regex = try .cached(pattern: regexString)
regex = try RegularExpression(pattern: regexString)
}
if configurationDict["included"] is String {
// TODO: [01/09/2025] Remove deprecation warning after ~2 years and replace this configuration by
@@ -52,7 +52,7 @@ struct PrivateUnitTestConfiguration: SeverityBasedRuleConfiguration, Equatable {
"warning: 'message' is ignored from now on. You may remove it from the configuration file."
)
}
if let severityString = configurationDict[$severityConfiguration] as? String {
if let severityString = configurationDict[$severityConfiguration.key] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct ProhibitedSuperConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ProhibitedSuperRule
@@ -10,7 +11,7 @@ struct ProhibitedSuperConfiguration: SeverityBasedRuleConfiguration, Equatable {
@ConfigurationElement(key: "included")
private(set) var included = ["*"]
private(set) var resolvedMethodNames = [
private static let methodNames = [
// NSFileProviderExtension
"providePlaceholder(at:completionHandler:)",
// NSTextInput
@@ -21,30 +22,10 @@ struct ProhibitedSuperConfiguration: SeverityBasedRuleConfiguration, Equatable {
"loadView()"
]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let excluded = [String].array(of: configuration[$excluded]) {
self.excluded = excluded
}
if let included = [String].array(of: configuration[$included]) {
self.included = included
}
resolvedMethodNames = calculateResolvedMethodNames()
}
private func calculateResolvedMethodNames() -> [String] {
var resolvedMethodNames: [String] {
var names = [String]()
if included.contains("*") && !excluded.contains("*") {
names += resolvedMethodNames
names += Self.methodNames
}
names += included.filter { $0 != "*" }
names = names.filter { !excluded.contains($0) }
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct SelfBindingConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = SelfBindingRule
@@ -7,16 +8,4 @@ struct SelfBindingConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "bind_identifier")
private(set) var bindIdentifier = "self"
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
bindIdentifier = configuration[$bindIdentifier] as? String ?? "self"
}
}
@@ -1,38 +1,20 @@
import SwiftLintCore
struct SortedImportsConfiguration: RuleConfiguration, Equatable {
@AutoApply
struct SortedImportsConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = SortedImportsRule
enum SortedImportsGroupingConfiguration: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum SortedImportsGroupingConfiguration: String {
/// Sorts import lines based on any import attributes (e.g. `@testable`, `@_exported`, etc.), followed by a case
/// insensitive comparison of the imported module name.
case attributes
/// Sorts import lines based on a case insensitive comparison of the imported module name.
case names
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
private(set) var severity = SeverityConfiguration<Parent>(.warning)
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "grouping")
private(set) var grouping = SortedImportsGroupingConfiguration.names
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let rawGrouping = configuration[$grouping] {
guard let rawGrouping = rawGrouping as? String,
let grouping = SortedImportsGroupingConfiguration(rawValue: rawGrouping) else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
self.grouping = grouping
}
if let severityString = configuration[$severity] as? String {
try severity.apply(configuration: severityString)
}
}
}
@@ -1,38 +1,17 @@
import SwiftLintCore
@AutoApply
struct StatementPositionConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = StatementPositionRule
enum StatementModeConfiguration: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum StatementModeConfiguration: String {
case `default` = "default"
case uncuddledElse = "uncuddled_else"
init(value: Any) throws {
if let string = (value as? String)?.lowercased(),
let value = Self(rawValue: string) {
self = value
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
@ConfigurationElement(key: "statement_mode")
private(set) var statementMode = StatementModeConfiguration.default
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let statementModeConfiguration = configurationDict[$statementMode] {
try statementMode = StatementModeConfiguration(value: statementModeConfiguration)
}
if let severity = configurationDict[$severityConfiguration] {
try severityConfiguration.apply(configuration: severity)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct SwitchCaseAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = SwitchCaseAlignmentRule
@@ -7,16 +8,4 @@ struct SwitchCaseAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatab
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "indented_cases")
private(set) var indentedCases = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
indentedCases = configuration[$indentedCases] as? Bool ?? false
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,9 @@
import SwiftLintCore
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable let_var_whitespace
@AutoApply
struct TestCaseAccessibilityConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TestCaseAccessibilityRule
@@ -7,24 +11,9 @@ struct TestCaseAccessibilityConfiguration: SeverityBasedRuleConfiguration, Equat
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "allowed_prefixes")
private(set) var allowedPrefixes: Set<String> = []
@ConfigurationElement(key: "test_parent_classes")
private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let allowedPrefixes = configuration[$allowedPrefixes] as? [String] {
self.allowedPrefixes = Set(allowedPrefixes)
}
if let extraTestParentClasses = configuration[$testParentClasses] as? [String] {
self.testParentClasses.formUnion(extraTestParentClasses)
}
}
@ConfigurationElement(
key: "test_parent_classes",
postprocessor: { $0.formUnion(["QuickSpec", "XCTestCase"]) }
)
private(set) var testParentClasses = Set<String>()
}
@@ -1,31 +1,17 @@
import SwiftLintCore
@AutoApply
struct TodoConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TodoRule
enum TodoKeyword: String, CaseIterable, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum TodoKeyword: String, CaseIterable {
case todo = "TODO"
case fixme = "FIXME"
func asOption() -> OptionType { .symbol(rawValue) }
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "only")
private(set) var only = TodoKeyword.allCases
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let onlyStrings = configuration[$only] as? [String] {
self.only = onlyStrings.compactMap { TodoKeyword(rawValue: $0) }
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct TrailingClosureConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TrailingClosureRule
@@ -7,16 +8,4 @@ struct TrailingClosureConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "only_single_muted_parameter")
private(set) var onlySingleMutedParameter = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
onlySingleMutedParameter = (configuration[$onlySingleMutedParameter] as? Bool == true)
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct TrailingCommaConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TrailingCommaRule
@@ -7,16 +8,4 @@ struct TrailingCommaConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "mandatory_comma")
private(set) var mandatoryComma = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
mandatoryComma = (configuration[$mandatoryComma] as? Bool == true)
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct TrailingWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TrailingWhitespaceRule
@@ -9,17 +10,4 @@ struct TrailingWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatabl
private(set) var ignoresEmptyLines = false
@ConfigurationElement(key: "ignores_comments")
private(set) var ignoresComments = true
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
ignoresEmptyLines = (configuration[$ignoresEmptyLines] as? Bool == true)
ignoresComments = (configuration[$ignoresComments] as? Bool == true)
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,6 +1,7 @@
import SwiftLintCore
enum TypeContent: String, AcceptableByConfigurationElement {
@MakeAcceptableByConfigurationElement
enum TypeContent: String {
case `case` = "case"
case typeAlias = "type_alias"
case associatedType = "associated_type"
@@ -16,10 +17,9 @@ enum TypeContent: String, AcceptableByConfigurationElement {
case otherMethod = "other_method"
case `subscript` = "subscript"
case deinitializer = "deinitializer"
func asOption() -> OptionType { .symbol(rawValue) }
}
@AutoApply
struct TypeContentsOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TypeContentsOrderRule
@@ -42,28 +42,4 @@ struct TypeContentsOrderConfiguration: SeverityBasedRuleConfiguration, Equatable
[.subscript],
[.deinitializer]
]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityValue = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityValue)
}
if let custom = configuration[$order] as? [Any] {
order.removeAll()
for entry in custom {
if let singleEntry = entry as? String {
if let typeContent = TypeContent(rawValue: singleEntry) {
order.append([typeContent])
}
} else if let arrayEntry = entry as? [String] {
let typeContents = arrayEntry.compactMap { TypeContent(rawValue: $0) }
order.append(typeContents)
}
}
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct TypeNameConfiguration: RuleConfiguration, Equatable {
typealias Parent = TypeNameRule
@@ -10,15 +11,4 @@ struct TypeNameConfiguration: RuleConfiguration, Equatable {
maxLengthError: 1000)
@ConfigurationElement(key: "validate_protocols")
private(set) var validateProtocols = true
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
try nameConfiguration.apply(configuration: configuration)
if let validateProtocols = configuration["validate_protocols"] as? Bool {
self.validateProtocols = validateProtocols
}
}
}
@@ -5,23 +5,16 @@ typealias EmptyXCTestMethodConfiguration = UnitTestConfiguration<EmptyXCTestMeth
typealias SingleTestClassConfiguration = UnitTestConfiguration<SingleTestClassRule>
typealias NoMagicNumbersConfiguration = UnitTestConfiguration<NoMagicNumbersRule>
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable let_var_whitespace
@AutoApply
struct UnitTestConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Equatable {
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "test_parent_classes")
private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let extraTestParentClasses = configuration[$testParentClasses] as? [String] {
self.testParentClasses.formUnion(extraTestParentClasses)
}
}
@ConfigurationElement(
key: "test_parent_classes",
postprocessor: { $0.formUnion(["QuickSpec", "XCTestCase"]) }
)
private(set) var testParentClasses = Set<String>()
}
@@ -1,5 +1,9 @@
import SwiftLintCore
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable let_var_whitespace
@AutoApply
struct UnusedDeclarationConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = UnusedDeclarationRule
@@ -7,29 +11,9 @@ struct UnusedDeclarationConfiguration: SeverityBasedRuleConfiguration, Equatable
private(set) var severityConfiguration = SeverityConfiguration<Parent>.error
@ConfigurationElement(key: "include_public_and_open")
private(set) var includePublicAndOpen = false
@ConfigurationElement(key: "related_usrs_to_skip")
private(set) var relatedUSRsToSkip = Set(["s:7SwiftUI15PreviewProviderP"])
mutating func apply(configuration: Any) throws {
guard let configDict = configuration as? [String: Any], configDict.isNotEmpty else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
for (string, value) in configDict {
switch (string, value) {
case ($severityConfiguration, let stringValue as String):
try severityConfiguration.apply(configuration: stringValue)
case ($includePublicAndOpen, let boolValue as Bool):
includePublicAndOpen = boolValue
case ($relatedUSRsToSkip, let value):
if let usrs = [String].array(of: value) {
relatedUSRsToSkip.formUnion(usrs)
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
@ConfigurationElement(
key: "related_usrs_to_skip",
postprocessor: { $0.insert("s:7SwiftUI15PreviewProviderP") }
)
private(set) var relatedUSRsToSkip = Set<String>()
}
@@ -8,7 +8,7 @@ struct TransitiveModuleConfiguration<Parent: Rule>: Equatable, AcceptableByConfi
/// The set of modules that can be transitively imported by `importedModule`.
let transitivelyImportedModules: [String]
init(configuration: Any) throws {
init(fromAny configuration: Any, context ruleID: String) throws {
guard let configurationDict = configuration as? [String: Any],
Set(configurationDict.keys) == ["module", "allowed_transitive_imports"],
let importedModule = configurationDict["module"] as? String,
@@ -27,6 +27,7 @@ struct TransitiveModuleConfiguration<Parent: Rule>: Equatable, AcceptableByConfi
}
}
@AutoApply
struct UnusedImportConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = UnusedImportRule
@@ -39,23 +40,4 @@ struct UnusedImportConfiguration: SeverityBasedRuleConfiguration, Equatable {
/// A set of modules to never remove the imports of.
@ConfigurationElement(key: "always_keep_imports")
private(set) var alwaysKeepImports = [String]()
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severity = configurationDict[$severityConfiguration] {
try severityConfiguration.apply(configuration: severity)
}
if let requireExplicitImports = configurationDict[$requireExplicitImports] as? Bool {
self.requireExplicitImports = requireExplicitImports
}
if let allowedTransitiveImports = configurationDict[$allowedTransitiveImports] as? [Any] {
self.allowedTransitiveImports = try allowedTransitiveImports.map(TransitiveModuleConfiguration.init)
}
if let alwaysKeepImports = configurationDict[$alwaysKeepImports] as? [String] {
self.alwaysKeepImports = alwaysKeepImports
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct UnusedOptionalBindingConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = UnusedOptionalBindingRule
@@ -7,18 +8,4 @@ struct UnusedOptionalBindingConfiguration: SeverityBasedRuleConfiguration, Equat
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "ignore_optional_try")
private(set) var ignoreOptionalTry = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let ignoreOptionalTry = configuration[$ignoreOptionalTry] as? Bool {
self.ignoreOptionalTry = ignoreOptionalTry
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,6 +1,6 @@
import SwiftLintCore
// swiftlint:disable:next type_name
@AutoApply // swiftlint:disable:next type_name
struct VerticalWhitespaceClosingBracesConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = VerticalWhitespaceClosingBracesRule
@@ -8,21 +8,4 @@ struct VerticalWhitespaceClosingBracesConfiguration: SeverityBasedRuleConfigurat
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "only_enforce_before_trivial_lines")
private(set) var onlyEnforceBeforeTrivialLines = false
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
for (string, value) in configuration {
switch (string, value) {
case ($severityConfiguration, let stringValue as String):
try severityConfiguration.apply(configuration: stringValue)
case ($onlyEnforceBeforeTrivialLines, let boolValue as Bool):
onlyEnforceBeforeTrivialLines = boolValue
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
}
@@ -1,5 +1,6 @@
import SwiftLintCore
@AutoApply
struct VerticalWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = VerticalWhitespaceRule
@@ -7,18 +8,4 @@ struct VerticalWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatabl
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "max_empty_lines")
private(set) var maxEmptyLines = 1
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let maxEmptyLines = configuration[$maxEmptyLines] as? Int {
self.maxEmptyLines = maxEmptyLines
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
@@ -1,31 +1,17 @@
import SwiftLintCore
@AutoApply
struct XCTSpecificMatcherConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = XCTSpecificMatcherRule
@MakeAcceptableByConfigurationElement
enum Matcher: String, CaseIterable {
case oneArgumentAsserts = "one-argument-asserts"
case twoArgumentAsserts = "two-argument-asserts"
}
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "matchers")
private(set) var matchers = Matcher.allCases
enum Matcher: String, Hashable, CaseIterable, AcceptableByConfigurationElement {
case oneArgumentAsserts = "one-argument-asserts"
case twoArgumentAsserts = "two-argument-asserts"
func asOption() -> OptionType { .symbol(rawValue) }
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let matchers = configuration[$matchers] as? [String] {
self.matchers = matchers.compactMap(Matcher.init)
}
}
}
@@ -1,9 +1,9 @@
import Foundation
import SourceKittenFramework
struct FileTypesOrderRule: ConfigurationProviderRule, OptInRule {
private typealias FileTypeOffset = (fileType: FileType, offset: ByteCount)
private typealias FileTypeOffset = (fileType: FileTypesOrderConfiguration.FileType, offset: ByteCount)
struct FileTypesOrderRule: ConfigurationProviderRule, OptInRule {
var configuration = FileTypesOrderConfiguration()
static let description = RuleDescription(
@@ -173,7 +173,7 @@ private extension SourceKittenDictionary {
}
private extension Array where Element == SourceKittenDictionary {
func offsets(for fileType: FileType) -> [(fileType: FileType, offset: ByteCount)] {
func offsets(for fileType: FileTypesOrderConfiguration.FileType) -> [FileTypeOffset] {
self.compactMap { substructure in
guard let offset = substructure.offset else { return nil }
return (fileType, offset)
@@ -70,7 +70,7 @@ struct SortedImportsRule: CorrectableRule, ConfigurationProviderRule, OptInRule
return violatingOffsets(inGroups: groups).map { index -> StyleViolation in
let location = Location(file: file, characterOffset: index)
return StyleViolation(ruleDescription: Self.description,
severity: configuration.severity.severity,
severity: configuration.severity,
location: location)
}
}
@@ -4,6 +4,26 @@ import SourceKittenFramework
private var regexCache = [RegexCacheKey: NSRegularExpression]()
private let regexCacheLock = NSLock()
public struct RegularExpression: Hashable, Comparable, ExpressibleByStringLiteral {
public let regex: NSRegularExpression
public init(pattern: String, options: NSRegularExpression.Options? = nil) throws {
regex = try .cached(pattern: pattern)
}
public init(stringLiteral value: String) {
// swiftlint:disable:next force_try
try! self.init(pattern: value)
}
var pattern: String { regex.pattern }
var numberOfCaptureGroups: Int { regex.numberOfCaptureGroups }
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.pattern < rhs.pattern
}
}
private struct RegexCacheKey: Hashable {
let pattern: String
let options: NSRegularExpression.Options
+12
View File
@@ -0,0 +1,12 @@
/// Macro to be attached to rule configurations. It generates the configuration parsing logic
/// automatically based on the defined `@ConfigurationElement`s.
@attached(member, names: named(apply))
public macro AutoApply() = #externalMacro(
module: "SwiftLintCoreMacros",
type: "AutoApply")
/// Macro that lets an enum with a ``String`` raw type automatically conform to ``AcceptableByConfigurationElement``.
@attached(extension, conformances: AcceptableByConfigurationElement, names: named(init), named(asOption))
public macro MakeAcceptableByConfigurationElement() = #externalMacro(
module: "SwiftLintCoreMacros",
type: "MakeAcceptableByConfigurationElement")
@@ -71,6 +71,19 @@ public struct RuleConfigurationDescription: Equatable {
}
return Self(options: options)
}
func allowedKeys() -> [String] {
options.flatMap { option -> [String] in
switch option.value {
case let .nested(nestedConfiguration) where option.key.isEmpty:
nestedConfiguration.allowedKeys()
case .empty:
[]
default:
[option.key]
}
}
}
}
extension RuleConfigurationDescription: Documentable {
@@ -280,7 +293,8 @@ public extension OptionType {
/// Create an option defined by nested configuration description.
///
/// - Parameter description: A configuration description buildable by applying the result builder syntax.
/// - Parameters:
/// - description: A configuration description buildable by applying the result builder syntax.
///
/// - Returns: A configuration option with a value being another configuration description.
static func nest(@RuleConfigurationDescriptionBuilder _ description: () -> RuleConfigurationDescription) -> Self {
@@ -297,6 +311,13 @@ private protocol AnyConfigurationElement {
/// Type of an object that can be used as a configuration element.
public protocol AcceptableByConfigurationElement {
/// Initializer taking a value from a configuration to create an element of `Self`.
///
/// - Parameters:
/// - value: Value from a configuration.
/// - ruleID: The rule's identifier in which context the configuration parsing runs.
init(fromAny value: Any, context ruleID: String) throws
/// Make the object an option.
///
/// - Returns: Option representing the object.
@@ -304,17 +325,29 @@ public protocol AcceptableByConfigurationElement {
/// Make the object a description.
///
/// - Parameter key: Name of the option to be put into the description.
/// - Parameters:
/// - key: Name of the option to be put into the description.
///
/// - Returns: Configuration description of this object.
func asDescription(with key: String) -> RuleConfigurationDescription
/// Update the object.
///
/// - Parameter value: New underlying data for the object.
mutating func apply(_ value: Any?, ruleID: String) throws
}
/// Default implementations which are shortcuts applicable for most of the types conforming to the protocol.
public extension AcceptableByConfigurationElement {
func asDescription(with key: String) -> RuleConfigurationDescription {
// By default, this method is just a shortcut applicable for most of the types conforming to the protocol.
RuleConfigurationDescription(options: [key => asOption()])
}
mutating func apply(_ value: Any?, ruleID: String) throws {
if let value {
self = try Self(fromAny: value, context: ruleID)
}
}
}
/// An option type that does not need a key when used in a ``ConfigurationElement``. Its value will be inlined.
@@ -357,23 +390,31 @@ public protocol InlinableOptionType: AcceptableByConfigurationElement {}
/// error: 2
/// ```
@propertyWrapper
public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatable>: Equatable {
public class ConfigurationElement<T: AcceptableByConfigurationElement & Equatable> {
/// Wrapped option value.
public var wrappedValue: T
/// The option's name. This field can only be accessed by the element's name prefixed with a `$`.
public var projectedValue: String { key }
public var projectedValue: ConfigurationElement { self }
private let key: String
/// Name of this configuration entry.
public let key: String
private let postprocessor: (inout T) throws -> Void
/// Default constructor.
///
/// - Parameters:
/// - value: Value to be wrapped.
/// - key: Name of the option.
public init(wrappedValue value: T, key: String) {
/// - postprocessor: Function to be applied to the wrapped value after parsing to validate and modify it.
public init(wrappedValue value: T, key: String, postprocessor: @escaping (inout T) throws -> Void = { _ in }) {
self.wrappedValue = value
self.key = key
self.postprocessor = postprocessor
// Validate and modify the set value immediately. An exception means invalid defaults.
try! performAfterParseOperations() // swiftlint:disable:this force_try
}
/// Constructor for optional values.
@@ -381,7 +422,7 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
/// It allows to skip explicit initialization with `nil` of the property.
///
/// - Parameter value: Value to be wrapped.
public init<Wrapped>(key: String) where T == Wrapped? {
public convenience init<Wrapped>(key: String) where T == Wrapped? {
self.init(wrappedValue: nil, key: key)
}
@@ -390,10 +431,16 @@ public struct ConfigurationElement<T: AcceptableByConfigurationElement & Equatab
/// ``InlinableOptionType``s are allowed to have an empty key. The configuration will be inlined into its
/// parent configuration in this specific case.
///
/// - Parameter value: Value to be wrapped.
public init(wrappedValue value: T) where T: InlinableOptionType {
/// - Parameters:
/// - value: Value to be wrapped.
public convenience init(wrappedValue value: T) where T: InlinableOptionType {
self.init(wrappedValue: value, key: "")
}
/// Run operations to validate and modify the parsed value.
public func performAfterParseOperations() throws {
try postprocessor(&wrappedValue)
}
}
extension ConfigurationElement: AnyConfigurationElement {
@@ -402,6 +449,12 @@ extension ConfigurationElement: AnyConfigurationElement {
}
}
extension ConfigurationElement: Equatable {
public static func == (lhs: ConfigurationElement, rhs: ConfigurationElement) -> Bool {
lhs.wrappedValue == rhs.wrappedValue && lhs.key == rhs.key
}
}
// MARK: AcceptableByConfigurationElement conformances
extension Optional: AcceptableByConfigurationElement where Wrapped: AcceptableByConfigurationElement {
@@ -411,6 +464,10 @@ extension Optional: AcceptableByConfigurationElement where Wrapped: AcceptableBy
}
return .empty
}
public init(fromAny value: Any, context ruleID: String) throws {
self = try Wrapped(fromAny: value, context: ruleID)
}
}
struct Symbol: Equatable, AcceptableByConfigurationElement {
@@ -419,11 +476,12 @@ struct Symbol: Equatable, AcceptableByConfigurationElement {
func asOption() -> OptionType {
.symbol(value)
}
}
extension OptionType: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
self
init(fromAny value: Any, context ruleID: String) throws {
guard let value = value as? String else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self.value = value
}
}
@@ -431,47 +489,85 @@ extension Bool: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.flag(self)
}
public init(fromAny value: Any, context ruleID: String) throws {
guard let value = value as? Self else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self = value
}
}
extension String: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.string(self)
}
public init(fromAny value: Any, context ruleID: String) throws {
guard let value = value as? Self else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self = value
}
}
extension Array: AcceptableByConfigurationElement where Element: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.list(map { $0.asOption() })
}
public init(fromAny value: Any, context ruleID: String) throws {
let values = value as? [Any] ?? [value]
self = try values.map { try Element(fromAny: $0, context: ruleID) }
}
}
extension Set: AcceptableByConfigurationElement where Element: AcceptableByConfigurationElement & Comparable {
public func asOption() -> OptionType {
sorted().asOption()
}
public init(fromAny value: Any, context ruleID: String) throws {
self = Set(try [Element].init(fromAny: value, context: ruleID))
}
}
extension Int: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.integer(self)
}
public init(fromAny value: Any, context ruleID: String) throws {
guard let value = value as? Self else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self = value
}
}
extension Double: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.float(self)
}
}
extension NSRegularExpression: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.string(pattern)
public init(fromAny value: Any, context ruleID: String) throws {
guard let value = value as? Self else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self = value
}
}
extension Range: AcceptableByConfigurationElement {
extension RegularExpression: AcceptableByConfigurationElement {
public func asOption() -> OptionType {
.symbol("\(lowerBound) ..< \(upperBound)")
.string(pattern)
}
public init(fromAny value: Any, context ruleID: String) throws {
guard let value = value as? String else {
throw Issue.invalidConfiguration(ruleID: ruleID)
}
self = try Self(pattern: value)
}
}
@@ -488,6 +584,16 @@ public extension RuleConfiguration {
}
return RuleConfigurationDescription(options: [key => asOption()])
}
mutating func apply(_ value: Any?, ruleID: String) throws {
if let value {
try apply(configuration: value)
}
}
init(fromAny value: Any, context ruleID: String) throws {
throw Issue.genericError("Do not call this initializer")
}
}
public extension SeverityConfiguration {
@@ -22,7 +22,7 @@ public struct SeverityConfiguration<Parent: Rule>: SeverityBasedRuleConfiguratio
public mutating func apply(configuration: Any) throws {
let configString = configuration as? String
let configDict = configuration as? [String: Any]
guard let severityString: String = configString ?? configDict?[$severity] as? String,
guard let severityString: String = configString ?? configDict?[$severity.key] as? String,
let severity = ViolationSeverity(rawValue: severityString.lowercased()) else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
}
@@ -1,4 +1,5 @@
/// The magnitude of a `StyleViolation`.
@MakeAcceptableByConfigurationElement
public enum ViolationSeverity: String, Comparable, Codable, InlinableOptionType {
/// Non-fatal. If using SwiftLint as an Xcode build phase, Xcode will mark the build as having succeeded.
case warning
@@ -10,6 +11,4 @@ public enum ViolationSeverity: String, Comparable, Codable, InlinableOptionType
public static func < (lhs: ViolationSeverity, rhs: ViolationSeverity) -> Bool {
return lhs == .warning && rhs == .error
}
public func asOption() -> OptionType { .symbol(rawValue) }
}
@@ -44,3 +44,10 @@ public extension RuleConfiguration where Self: Equatable {
public extension RuleConfiguration {
var parameterDescription: RuleConfigurationDescription? { nil }
}
public extension RuleConfiguration {
/// All keys supported by this configuration.
var supportedKeys: Set<String> {
Set(RuleConfigurationDescription.from(configuration: self).allowedKeys())
}
}
@@ -11,7 +11,7 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
public var message = "Regex matched"
/// The regular expression to apply to trigger violations for this custom rule.
@ConfigurationElement(key: "regex")
var regex: NSRegularExpression!
var regex: RegularExpression!
/// Regular expressions to include when matching the file path.
public var included: [NSRegularExpression] = []
/// Regular expressions to exclude when matching the file path.
@@ -58,11 +58,11 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
public mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any],
let regexString = configurationDict[$regex] as? String else {
let regexString = configurationDict[$regex.key] as? String else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
}
regex = try .cached(pattern: regexString)
regex = try RegularExpression(pattern: regexString)
if let includedString = configurationDict["included"] as? String {
included = [try .cached(pattern: includedString)]
@@ -86,7 +86,7 @@ public struct RegexConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration,
if let message = configurationDict["message"] as? String {
self.message = message
}
if let severityString = configurationDict[$severityConfiguration] as? String {
if let severityString = configurationDict[$severityConfiguration.key] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let captureGroup = configurationDict["capture_group"] as? Int {
@@ -29,12 +29,27 @@ public struct SeverityLevelsConfiguration<Parent: Rule>: RuleConfiguration, Equa
if let configurationArray = [Int].array(of: configuration), configurationArray.isNotEmpty {
warning = configurationArray[0]
error = (configurationArray.count > 1) ? configurationArray[1] : nil
} else if let configDict = configuration as? [String: Int?],
configDict.isNotEmpty, Set(configDict.keys).isSubset(of: [$warning, $error]) {
warning = (configDict[$warning] as? Int) ?? warning
error = configDict[$error] as? Int
} else if let configDict = configuration as? [String: Any?] {
if let warningValue = configDict[$warning.key] {
if let warning = warningValue as? Int {
self.warning = warning
} else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
}
}
if let errorValue = configDict[$error.key] {
if errorValue == nil {
self.error = nil
} else if let error = errorValue as? Int {
self.error = error
} else {
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
}
} else {
self.error = nil
}
} else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)
}
}
}
@@ -0,0 +1,158 @@
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacros
private let configurationElementName = "ConfigurationElement"
private let acceptableByConfigurationElementName = "AcceptableByConfigurationElement"
private enum RuleConfigurationMacroError: String, DiagnosticMessage {
case notStruct = "Attribute can only be applied to structs"
case notEnum = "Attribute can only be applied to enums"
case noStringRawType = "Attribute can only be applied to enums with a 'String' raw type"
var message: String {
rawValue
}
var diagnosticID: MessageID {
MessageID(domain: "SwiftLint", id: "RuleConfigurationMacro.\(self)")
}
var severity: DiagnosticSeverity {
.error
}
func diagnose(at node: some SyntaxProtocol) -> Diagnostic {
Diagnostic(node: Syntax(node), message: self)
}
}
struct AutoApply: MemberMacro {
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let configuration = declaration.as(StructDeclSyntax.self) else {
context.diagnose(RuleConfigurationMacroError.notStruct.diagnose(at: declaration))
return []
}
var annotatedVarDecls = configuration.memberBlock.members
.compactMap {
if let varDecl = $0.decl.as(VariableDeclSyntax.self),
let annotation = varDecl.configurationElementAnnotation {
return (varDecl, annotation)
}
return nil
}
let firstIndexWithoutKey = annotatedVarDecls
.partition { _, annotation in
if case let .argumentList(arguments) = annotation.arguments {
return arguments.contains { $0.label?.text == "key" } == true
}
return false
}
let elementNames = annotatedVarDecls.compactMap {
$0.0.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
}
let elementsWithoutKeyUpdate = elementNames[..<firstIndexWithoutKey]
.map {
"""
try \($0).apply(configuration, ruleID: Parent.identifier)
"""
}
let elementsWithKeyUpdate = elementNames[firstIndexWithoutKey...]
.map {
"""
try \($0).apply(configuration[$\($0).key], ruleID: Parent.identifier)
try $\($0).performAfterParseOperations()
"""
}
let configBinding = elementsWithKeyUpdate.isEmpty ? "_" : "configuration"
return [
"""
mutating func apply(configuration: Any) throws {
\(raw: elementsWithoutKeyUpdate.joined(separator: "\n"))
guard let \(raw: configBinding) = configuration as? [String: Any] else {
\(raw: elementsWithoutKeyUpdate.isEmpty
? "throw Issue.invalidConfiguration(ruleID: Parent.description.identifier)"
: "return")
}
\(raw: elementsWithKeyUpdate.joined(separator: "\n"))
if !supportedKeys.isSuperset(of: configuration.keys) {
let unknownKeys = Set(configuration.keys).subtracting(supportedKeys)
throw Issue.invalidConfigurationKeys(unknownKeys.sorted())
}
}
"""
]
}
}
struct MakeAcceptableByConfigurationElement: ExtensionMacro {
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
guard let enumDecl = declaration.as(EnumDeclSyntax.self) else {
context.diagnose(RuleConfigurationMacroError.notEnum.diagnose(at: declaration))
return []
}
guard enumDecl.hasStringRawType else {
context.diagnose(RuleConfigurationMacroError.noStringRawType.diagnose(at: declaration))
return []
}
let accessLevel = enumDecl.accessLevel
return [
try ExtensionDeclSyntax("""
extension \(type): \(raw: acceptableByConfigurationElementName) {
\(raw: accessLevel)func asOption() -> OptionType { .symbol(rawValue) }
\(raw: accessLevel)init(fromAny value: Any, context ruleID: String) throws {
if let value = value as? String, let newSelf = Self(rawValue: value) {
self = newSelf
} else {
throw Issue.unknownConfiguration(ruleID: ruleID)
}
}
}
""")
]
}
}
private extension VariableDeclSyntax {
var configurationElementAnnotation: AttributeSyntax? {
let attribute = attributes.first {
if let attr = $0.as(AttributeSyntax.self), let attrId = attr.attributeName.as(IdentifierTypeSyntax.self) {
return attrId.name.text == configurationElementName
}
return false
}
return if case let .attribute(unwrapped) = attribute { unwrapped } else { nil }
}
}
private extension EnumDeclSyntax {
var hasStringRawType: Bool {
if let inheritanceClause {
return inheritanceClause.inheritedTypes.contains {
$0.type.as(IdentifierTypeSyntax.self)?.name.text == "String"
}
}
return false
}
var accessLevel: String {
modifiers.compactMap {
switch $0.name.tokenKind {
case .keyword(.public): "public "
case .keyword(.package): "package "
case .keyword(.private): "private "
default: nil
}
}.first ?? ""
}
}
@@ -0,0 +1,10 @@
import SwiftCompilerPlugin
import SwiftSyntaxMacros
@main
struct SwiftLintCoreMacros: CompilerPlugin {
let providingMacros: [Macro.Type] = [
AutoApply.self,
MakeAcceptableByConfigurationElement.self
]
}
@@ -17,7 +17,7 @@ class CustomRulesTests: SwiftLintTestCase {
var comp = Configuration(identifier: "my_custom_rule")
comp.name = "MyCustomRule"
comp.message = "Message"
comp.regex = regex("regex")
comp.regex = "regex"
comp.severityConfiguration = SeverityConfiguration(.error)
comp.excludedMatchKinds = SyntaxKind.allKinds.subtracting([.comment])
var compRules = CustomRulesConfiguration()
@@ -44,7 +44,7 @@ class CustomRulesTests: SwiftLintTestCase {
var comp = Configuration(identifier: "my_custom_rule")
comp.name = "MyCustomRule"
comp.message = "Message"
comp.regex = regex("regex")
comp.regex = "regex"
comp.severityConfiguration = SeverityConfiguration(.error)
comp.excludedMatchKinds = Set<SyntaxKind>([.comment])
var compRules = CustomRulesConfiguration()
@@ -43,6 +43,7 @@ class CyclomaticComplexityConfigurationTests: SwiftLintTestCase {
let error2 = 40
let length2 = SeverityLevelsConfiguration<CyclomaticComplexityRule>(warning: warning2, error: error2)
let config2: [String: Int] = ["warning": warning2, "error": error2]
let length3 = SeverityLevelsConfiguration<CyclomaticComplexityRule>(warning: warning2)
let config3: [String: Bool] = ["ignores_case_statements": false]
try configuration.apply(configuration: config1)
@@ -54,22 +55,21 @@ class CyclomaticComplexityConfigurationTests: SwiftLintTestCase {
XCTAssertTrue(configuration.ignoresCaseStatements)
try configuration.apply(configuration: config3)
XCTAssertEqual(configuration.length, length2)
XCTAssertEqual(configuration.length, length3)
XCTAssertFalse(configuration.ignoresCaseStatements)
}
func testCyclomaticComplexityConfigurationThrowsOnBadConfigValues() {
let badConfigs: [[String: Any]] = [
["warning": true],
["ignores_case_statements": 300],
["unsupported_key": "unsupported key is unsupported"]
["ignores_case_statements": 300]
]
for badConfig in badConfigs {
var configuration = CyclomaticComplexityConfiguration(
length: SeverityLevelsConfiguration<CyclomaticComplexityRule>(warning: 100, error: 150)
)
checkError(Issue.unknownConfiguration(ruleID: CyclomaticComplexityRule.description.identifier)) {
checkError(Issue.invalidConfiguration(ruleID: CyclomaticComplexityRule.description.identifier)) {
try configuration.apply(configuration: badConfig)
}
}
@@ -21,14 +21,14 @@ class ExplicitTypeInterfaceConfigurationTests: SwiftLintTestCase {
func testInvalidKeyInCustomConfiguration() {
var config = ExplicitTypeInterfaceConfiguration()
checkError(Issue.unknownConfiguration(ruleID: ExplicitTypeInterfaceRule.description.identifier)) {
checkError(Issue.invalidConfigurationKeys(["invalidKey"])) {
try config.apply(configuration: ["invalidKey": "error"])
}
}
func testInvalidTypeOfCustomConfiguration() {
var config = ExplicitTypeInterfaceConfiguration()
checkError(Issue.unknownConfiguration(ruleID: ExplicitTypeInterfaceRule.description.identifier)) {
checkError(Issue.invalidConfiguration(ruleID: ExplicitTypeInterfaceRule.description.identifier)) {
try config.apply(configuration: "invalidKey")
}
}
@@ -1,8 +1,17 @@
@testable import SwiftLintBuiltInRules
import SwiftLintTestHelpers
import XCTest
class IndentationWidthRuleTests: SwiftLintTestCase {
// MARK: Examples
func testInvalidIndentation() {
var testee = IndentationWidthConfiguration()
for indentation in [0, -1, -5] {
checkError(Issue.invalidConfiguration(ruleID: IndentationWidthRule.description.identifier)) {
try testee.apply(configuration: ["indentation_width": indentation])
}
}
}
/// It's not okay to have the first line indented.
func testFirstLineIndentation() {
assert1Violation(in: " firstLine")
@@ -62,7 +62,7 @@ class LineLengthConfigurationTests: SwiftLintTestCase {
func testLineLengthConfigurationThrowsOnBadConfig() {
let config = "unknown"
var configuration = LineLengthConfiguration(length: severityLevels)
checkError(Issue.unknownConfiguration(ruleID: LineLengthRule.description.identifier)) {
checkError(Issue.invalidConfiguration(ruleID: LineLengthRule.description.identifier)) {
try configuration.apply(configuration: config)
}
}
@@ -70,13 +70,12 @@ class LineLengthConfigurationTests: SwiftLintTestCase {
func testLineLengthConfigurationThrowsOnBadConfigValues() {
let badConfigs: [[String: Any]] = [
["warning": true],
["ignores_function_declarations": 300],
["unsupported_key": "unsupported key is unsupported"]
["ignores_function_declarations": 300]
]
for badConfig in badConfigs {
var configuration = LineLengthConfiguration(length: severityLevels)
checkError(Issue.unknownConfiguration(ruleID: LineLengthRule.description.identifier)) {
checkError(Issue.invalidConfiguration(ruleID: LineLengthRule.description.identifier)) {
try configuration.apply(configuration: badConfig)
}
}
@@ -122,7 +121,7 @@ class LineLengthConfigurationTests: SwiftLintTestCase {
let length2 = SeverityLevelsConfiguration<LineLengthRule>(warning: warning2, error: error2)
let config2: [String: Int] = ["warning": warning2, "error": error2]
let length3 = SeverityLevelsConfiguration<LineLengthRule>(warning: warning2, error: error2)
let length3 = SeverityLevelsConfiguration<LineLengthRule>(warning: warning2)
let config3: [String: Bool] = ["ignores_urls": false,
"ignores_function_declarations": false,
"ignores_comments": false]
@@ -1,10 +1,14 @@
@testable import SwiftLintCore
import SwiftLintTestHelpers
import XCTest
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable let_var_whitespace
// swiftlint:disable file_length
// swiftlint:disable:next type_body_length
class RuleConfigurationDescriptionTests: XCTestCase {
@AutoApply
private struct TestConfiguration: RuleConfiguration {
typealias Parent = RuleMock // swiftlint:disable:this nesting
@@ -13,17 +17,22 @@ class RuleConfigurationDescriptionTests: XCTestCase {
@ConfigurationElement(key: "string")
var string = "value"
@ConfigurationElement(key: "symbol")
var symbol = Symbol(value: "value")
var symbol = try! Symbol(fromAny: "value", context: "rule") // swiftlint:disable:this force_try
@ConfigurationElement(key: "integer")
var integer = 2
@ConfigurationElement(key: "nil")
var `nil`: Int?
@ConfigurationElement(key: "null")
var null: Int?
@ConfigurationElement(key: "double")
var double = 2.1
@ConfigurationElement(key: "severity")
var severity = ViolationSeverity.warning
@ConfigurationElement(key: "list")
var list: [OptionType?] = [.flag(true), .string("value")]
@ConfigurationElement(
key: "list",
postprocessor: { list in list = list.map { $0.uppercased() } }
)
var list = ["string1", "string2"]
@ConfigurationElement(key: "set")
var set: Set<Int> = [1, 2, 3]
@ConfigurationElement
var severityConfig = SeverityConfiguration<Parent>(.error)
@ConfigurationElement(key: "SEVERITY")
@@ -33,8 +42,6 @@ class RuleConfigurationDescriptionTests: XCTestCase {
@ConfigurationElement(key: "levels")
var nestedSeverityLevels = SeverityLevelsConfiguration<Parent>(warning: 3, error: nil)
mutating func apply(configuration: Any) throws {}
func isEqualTo(_ ruleConfiguration: some RuleConfiguration) -> Bool { false }
}
@@ -49,7 +56,8 @@ class RuleConfigurationDescriptionTests: XCTestCase {
integer: 2; \
double: 2.1; \
severity: warning; \
list: [true, "value"]; \
list: ["STRING1", "STRING2"]; \
set: [1, 2, 3]; \
severity: error; \
SEVERITY: warning; \
warning: 1; \
@@ -116,7 +124,15 @@ class RuleConfigurationDescriptionTests: XCTestCase {
list
</td>
<td>
[true, &quot;value&quot;]
[&quot;STRING1&quot;, &quot;STRING2&quot;]
</td>
</tr>
<tr>
<td>
set
</td>
<td>
[1, 2, 3]
</td>
</tr>
<tr>
@@ -184,7 +200,8 @@ class RuleConfigurationDescriptionTests: XCTestCase {
integer: 2
double: 2.1
severity: warning
list: [true, "value"]
list: ["STRING1", "STRING2"]
set: [1, 2, 3]
severity: error
SEVERITY: warning
warning: 1
@@ -441,6 +458,52 @@ class RuleConfigurationDescriptionTests: XCTestCase {
""")
}
func testUpdate() throws {
var configuration = TestConfiguration()
try configuration.apply(configuration: [
"flag": false,
"string": "new value",
"symbol": "new symbol",
"integer": 5,
"null": 0,
"double": 5.1,
"severity": "error",
"list": ["string3", "string4"],
"set": [4, 5, 6],
"SEVERITY": "error",
"warning": 12,
"levels": ["warning": 6, "error": 7]
])
XCTAssertFalse(configuration.flag)
XCTAssertEqual(configuration.string, "new value")
XCTAssertEqual(configuration.symbol, try Symbol(fromAny: "new symbol", context: "rule"))
XCTAssertEqual(configuration.integer, 5)
XCTAssertEqual(configuration.null, 0)
XCTAssertEqual(configuration.double, 5.1)
XCTAssertEqual(configuration.severity, .error)
XCTAssertEqual(configuration.list, ["STRING3", "STRING4"])
XCTAssertEqual(configuration.set, [4, 5, 6])
XCTAssertEqual(configuration.severityConfig, .error)
XCTAssertEqual(configuration.renamedSeverityConfig, .error)
XCTAssertEqual(configuration.inlinedSeverityLevels, SeverityLevelsConfiguration(warning: 12))
XCTAssertEqual(configuration.nestedSeverityLevels, SeverityLevelsConfiguration(warning: 6, error: 7))
}
func testInvalidKeys() throws {
var configuration = TestConfiguration()
checkError(Issue.invalidConfigurationKeys(["unknown", "unsupported"])) {
try configuration.apply(configuration: [
"severity": "error",
"warning": 3,
"unknown": 1,
"unsupported": true
])
}
}
private func description(@RuleConfigurationDescriptionBuilder _ content: () -> RuleConfigurationDescription)
-> RuleConfigurationDescription { content() }
}
@@ -37,7 +37,7 @@ class RuleConfigurationTests: SwiftLintTestCase {
func testNestingConfigurationThrowsOnBadConfig() {
let config = 17
var nestingConfig = defaultNestingConfiguration
checkError(Issue.unknownConfiguration(ruleID: NestingRule.description.identifier)) {
checkError(Issue.invalidConfiguration(ruleID: NestingRule.description.identifier)) {
try nestingConfig.apply(configuration: config)
}
}
@@ -120,7 +120,7 @@ class RuleConfigurationTests: SwiftLintTestCase {
let config = "unknown"
var configuration = TrailingWhitespaceConfiguration(ignoresEmptyLines: false,
ignoresComments: true)
checkError(Issue.unknownConfiguration(ruleID: TrailingWhitespaceRule.description.identifier)) {
checkError(Issue.invalidConfiguration(ruleID: TrailingWhitespaceRule.description.identifier)) {
try configuration.apply(configuration: config)
}
}
@@ -78,7 +78,7 @@ class RuleTests: SwiftLintTestCase {
func testSeverityLevelRuleInitsWithConfigDictionary() {
let config = ["warning": 17, "error": 7]
let rule = try? RuleWithLevelsMock(configuration: config)
var comp = RuleWithLevelsMock()
let comp = RuleWithLevelsMock()
comp.configuration.warning = 17
comp.configuration.error = 7
XCTAssertEqual(rule?.isEqualTo(comp), true)
@@ -87,7 +87,7 @@ class RuleTests: SwiftLintTestCase {
func testSeverityLevelRuleInitsWithWarningOnlyConfigDictionary() {
let config = ["warning": 17]
let rule = try? RuleWithLevelsMock(configuration: config)
var comp = RuleWithLevelsMock()
let comp = RuleWithLevelsMock()
comp.configuration.warning = 17
comp.configuration.error = nil
XCTAssertEqual(rule?.isEqualTo(comp), true)
@@ -96,7 +96,7 @@ class RuleTests: SwiftLintTestCase {
func testSeverityLevelRuleInitsWithErrorOnlyConfigDictionary() {
let config = ["error": 17]
let rule = try? RuleWithLevelsMock(configuration: config)
var comp = RuleWithLevelsMock()
let comp = RuleWithLevelsMock()
comp.configuration.error = 17
XCTAssertEqual(rule?.isEqualTo(comp), true)
}
@@ -104,7 +104,7 @@ class RuleTests: SwiftLintTestCase {
func testSeverityLevelRuleInitsWithConfigArray() {
let config = [17, 7] as Any
let rule = try? RuleWithLevelsMock(configuration: config)
var comp = RuleWithLevelsMock()
let comp = RuleWithLevelsMock()
comp.configuration.warning = 17
comp.configuration.error = 7
XCTAssertEqual(rule?.isEqualTo(comp), true)
@@ -113,7 +113,7 @@ class RuleTests: SwiftLintTestCase {
func testSeverityLevelRuleInitsWithSingleValueConfigArray() {
let config = [17] as Any
let rule = try? RuleWithLevelsMock(configuration: config)
var comp = RuleWithLevelsMock()
let comp = RuleWithLevelsMock()
comp.configuration.warning = 17
comp.configuration.error = nil
XCTAssertEqual(rule?.isEqualTo(comp), true)
@@ -122,7 +122,7 @@ class RuleTests: SwiftLintTestCase {
func testSeverityLevelRuleInitsWithLiteral() {
let config = 17 as Any
let rule = try? RuleWithLevelsMock(configuration: config)
var comp = RuleWithLevelsMock()
let comp = RuleWithLevelsMock()
comp.configuration.warning = 17
comp.configuration.error = nil
XCTAssertEqual(rule?.isEqualTo(comp), true)
@@ -3,21 +3,17 @@ import XCTest
class UnusedDeclarationConfigurationTests: XCTestCase {
func testParseConfiguration() throws {
var testee = UnusedDeclarationConfiguration(
severityConfiguration: .warning,
includePublicAndOpen: false,
relatedUSRsToSkip: []
)
var testee = UnusedDeclarationConfiguration()
let config = [
"severity": "error",
"severity": "warning",
"include_public_and_open": true,
"related_usrs_to_skip": ["a", "b"]
] as [String: Any]
try testee.apply(configuration: config)
XCTAssertEqual(testee.severityConfiguration.severity, .error)
XCTAssertEqual(testee.severityConfiguration.severity, .warning)
XCTAssertTrue(testee.includePublicAndOpen)
XCTAssertEqual(testee.relatedUSRsToSkip, ["a", "b"])
XCTAssertEqual(testee.relatedUSRsToSkip, ["a", "b", "s:7SwiftUI15PreviewProviderP"])
}
}