From d652cfe4abd52cc88758c1ea48a87f4e2ab69d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Mon, 1 May 2017 00:22:59 +0100 Subject: [PATCH] Add options to generic_type_name and type_name rules --- CHANGELOG.md | 5 +- .../Rules/GenericTypeNameRule.swift | 6 +- .../Rules/TypeNameRule.swift | 6 +- SwiftLint.xcodeproj/project.pbxproj | 8 ++ Tests/LinuxMain.swift | 2 + .../GenericTypeNameRuleTests.swift | 73 +++++++++++++++++++ .../IdentifierNameRuleTests.swift | 10 ++- .../SwiftLintFrameworkTests/RulesTests.swift | 10 --- .../TypeNameRuleTests.swift | 72 ++++++++++++++++++ 9 files changed, 172 insertions(+), 20 deletions(-) create mode 100644 Tests/SwiftLintFrameworkTests/GenericTypeNameRuleTests.swift create mode 100644 Tests/SwiftLintFrameworkTests/TypeNameRuleTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a433488f..ec0b901f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,9 @@ ##### Enhancements -* Add opt-in options to `identifier_name` rule to exclude non-alphanumeric - characters and to allow names that start with uppercase. +* Add opt-in configurations to `generic_type_name`, `identifier_name` and + `type_name` rules to allow excluding non-alphanumeric characters and names + that start with uppercase. [Javier Hernández](https://github.com/jaherhi) [#541](https://github.com/realm/SwiftLint/issues/541) diff --git a/Source/SwiftLintFramework/Rules/GenericTypeNameRule.swift b/Source/SwiftLintFramework/Rules/GenericTypeNameRule.swift index b80c05bc0..a2eb59082 100644 --- a/Source/SwiftLintFramework/Rules/GenericTypeNameRule.swift +++ b/Source/SwiftLintFramework/Rules/GenericTypeNameRule.swift @@ -166,14 +166,16 @@ public struct GenericTypeNameRule: ASTRule, ConfigurationProviderRule { return [] } - if !CharacterSet.alphanumerics.isSuperset(ofCharactersIn: name) { + let containsAllowedSymbol = configuration.allowedSymbols.first(where: { name.contains($0) }) != nil + if !containsAllowedSymbol && !CharacterSet.alphanumerics.isSuperset(ofCharactersIn: name) { return [ StyleViolation(ruleDescription: type(of: self).description, severity: .error, location: Location(file: file, byteOffset: offset), reason: "Generic type name should only contain alphanumeric characters: '\(name)'") ] - } else if !name.substring(to: name.index(after: name.startIndex)).isUppercase() { + } else if configuration.validatesStartWithLowercase && + !name.substring(to: name.index(after: name.startIndex)).isUppercase() { return [ StyleViolation(ruleDescription: type(of: self).description, severity: .error, diff --git a/Source/SwiftLintFramework/Rules/TypeNameRule.swift b/Source/SwiftLintFramework/Rules/TypeNameRule.swift index 1de8d8b32..f1b6f24ff 100644 --- a/Source/SwiftLintFramework/Rules/TypeNameRule.swift +++ b/Source/SwiftLintFramework/Rules/TypeNameRule.swift @@ -74,12 +74,14 @@ public struct TypeNameRule: ASTRule, ConfigurationProviderRule { } let name = name.nameStrippingLeadingUnderscoreIfPrivate(dictionary) - if !CharacterSet.alphanumerics.isSuperset(ofCharactersIn: name) { + let containsAllowedSymbol = configuration.allowedSymbols.first(where: { name.contains($0) }) != nil + if !containsAllowedSymbol && !CharacterSet.alphanumerics.isSuperset(ofCharactersIn: name) { return [StyleViolation(ruleDescription: type(of: self).description, severity: .error, location: Location(file: file, byteOffset: offset), reason: "Type name should only contain alphanumeric characters: '\(name)'")] - } else if !name.substring(to: name.index(after: name.startIndex)).isUppercase() { + } else if configuration.validatesStartWithLowercase && + !name.substring(to: name.index(after: name.startIndex)).isUppercase() { return [StyleViolation(ruleDescription: type(of: self).description, severity: .error, location: Location(file: file, byteOffset: offset), diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 2dc241d8e..4542eeccc 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -33,6 +33,8 @@ 3B12C9C51C322032000B423F /* MasterRuleList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B12C9C41C322032000B423F /* MasterRuleList.swift */; }; 3B12C9C71C3361CB000B423F /* RuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B12C9C61C3361CB000B423F /* RuleTests.swift */; }; 3B1DF0121C5148140011BCED /* CustomRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1DF0111C5148140011BCED /* CustomRules.swift */; }; + 3B20CD0A1EB699380069EF2E /* GenericTypeNameRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B20CD091EB699380069EF2E /* GenericTypeNameRuleTests.swift */; }; + 3B20CD0C1EB699C20069EF2E /* TypeNameRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B20CD0B1EB699C20069EF2E /* TypeNameRuleTests.swift */; }; 3B30C4A11C3785B300E04027 /* YamlParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B30C4A01C3785B300E04027 /* YamlParserTests.swift */; }; 3B3A9A331EA3DFD90075B121 /* IdentifierNameRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A9A321EA3DFD90075B121 /* IdentifierNameRuleTests.swift */; }; 3B5B9FE11C444DA20009AD27 /* Array+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5B9FE01C444DA20009AD27 /* Array+SwiftLint.swift */; }; @@ -301,6 +303,8 @@ 3B12C9C41C322032000B423F /* MasterRuleList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterRuleList.swift; sourceTree = ""; }; 3B12C9C61C3361CB000B423F /* RuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleTests.swift; sourceTree = ""; }; 3B1DF0111C5148140011BCED /* CustomRules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomRules.swift; sourceTree = ""; }; + 3B20CD091EB699380069EF2E /* GenericTypeNameRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericTypeNameRuleTests.swift; sourceTree = ""; }; + 3B20CD0B1EB699C20069EF2E /* TypeNameRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeNameRuleTests.swift; sourceTree = ""; }; 3B30C4A01C3785B300E04027 /* YamlParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YamlParserTests.swift; sourceTree = ""; }; 3B3A9A321EA3DFD90075B121 /* IdentifierNameRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifierNameRuleTests.swift; sourceTree = ""; }; 3B5B9FE01C444DA20009AD27 /* Array+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+SwiftLint.swift"; sourceTree = ""; }; @@ -759,6 +763,7 @@ 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */, D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */, D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */, + 3B20CD091EB699380069EF2E /* GenericTypeNameRuleTests.swift */, 3B3A9A321EA3DFD90075B121 /* IdentifierNameRuleTests.swift */, 47ACC8991E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift */, 47ACC89B1E7DCFA00088EEB2 /* ImplicitlyUnwrappedOptionalRuleTests.swift */, @@ -775,6 +780,7 @@ E81224991B04F85B001783D2 /* TestHelpers.swift */, D4DB92241E628898005DE9C1 /* TodoRuleTests.swift */, C9802F2E1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift */, + 3B20CD0B1EB699C20069EF2E /* TypeNameRuleTests.swift */, D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */, 006204DD1E1E4E0A00FFFBE1 /* VerticalWhitespaceRuleTests.swift */, 3B12C9C21C320A53000B423F /* Yaml+SwiftLintTests.swift */, @@ -1368,12 +1374,14 @@ 6C7045441C6ADA450003F15A /* SourceKitCrashTests.swift in Sources */, 3BB47D871C51DE6E00AE6A10 /* CustomRulesTests.swift in Sources */, E812249A1B04F85B001783D2 /* TestHelpers.swift in Sources */, + 3B20CD0C1EB699C20069EF2E /* TypeNameRuleTests.swift in Sources */, 3B3A9A331EA3DFD90075B121 /* IdentifierNameRuleTests.swift in Sources */, E86396C71BADAFE6002C9E88 /* ReporterTests.swift in Sources */, D43B04661E071ED3004016AF /* ColonRuleTests.swift in Sources */, 3B12C9C71C3361CB000B423F /* RuleTests.swift in Sources */, 67EB4DFC1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift in Sources */, 3B30C4A11C3785B300E04027 /* YamlParserTests.swift in Sources */, + 3B20CD0A1EB699380069EF2E /* GenericTypeNameRuleTests.swift in Sources */, D4998DE71DF191380006E05D /* AttributesRuleTests.swift in Sources */, E88198631BEA9A5400333A11 /* RulesTests.swift in Sources */, 47ACC89A1E7DCCAD0088EEB2 /* ImplicitlyUnwrappedOptionalConfigurationTests.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index c09b3c130..57dde5943 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -19,6 +19,7 @@ XCTMain([ testCase(ExtendedNSStringTests.allTests), testCase(FileHeaderRuleTests.allTests), testCase(FunctionBodyLengthRuleTests.allTests), + testCase(GenericTypeNameRuleTests.allTests), testCase(IdentifierNameRuleTests.allTests), testCase(ImplicitlyUnwrappedOptionalConfigurationTests.allTests), testCase(ImplicitlyUnwrappedOptionalRuleTests.allTests), @@ -33,6 +34,7 @@ XCTMain([ testCase(RuleTests.allTests), testCase(SourceKitCrashTests.allTests), testCase(TrailingCommaRuleTests.allTests), + testCase(TypeNameRuleTests.allTests), testCase(TodoRuleTests.allTests), testCase(UnusedOptionalBindingRuleTests.allTests), testCase(VerticalWhitespaceRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/GenericTypeNameRuleTests.swift b/Tests/SwiftLintFrameworkTests/GenericTypeNameRuleTests.swift new file mode 100644 index 000000000..026098f05 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/GenericTypeNameRuleTests.swift @@ -0,0 +1,73 @@ +// +// GenericTypeNameRuleTests.swift +// SwiftLint +// +// Created by Javier Hernandez on 30/04/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import SwiftLintFramework +import XCTest + +class GenericTypeNameRuleTests: XCTestCase { + + func testGenericTypeName() { + verifyRule(GenericTypeNameRule.description) + } + + func testGenericTypeNameWithAllowedSymbols() { + let baseDescription = GenericTypeNameRule.description + let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ + "func foo() {}\n", + "func foo(param: U%) -> T$ {}\n", + "typealias StringDictionary = Dictionary\n", + "class Foo {}\n", + "struct Foo {}\n", + "enum Foo {}\n" + ] + + let description = RuleDescription(identifier: baseDescription.identifier, + name: baseDescription.name, + description: baseDescription.description, + nonTriggeringExamples: nonTriggeringExamples, + triggeringExamples: baseDescription.triggeringExamples, + corrections: baseDescription.corrections, + deprecatedAliases: baseDescription.deprecatedAliases) + + verifyRule(description, ruleConfiguration: ["allowed_symbols": ["$", "%"]]) + } + + func testGenericTypeNameWithIgnoreStartWithLowercase() { + let baseDescription = GenericTypeNameRule.description + let triggeringExamplesToRemove = [ + "func foo<↓type>() {}\n", + "class Foo<↓type> {}\n", + "struct Foo<↓type> {}\n", + "enum Foo<↓type> {}\n" + ] + let nonTriggeringExamples = baseDescription.nonTriggeringExamples + + triggeringExamplesToRemove.map { $0.replacingOccurrences(of: "↓", with: "") } + let triggeringExamples = baseDescription.triggeringExamples + .filter { !triggeringExamplesToRemove.contains($0) } + + let description = RuleDescription(identifier: baseDescription.identifier, + name: baseDescription.name, + description: baseDescription.description, + nonTriggeringExamples: nonTriggeringExamples, + triggeringExamples: triggeringExamples, + corrections: baseDescription.corrections, + deprecatedAliases: baseDescription.deprecatedAliases) + + verifyRule(description, ruleConfiguration: ["validates_start_lowercase": false]) + } +} + +extension GenericTypeNameRuleTests { + static var allTests: [(String, (GenericTypeNameRuleTests) -> () throws -> Void)] { + return [ + ("testGenericTypeName", testGenericTypeName), + ("testGenericTypeNameWithAllowedSymbols", testGenericTypeNameWithAllowedSymbols), + ("testGenericTypeNameWithIgnoreStartWithLowercase", testGenericTypeNameWithIgnoreStartWithLowercase) + ] + } +} diff --git a/Tests/SwiftLintFrameworkTests/IdentifierNameRuleTests.swift b/Tests/SwiftLintFrameworkTests/IdentifierNameRuleTests.swift index b426c1f20..c4f3af9b6 100644 --- a/Tests/SwiftLintFrameworkTests/IdentifierNameRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/IdentifierNameRuleTests.swift @@ -36,12 +36,14 @@ class IdentifierNameRuleTests: XCTestCase { func testIdentifierNameWithIgnoreStartWithLowercase() { let baseDescription = IdentifierNameRule.description - let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ - "let MyLet = 0", - "enum Foo { case MyEnum }" + let triggeringExamplesToRemove = [ + "↓let MyLet = 0", + "enum Foo { case ↓MyEnum }" ] + let nonTriggeringExamples = baseDescription.nonTriggeringExamples + + triggeringExamplesToRemove.map { $0.replacingOccurrences(of: "↓", with: "") } let triggeringExamples = baseDescription.triggeringExamples - .filter { !$0.contains("MyLet") && !$0.contains("MyEnum") } + .filter { !triggeringExamplesToRemove.contains($0) } let description = RuleDescription(identifier: baseDescription.identifier, name: baseDescription.name, diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index ccbc4a1d2..0321e3dd0 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -121,10 +121,6 @@ class RulesTests: XCTestCase { verifyRule(FunctionParameterCountRule.description) } - func testGenericTypeName() { - verifyRule(GenericTypeNameRule.description) - } - func testImplicitGetter() { verifyRule(ImplicitGetterRule.description) } @@ -314,10 +310,6 @@ class RulesTests: XCTestCase { verifyRule(TypeBodyLengthRule.description) } - func testTypeName() { - verifyRule(TypeNameRule.description) - } - func testUnusedClosureParameter() { verifyRule(UnusedClosureParameterRule.description) } @@ -377,7 +369,6 @@ extension RulesTests { ("testForWhere", testForWhere), ("testFunctionBodyLength", testFunctionBodyLength), ("testFunctionParameterCount", testFunctionParameterCount), - ("testGenericTypeName", testGenericTypeName), ("testImplicitGetter", testImplicitGetter), ("testImplicitlyUnwrappedOptional", testImplicitlyUnwrappedOptional), ("testImplicitReturn", testImplicitReturn), @@ -414,7 +405,6 @@ extension RulesTests { ("testTrailingSemicolon", testTrailingSemicolon), ("testTrailingWhitespace", testTrailingWhitespace), ("testTypeBodyLength", testTypeBodyLength), - ("testTypeName", testTypeName), ("testUnusedClosureParameter", testUnusedClosureParameter), ("testUnusedEnumerated", testUnusedEnumerated), ("testValidIBInspectable", testValidIBInspectable), diff --git a/Tests/SwiftLintFrameworkTests/TypeNameRuleTests.swift b/Tests/SwiftLintFrameworkTests/TypeNameRuleTests.swift new file mode 100644 index 000000000..3062953a9 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/TypeNameRuleTests.swift @@ -0,0 +1,72 @@ +// +// TypeNameRuleTests.swift +// SwiftLint +// +// Created by Javier Hernandez on 30/04/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import SwiftLintFramework +import XCTest + +class TypeNameRuleTests: XCTestCase { + + func testTypeName() { + verifyRule(TypeNameRule.description) + } + + func testTypeNameWithAllowedSymbols() { + let baseDescription = TypeNameRule.description + let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ + "class MyType$ {}", + "struct MyType$ {}", + "enum MyType$ {}", + "typealias Foo$ = Void", + "protocol Foo {\n associatedtype Bar$\n }" + ] + + let description = RuleDescription(identifier: baseDescription.identifier, + name: baseDescription.name, + description: baseDescription.description, + nonTriggeringExamples: nonTriggeringExamples, + triggeringExamples: baseDescription.triggeringExamples, + corrections: baseDescription.corrections, + deprecatedAliases: baseDescription.deprecatedAliases) + + verifyRule(description, ruleConfiguration: ["allowed_symbols": ["$"]]) + } + + func testTypeNameWithIgnoreStartWithLowercase() { + let baseDescription = TypeNameRule.description + let triggeringExamplesToRemove = [ + "private typealias ↓foo = Void", + "↓class myType {}", + "↓struct myType {}", + "↓enum myType {}" + ] + let nonTriggeringExamples = baseDescription.nonTriggeringExamples + + triggeringExamplesToRemove.map { $0.replacingOccurrences(of: "↓", with: "") } + let triggeringExamples = baseDescription.triggeringExamples + .filter { !triggeringExamplesToRemove.contains($0) } + + let description = RuleDescription(identifier: baseDescription.identifier, + name: baseDescription.name, + description: baseDescription.description, + nonTriggeringExamples: nonTriggeringExamples, + triggeringExamples: triggeringExamples, + corrections: baseDescription.corrections, + deprecatedAliases: baseDescription.deprecatedAliases) + + verifyRule(description, ruleConfiguration: ["validates_start_lowercase": false]) + } +} + +extension TypeNameRuleTests { + static var allTests: [(String, (TypeNameRuleTests) -> () throws -> Void)] { + return [ + ("testTypeName", testTypeName), + ("testTypeNameWithAllowedSymbols", testTypeNameWithAllowedSymbols), + ("testTypeNameWithIgnoreStartWithLowercase", testTypeNameWithIgnoreStartWithLowercase) + ] + } +}