mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
Add configuration for implicitly_unwrapped_optional_rule, add triggering on complex types(AnyCollection<Int!> for ex.)
This commit is contained in:
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
* Add `implicitly_unwrapped_optional` rule
|
||||
that warns when using implicitly unwrapped optional,
|
||||
except cases when this IUO is IBOutlet.
|
||||
except cases when this IUO is IBOutlet.
|
||||
[Siarhei Fedartsou](https://github.com/SiarheiFedartsou/)
|
||||
|
||||
* Performance improvements to `generic_type_name`,
|
||||
|
||||
@@ -71,7 +71,7 @@ public struct CustomRules: Rule, ConfigurationProviderRule {
|
||||
}
|
||||
|
||||
return configurations.flatMap { configuration -> [StyleViolation] in
|
||||
let pattern = configuration.regex?.pattern ?? ""
|
||||
let pattern = configuration.regex.pattern
|
||||
let excludingKinds = Array(Set(SyntaxKind.allKinds()).subtracting(configuration.matchKinds))
|
||||
return file.match(pattern: pattern, excludingSyntaxKinds: excludingKinds).map({
|
||||
StyleViolation(ruleDescription: configuration.description,
|
||||
|
||||
@@ -10,7 +10,8 @@ import Foundation
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct ImplicitlyUnwrappedOptionalRule: ASTRule, ConfigurationProviderRule, OptInRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
public var configuration = ImplicitlyUnwrappedOptionalConfiguration(mode: .allExceptIBOutlets,
|
||||
severity: SeverityConfiguration(.warning))
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -21,6 +22,7 @@ public struct ImplicitlyUnwrappedOptionalRule: ASTRule, ConfigurationProviderRul
|
||||
nonTriggeringExamples: [
|
||||
"@IBOutlet private var label: UILabel!",
|
||||
"@IBOutlet var label: UILabel!",
|
||||
"@IBOutlet var label: [UILabel!]",
|
||||
"if !boolean {}",
|
||||
"let int: Int? = 42",
|
||||
"let int: Int? = nil"
|
||||
@@ -28,13 +30,22 @@ public struct ImplicitlyUnwrappedOptionalRule: ASTRule, ConfigurationProviderRul
|
||||
triggeringExamples: [
|
||||
"let label: UILabel!",
|
||||
"let IBOutlet: UILabel!",
|
||||
"let labels: [UILabel!]",
|
||||
"var ints: [Int!] = [42, nil, 42]",
|
||||
"let label: IBOutlet!",
|
||||
"let int: Int! = 42",
|
||||
"let int: Int! = nil",
|
||||
"var int: Int! = 42"
|
||||
"var int: Int! = 42",
|
||||
"let int: ImplicitlyUnwrappedOptional<Int>",
|
||||
"let collection: AnyCollection<Int!>",
|
||||
"func foo(int: Int!) {}"
|
||||
]
|
||||
)
|
||||
|
||||
private func hasImplicitlyUnwrappedOptional(_ typeName: String) -> Bool {
|
||||
return typeName.range(of: "!") != nil || typeName.range(of: "ImplicitlyUnwrappedOptional<") != nil
|
||||
}
|
||||
|
||||
public func validate(file: File, kind: SwiftDeclarationKind,
|
||||
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
|
||||
guard SwiftDeclarationKind.variableKinds().contains(kind) else {
|
||||
@@ -42,10 +53,12 @@ public struct ImplicitlyUnwrappedOptionalRule: ASTRule, ConfigurationProviderRul
|
||||
}
|
||||
|
||||
guard let typeName = dictionary.typeName else { return [] }
|
||||
guard typeName.hasSuffix("!") else { return [] }
|
||||
guard hasImplicitlyUnwrappedOptional(typeName) else { return [] }
|
||||
|
||||
let isOutlet = dictionary.enclosedSwiftAttributes.contains("source.decl.attribute.iboutlet")
|
||||
if isOutlet { return [] }
|
||||
if configuration.mode == .allExceptIBOutlets {
|
||||
let isOutlet = dictionary.enclosedSwiftAttributes.contains("source.decl.attribute.iboutlet")
|
||||
if isOutlet { return [] }
|
||||
}
|
||||
|
||||
let location: Location
|
||||
if let offset = dictionary.offset {
|
||||
@@ -56,7 +69,7 @@ public struct ImplicitlyUnwrappedOptionalRule: ASTRule, ConfigurationProviderRul
|
||||
|
||||
return [
|
||||
StyleViolation(ruleDescription: type(of: self).description,
|
||||
severity: configuration.severity,
|
||||
severity: configuration.severity.severity,
|
||||
location: location)
|
||||
]
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// ImplicitlyUnwrappedOptionalConfiguration.swift
|
||||
// SwiftLint
|
||||
//
|
||||
// Created by Siarhei Fedartsou on 18/03/17.
|
||||
// Copyright © 2017 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
public enum ImplicitlyUnwrappedOptionalModeConfiguration: String {
|
||||
case all = "all"
|
||||
case allExceptIBOutlets = "all_except_iboutlets"
|
||||
|
||||
init(value: Any) throws {
|
||||
if let string = (value as? String)?.lowercased(),
|
||||
let value = ImplicitlyUnwrappedOptionalModeConfiguration(rawValue: string) {
|
||||
self = value
|
||||
} else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ImplicitlyUnwrappedOptionalConfiguration: RuleConfiguration, Equatable {
|
||||
private(set) var severity: SeverityConfiguration
|
||||
private(set) var mode: ImplicitlyUnwrappedOptionalModeConfiguration
|
||||
|
||||
init(mode: ImplicitlyUnwrappedOptionalModeConfiguration, severity: SeverityConfiguration) {
|
||||
self.mode = mode
|
||||
self.severity = severity
|
||||
}
|
||||
|
||||
public var consoleDescription: String {
|
||||
return severity.consoleDescription +
|
||||
", mode: \(mode)"
|
||||
}
|
||||
|
||||
public mutating func apply(configuration: Any) throws {
|
||||
guard let configuration = configuration as? [String: Any] else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
|
||||
if let modeString = configuration["mode"] {
|
||||
try mode = ImplicitlyUnwrappedOptionalModeConfiguration(value: modeString)
|
||||
}
|
||||
|
||||
if let severityString = configuration["severity"] as? String {
|
||||
try severity.apply(configuration: severityString)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: ImplicitlyUnwrappedOptionalConfiguration,
|
||||
rhs: ImplicitlyUnwrappedOptionalConfiguration) -> Bool {
|
||||
return lhs.severity == rhs.severity &&
|
||||
lhs.mode == rhs.mode
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -13,7 +13,7 @@ public struct PrivateUnitTestConfiguration: RuleConfiguration, Equatable {
|
||||
public let identifier: String
|
||||
public var name: String?
|
||||
public var message = "Regex matched."
|
||||
public var regex: NSRegularExpression?
|
||||
public var regex: NSRegularExpression!
|
||||
public var included: NSRegularExpression?
|
||||
public var severityConfiguration = SeverityConfiguration(.warning)
|
||||
|
||||
@@ -22,8 +22,7 @@ public struct PrivateUnitTestConfiguration: RuleConfiguration, Equatable {
|
||||
}
|
||||
|
||||
public var consoleDescription: String {
|
||||
let regexPattern = regex?.pattern ?? ""
|
||||
return "\(severity.rawValue): \(regexPattern)"
|
||||
return "\(severity.rawValue): \(regex.pattern)"
|
||||
}
|
||||
|
||||
public var description: RuleDescription {
|
||||
|
||||
@@ -13,7 +13,7 @@ public struct RegexConfiguration: RuleConfiguration, Equatable {
|
||||
public let identifier: String
|
||||
public var name: String?
|
||||
public var message = "Regex matched."
|
||||
public var regex: NSRegularExpression?
|
||||
public var regex: NSRegularExpression!
|
||||
public var included: NSRegularExpression?
|
||||
public var matchKinds = Set(SyntaxKind.allKinds())
|
||||
public var severityConfiguration = SeverityConfiguration(.warning)
|
||||
@@ -23,8 +23,7 @@ public struct RegexConfiguration: RuleConfiguration, Equatable {
|
||||
}
|
||||
|
||||
public var consoleDescription: String {
|
||||
let regexPattern = regex?.pattern ?? ""
|
||||
return "\(severity.rawValue): \(regexPattern)"
|
||||
return "\(severity.rawValue): \(regex.pattern)"
|
||||
}
|
||||
|
||||
public var description: RuleDescription {
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
3BCC04D41C502BAB006073C3 /* RuleConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCC04D31C502BAB006073C3 /* RuleConfigurationTests.swift */; };
|
||||
3BD9CD3D1C37175B009A5D25 /* YamlParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD9CD3C1C37175B009A5D25 /* YamlParser.swift */; };
|
||||
3BDB224B1C345B4900473680 /* ProjectMock in Resources */ = {isa = PBXBuildFile; fileRef = 3BDB224A1C345B4900473680 /* ProjectMock */; };
|
||||
47ACC8981E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ACC8971E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift */; };
|
||||
47ACC89A1E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ACC8991E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift */; };
|
||||
47ACC89C1E7DCFA00088EEB2 /* ImplicitlyUnwrappedOptionalRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47ACC89B1E7DCFA00088EEB2 /* ImplicitlyUnwrappedOptionalRuleTests.swift */; };
|
||||
47FF3BE11E7C75B600187E6D /* ImplicitlyUnwrappedOptionalRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FF3BDF1E7C745100187E6D /* ImplicitlyUnwrappedOptionalRule.swift */; };
|
||||
4A9A3A3A1DC1D75F00DF5183 /* HTMLReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A9A3A391DC1D75F00DF5183 /* HTMLReporter.swift */; };
|
||||
4DB7815E1CAD72BA00BC4723 /* LegacyCGGeometryFunctionsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB7815C1CAD690100BC4723 /* LegacyCGGeometryFunctionsRule.swift */; };
|
||||
@@ -312,6 +315,9 @@
|
||||
3BCC04D31C502BAB006073C3 /* RuleConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleConfigurationTests.swift; sourceTree = "<group>"; };
|
||||
3BD9CD3C1C37175B009A5D25 /* YamlParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YamlParser.swift; sourceTree = "<group>"; };
|
||||
3BDB224A1C345B4900473680 /* ProjectMock */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ProjectMock; sourceTree = "<group>"; };
|
||||
47ACC8971E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitlyUnwrappedOptionalConfiguration.swift; sourceTree = "<group>"; };
|
||||
47ACC8991E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitlyUnwrappedOptionalConfigurationTests.swift; sourceTree = "<group>"; };
|
||||
47ACC89B1E7DCFA00088EEB2 /* ImplicitlyUnwrappedOptionalRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitlyUnwrappedOptionalRuleTests.swift; sourceTree = "<group>"; };
|
||||
47FF3BDF1E7C745100187E6D /* ImplicitlyUnwrappedOptionalRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitlyUnwrappedOptionalRule.swift; sourceTree = "<group>"; };
|
||||
4A9A3A391DC1D75F00DF5183 /* HTMLReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLReporter.swift; sourceTree = "<group>"; };
|
||||
4DB7815C1CAD690100BC4723 /* LegacyCGGeometryFunctionsRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyCGGeometryFunctionsRule.swift; sourceTree = "<group>"; };
|
||||
@@ -559,6 +565,7 @@
|
||||
D43B04671E07228D004016AF /* ColonConfiguration.swift */,
|
||||
67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */,
|
||||
D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */,
|
||||
47ACC8971E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift */,
|
||||
3B034B6C1E0BE544005D49A9 /* LineLengthConfiguration.swift */,
|
||||
3BCC04D01C4F56D3006073C3 /* NameConfiguration.swift */,
|
||||
D93DA3CF1E699E4E00809827 /* NestingConfiguration.swift */,
|
||||
@@ -761,6 +768,8 @@
|
||||
006204DD1E1E4E0A00FFFBE1 /* VerticalWhitespaceRuleTests.swift */,
|
||||
67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */,
|
||||
67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */,
|
||||
47ACC8991E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift */,
|
||||
47ACC89B1E7DCFA00088EEB2 /* ImplicitlyUnwrappedOptionalRuleTests.swift */,
|
||||
);
|
||||
name = SwiftLintFrameworkTests;
|
||||
path = Tests/SwiftLintFrameworkTests;
|
||||
@@ -1243,6 +1252,7 @@
|
||||
D43B04691E072291004016AF /* ColonConfiguration.swift in Sources */,
|
||||
D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */,
|
||||
24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */,
|
||||
47ACC8981E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift in Sources */,
|
||||
009E09281DFEE4C200B588A7 /* ProhibitedSuperRule.swift in Sources */,
|
||||
E80E018F1B92C1350078EB70 /* Region.swift in Sources */,
|
||||
E88198581BEA956C00333A11 /* FunctionBodyLengthRule.swift in Sources */,
|
||||
@@ -1333,6 +1343,7 @@
|
||||
E832F10D1B17E725003F265F /* IntegrationTests.swift in Sources */,
|
||||
D4C27C001E12DFF500DF713E /* LinterCacheTests.swift in Sources */,
|
||||
D4998DE91DF194F20006E05D /* FileHeaderRuleTests.swift in Sources */,
|
||||
47ACC89C1E7DCFA00088EEB2 /* ImplicitlyUnwrappedOptionalRuleTests.swift in Sources */,
|
||||
006204DE1E1E4E0A00FFFBE1 /* VerticalWhitespaceRuleTests.swift in Sources */,
|
||||
02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */,
|
||||
D4CA758F1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift in Sources */,
|
||||
@@ -1349,6 +1360,7 @@
|
||||
3B30C4A11C3785B300E04027 /* YamlParserTests.swift in Sources */,
|
||||
D4998DE71DF191380006E05D /* AttributesRuleTests.swift in Sources */,
|
||||
E88198631BEA9A5400333A11 /* RulesTests.swift in Sources */,
|
||||
47ACC89A1E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift in Sources */,
|
||||
D46202211E16002A0027AAD1 /* Swift2RulesTests.swift in Sources */,
|
||||
67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */,
|
||||
C9802F2F1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// ImplicitlyUnwrappedOptionalConfigurationTests.swift
|
||||
// SwiftLint
|
||||
//
|
||||
// Created by Siarhei Fedartsou on 18/03/17.
|
||||
// Copyright © 2017 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
import SourceKittenFramework
|
||||
@testable import SwiftLintFramework
|
||||
import XCTest
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
class ImplicitlyUnwrappedOptionalConfigurationTests: XCTestCase {
|
||||
|
||||
func testImplicitlyUnwrappedOptionalConfigurationProperlyAppliesConfigurationFromDictionary() throws {
|
||||
var configuration = ImplicitlyUnwrappedOptionalConfiguration(mode: .allExceptIBOutlets,
|
||||
severity: SeverityConfiguration(.warning))
|
||||
|
||||
try configuration.apply(configuration: ["mode": "all", "severity": "error"])
|
||||
XCTAssertEqual(configuration.mode, .all)
|
||||
XCTAssertEqual(configuration.severity.severity, .error)
|
||||
|
||||
try configuration.apply(configuration: ["mode": "all_except_iboutlets"])
|
||||
XCTAssertEqual(configuration.mode, .allExceptIBOutlets)
|
||||
XCTAssertEqual(configuration.severity.severity, .error)
|
||||
|
||||
try configuration.apply(configuration: ["severity": "warning"])
|
||||
XCTAssertEqual(configuration.mode, .allExceptIBOutlets)
|
||||
XCTAssertEqual(configuration.severity.severity, .warning)
|
||||
|
||||
try configuration.apply(configuration: ["mode": "all", "severity": "warning"])
|
||||
XCTAssertEqual(configuration.mode, .all)
|
||||
XCTAssertEqual(configuration.severity.severity, .warning)
|
||||
}
|
||||
|
||||
func testImplicitlyUnwrappedOptionalConfigurationThrowsOnBadConfig() {
|
||||
let badConfigs: [[String: Any]] = [
|
||||
["mode": "everything"],
|
||||
["mode": false],
|
||||
["mode": 42]
|
||||
]
|
||||
|
||||
for badConfig in badConfigs {
|
||||
var configuration = ImplicitlyUnwrappedOptionalConfiguration(mode: .allExceptIBOutlets,
|
||||
severity: SeverityConfiguration(.warning))
|
||||
checkError(ConfigurationError.unknownConfiguration) {
|
||||
try configuration.apply(configuration: badConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ImplicitlyUnwrappedOptionalConfigurationTests {
|
||||
static var allTests: [(String, (ImplicitlyUnwrappedOptionalConfigurationTests) -> () throws -> Void)] {
|
||||
return [
|
||||
("testImplicitlyUnwrappedOptionalConfigurationProperlyAppliesConfigurationFromDictionary",
|
||||
testImplicitlyUnwrappedOptionalConfigurationProperlyAppliesConfigurationFromDictionary),
|
||||
("testImplicitlyUnwrappedOptionalConfigurationThrowsOnBadConfig",
|
||||
testImplicitlyUnwrappedOptionalConfigurationThrowsOnBadConfig)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// ImplicitlyUnwrappedOptionalRuleTests.swift
|
||||
// SwiftLint
|
||||
//
|
||||
// Created by Siarhei Fedartsou on 18/03/17.
|
||||
// Copyright © 2017 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import SwiftLintFramework
|
||||
import XCTest
|
||||
|
||||
class ImplicitlyUnwrappedOptionalRuleTests: XCTestCase {
|
||||
|
||||
func testImplicitlyUnwrappedOptionalRuleDefaultConfiguration() {
|
||||
let rule = ImplicitlyUnwrappedOptionalRule()
|
||||
XCTAssertEqual(rule.configuration.mode, .allExceptIBOutlets)
|
||||
XCTAssertEqual(rule.configuration.severity.severity, .warning)
|
||||
}
|
||||
|
||||
func testImplicitlyUnwrappedOptionalRuleWarnsOnOutletsInAllMode() {
|
||||
let baseDescription = ImplicitlyUnwrappedOptionalRule.description
|
||||
let triggeringExamples = [
|
||||
"@IBOutlet private var label: UILabel!",
|
||||
"@IBOutlet var label: UILabel!",
|
||||
"let int: Int!"
|
||||
]
|
||||
|
||||
let nonTriggeringExamples = ["if !boolean {}"]
|
||||
let description = RuleDescription(identifier: baseDescription.identifier,
|
||||
name: baseDescription.name,
|
||||
description: baseDescription.description,
|
||||
nonTriggeringExamples: nonTriggeringExamples,
|
||||
triggeringExamples: triggeringExamples,
|
||||
corrections: baseDescription.corrections)
|
||||
verifyRule(description, ruleConfiguration: ["mode": "all"],
|
||||
commentDoesntViolate: true, stringDoesntViolate: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImplicitlyUnwrappedOptionalRuleTests {
|
||||
static var allTests: [(String, (ImplicitlyUnwrappedOptionalRuleTests) -> () throws -> Void)] {
|
||||
return [
|
||||
("testImplicitlyUnwrappedOptionalRuleDefaultConfiguration",
|
||||
testImplicitlyUnwrappedOptionalRuleDefaultConfiguration),
|
||||
("testImplicitlyUnwrappedOptionalRuleWarnsOnOutletsInAllMode",
|
||||
testImplicitlyUnwrappedOptionalRuleWarnsOnOutletsInAllMode)
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user