Add configuration for implicitly_unwrapped_optional_rule, add triggering on complex types(AnyCollection<Int!> for ex.)

This commit is contained in:
Siarhei Fedartsou
2017-03-19 00:27:13 +03:00
parent d25ec1f1fc
commit 2e579aaa6c
9 changed files with 210 additions and 14 deletions
+1 -1
View File
@@ -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)
]
}
@@ -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
}
}
@@ -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 {
+12
View File
@@ -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)
]
}
}