diff --git a/Source/SwiftLintFramework/File+SwiftLint.swift b/Source/SwiftLintFramework/File+SwiftLint.swift index 822141ae7..419c419bf 100644 --- a/Source/SwiftLintFramework/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/File+SwiftLint.swift @@ -47,7 +47,7 @@ extension File { let kindString = subDict["key.kind"] as? String, let kind = flatMap(kindString, { SwiftDeclarationKind(rawValue: $0) }) { violations.extend(self.astViolationsInDictionary(subDict)) - violations.extend(self.validateTypeName(kind, dict: subDict)) + violations.extend(TypeNameRule.validateFile(self, kind: kind, dictionary: subDict)) violations.extend(self.validateVariableName(kind, dict: subDict)) violations.extend(TypeBodyLengthRule.validateFile(self, kind: kind, dictionary: subDict)) violations.extend(FunctionBodyLengthRule.validateFile(self, kind: kind, dictionary: subDict)) @@ -57,40 +57,6 @@ extension File { } } - func validateTypeName(kind: SwiftDeclarationKind, dict: XPCDictionary) -> [StyleViolation] { - let typeKinds: [SwiftDeclarationKind] = [ - .Class, - .Struct, - .Typealias, - .Enum, - .Enumelement - ] - if !contains(typeKinds, kind) { - return [] - } - var violations = [StyleViolation]() - if let name = dict["key.name"] as? String, - let offset = flatMap(dict["key.offset"] as? Int64, { Int($0) }) { - let location = Location(file: self, offset: offset) - let nameCharacterSet = NSCharacterSet(charactersInString: name) - if !NSCharacterSet.alphanumericCharacterSet().isSupersetOfSet(nameCharacterSet) { - violations.append(StyleViolation(type: .NameFormat, - location: location, - reason: "Type name should only contain alphanumeric characters: '\(name)'")) - } else if !name.substringToIndex(name.startIndex.successor()).isUppercase() { - violations.append(StyleViolation(type: .NameFormat, - location: location, - reason: "Type name should start with an uppercase character: '\(name)'")) - } else if count(name) < 3 || count(name) > 40 { - violations.append(StyleViolation(type: .NameFormat, - location: location, - reason: "Type name should be between 3 and 40 characters in length: " + - "'\(name)'")) - } - } - return violations - } - func validateVariableName(kind: SwiftDeclarationKind, dict: XPCDictionary) -> [StyleViolation] { let variableKinds: [SwiftDeclarationKind] = [ .VarClass, diff --git a/Source/SwiftLintFramework/Rules/TypeNameRule.swift b/Source/SwiftLintFramework/Rules/TypeNameRule.swift new file mode 100644 index 000000000..813e62eb5 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/TypeNameRule.swift @@ -0,0 +1,71 @@ +// +// TypeNameRule.swift +// SwiftLint +// +// Created by JP Simard on 5/18/15. +// Copyright (c) 2015 Realm. All rights reserved. +// + +import SourceKittenFramework +import SwiftXPC + +struct TypeNameRule: Rule { + static let identifier = "type_name" + static let parameters = [RuleParameter]() + + static func validateFile(file: File) -> [StyleViolation] { + return self.validateFile(file, dictionary: Structure(file: file).dictionary) + } + + static func validateFile(file: File, dictionary: XPCDictionary) -> [StyleViolation] { + return (dictionary["key.substructure"] as? XPCArray ?? []).flatMap { subItem in + var violations = [StyleViolation]() + if let subDict = subItem as? XPCDictionary, + let kindString = subDict["key.kind"] as? String, + let kind = flatMap(kindString, { SwiftDeclarationKind(rawValue: $0) }) { + violations.extend(self.validateFile(file, dictionary: subDict)) + violations.extend(self.validateFile(file, kind: kind, dictionary: subDict)) + } + return violations + } + } + + static func validateFile(file: File, + kind: SwiftDeclarationKind, + dictionary: XPCDictionary) -> [StyleViolation] { + let typeKinds: [SwiftDeclarationKind] = [ + .Class, + .Struct, + .Typealias, + .Enum, + .Enumelement + ] + if !contains(typeKinds, kind) { + return [] + } + var violations = [StyleViolation]() + if let name = dictionary["key.name"] as? String, + let offset = flatMap(dictionary["key.offset"] as? Int64, { Int($0) }) { + let location = Location(file: file, offset: offset) + let nameCharacterSet = NSCharacterSet(charactersInString: name) + if !NSCharacterSet.alphanumericCharacterSet().isSupersetOfSet(nameCharacterSet) { + violations.append(StyleViolation(type: .NameFormat, + location: location, + severity: .Medium, + reason: "Type name should only contain alphanumeric characters: '\(name)'")) + } else if !name.substringToIndex(name.startIndex.successor()).isUppercase() { + violations.append(StyleViolation(type: .NameFormat, + location: location, + severity: .High, + reason: "Type name should start with an uppercase character: '\(name)'")) + } else if count(name) < 3 || count(name) > 40 { + violations.append(StyleViolation(type: .NameFormat, + location: location, + severity: .Medium, + reason: "Type name should be between 3 and 40 characters in length: " + + "'\(name)'")) + } + } + return violations + } +} diff --git a/Source/SwiftLintFrameworkTests/LinterTests.swift b/Source/SwiftLintFrameworkTests/LinterTests.swift index cb070eb5f..f466b65b5 100644 --- a/Source/SwiftLintFrameworkTests/LinterTests.swift +++ b/Source/SwiftLintFrameworkTests/LinterTests.swift @@ -25,14 +25,17 @@ class LinterTests: XCTestCase { XCTAssertEqual(violations("\(kind) Ab_ {}\n"), [StyleViolation(type: .NameFormat, location: Location(file: nil, line: 1), + severity: .Medium, reason: "Type name should only contain alphanumeric characters: 'Ab_'")]) XCTAssertEqual(violations("\(kind) abc {}\n"), [StyleViolation(type: .NameFormat, location: Location(file: nil, line: 1), + severity: .High, reason: "Type name should start with an uppercase character: 'abc'")]) XCTAssertEqual(violations("\(kind) Ab {}\n"), [StyleViolation(type: .NameFormat, location: Location(file: nil, line: 1), + severity: .Medium, reason: "Type name should be between 3 and 40 characters in length: 'Ab'")]) let longName = join("", Array(count: 40, repeatedValue: "A")) @@ -41,6 +44,7 @@ class LinterTests: XCTestCase { XCTAssertEqual(violations("\(kind) \(longerName) {}\n"), [ StyleViolation(type: .NameFormat, location: Location(file: nil, line: 1), + severity: .Medium, reason: "Type name should be between 3 and 40 characters in length: " + "'\(longerName)'") ]) @@ -66,6 +70,7 @@ class LinterTests: XCTestCase { [ StyleViolation(type: .NameFormat, location: Location(file: nil, line: 2), + severity: .High, reason: "Type name should start with an uppercase character: 'def'") ]) diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index c71ad8778..c4ac4ab99 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA8B1B0999A000A66CB0 /* ASTRule.swift */; }; E88DEA8E1B0999CD00A66CB0 /* TypeBodyLengthRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */; }; E88DEA901B099A3100A66CB0 /* FunctionBodyLengthRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */; }; + E88DEA921B099B1F00A66CB0 /* TypeNameRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */; }; E8AB1A2E1A649F2100452012 /* libclang.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E8AB1A2D1A649F2100452012 /* libclang.dylib */; }; E8BA7E111B07A3EC003E02D0 /* Commandant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8BA7E101B07A3EC003E02D0 /* Commandant.framework */; }; E8BA7E131B07A3F3003E02D0 /* LlamaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8BA7E121B07A3F3003E02D0 /* LlamaKit.framework */; }; @@ -148,6 +149,7 @@ E88DEA8B1B0999A000A66CB0 /* ASTRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTRule.swift; sourceTree = ""; }; E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeBodyLengthRule.swift; sourceTree = ""; }; E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRule.swift; sourceTree = ""; }; + E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeNameRule.swift; sourceTree = ""; }; E8AB1A2D1A649F2100452012 /* libclang.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libclang.dylib; path = Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib; sourceTree = DEVELOPER_DIR; }; E8BA7E101B07A3EC003E02D0 /* Commandant.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Commandant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E8BA7E121B07A3F3003E02D0 /* LlamaKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = LlamaKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -364,6 +366,7 @@ E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */, E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */, + E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */, ); path = Rules; sourceTree = ""; @@ -528,6 +531,7 @@ E88DEA771B098D0C00A66CB0 /* Rule.swift in Sources */, E88DEA7C1B098D7D00A66CB0 /* LineLengthRule.swift in Sources */, E88DEA801B09903300A66CB0 /* ForceCastRule.swift in Sources */, + E88DEA921B099B1F00A66CB0 /* TypeNameRule.swift in Sources */, E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */, E88DEA821B0990A700A66CB0 /* TodoRule.swift in Sources */,