diff --git a/Source/SwiftLintFramework/Linter.swift b/Source/SwiftLintFramework/Linter.swift index bfeee14ef..87dcd2d23 100644 --- a/Source/SwiftLintFramework/Linter.swift +++ b/Source/SwiftLintFramework/Linter.swift @@ -234,30 +234,82 @@ extension File { // swiftlint:enable_rule:force_cast var violations = self.astViolationsInDictionary(subDict) if let kindString = subDict["key.kind"] as? String, - let kind = flatMap(kindString, { SwiftDeclarationKind(rawValue: $0) }) - where contains([.Class, .Struct, .Typealias, .Enum, .Enumelement], kind), - let name = subDict["key.name"] as? String, - let offset = flatMap(subDict["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)'")) - } + let kind = flatMap(kindString, { SwiftDeclarationKind(rawValue: $0) }) { + violations.extend(self.validateTypeName(kind, dict: subDict)) + violations.extend(self.validateVariableName(kind, dict: subDict)) } return violations }, [], +) } + + 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, + .VarGlobal, + .VarInstance, + .VarLocal, + .VarParameter, + .VarStatic + ] + if !contains(variableKinds, 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: "Variable name should only contain alphanumeric characters: '\(name)'")) + } else if name.substringToIndex(name.startIndex.successor()).isUppercase() { + violations.append(StyleViolation(type: .NameFormat, + location: location, + reason: "Variable name should start with a lowercase character: '\(name)'")) + } else if count(name) < 3 || count(name) > 40 { + violations.append(StyleViolation(type: .NameFormat, + location: location, + reason: "Variable name should be between 3 and 40 characters in length: " + + "'\(name)'")) + } + } + return violations + } } extension String { diff --git a/Source/SwiftLintFrameworkTests/LinterTests.swift b/Source/SwiftLintFrameworkTests/LinterTests.swift index bdae37374..a269925e7 100644 --- a/Source/SwiftLintFrameworkTests/LinterTests.swift +++ b/Source/SwiftLintFrameworkTests/LinterTests.swift @@ -40,7 +40,6 @@ class LinterTests: XCTestCase { XCTAssertEqual(violations("\(kind) Ab {}\n"), [StyleViolation(type: .NameFormat, location: Location(file: nil, line: 1), reason: "Type name should be between 3 and 40 characters in length: 'Ab'")]) - XCTAssertEqual(violations("\(kind) Abc {}\n"), []) let longName = join("", Array(count: 40, repeatedValue: "A")) XCTAssertEqual(violations("\(kind) \(longName) {}\n"), []) @@ -80,7 +79,40 @@ class LinterTests: XCTestCase { } func testVariableNames() { - // TODO: Variable names should contain between 3 and 20 characters. + for kind in ["class", "struct"] { + for varType in ["var", "let"] { + XCTAssertEqual(violations("\(kind) Abc { \(varType) def: Void }\n"), []) + + XCTAssertEqual(violations("\(kind) Abc { \(varType) de_: Void }\n"), [ + StyleViolation(type: .NameFormat, + location: Location(file: nil, line: 1), + reason: "Variable name should only contain alphanumeric characters: 'de_'") + ]) + + XCTAssertEqual(violations("\(kind) Abc { \(varType) Def: Void }\n"), [ + StyleViolation(type: .NameFormat, + location: Location(file: nil, line: 1), + reason: "Variable name should start with a lowercase character: 'Def'") + ]) + + XCTAssertEqual(violations("\(kind) Abc { \(varType) de: Void }\n"), [ + StyleViolation(type: .NameFormat, + location: Location(file: nil, line: 1), + reason: "Variable name should be between 3 and 40 characters in length: " + + "'de'") + ]) + + let longName = join("", Array(count: 40, repeatedValue: "d")) + XCTAssertEqual(violations("\(kind) Abc { \(varType) \(longName): Void }\n"), []) + let longerName = longName + "d" + XCTAssertEqual(violations("\(kind) Abc { \(varType) \(longerName): Void }\n"), [ + StyleViolation(type: .NameFormat, + location: Location(file: nil, line: 1), + reason: "Variable name should be between 3 and 40 characters in length: " + + "'\(longerName)'") + ]) + } + } } func testClosureLengths() { @@ -207,6 +239,7 @@ class LinterTests: XCTestCase { func testForceCasting() { XCTAssertEqual(violations("NSNumber() as? Int\n"), []) + XCTAssertEqual(violations("// NSNumber() as! Int\n"), []) XCTAssertEqual(violations("NSNumber() as! Int\n"), [StyleViolation(type: .ForceCast, location: Location(file: nil, line: 1), diff --git a/Source/swiftlint/Lint.swift b/Source/swiftlint/Lint.swift index 1475e3d6f..ea32a455e 100644 --- a/Source/swiftlint/Lint.swift +++ b/Source/swiftlint/Lint.swift @@ -16,7 +16,8 @@ let fileManager = NSFileManager.defaultManager() struct LintCommand: CommandType { let verb = "lint" - let function = "Print lint warnings and errors for the Swift files in the current directory (default command)" + let function = "Print lint warnings and errors for the Swift files in the current directory " + + "(default command)" func run(mode: CommandMode) -> Result<(), CommandantError<()>> { println("Finding Swift files in current directory...")