diff --git a/Source/SwiftLintFramework/ASTRule.swift b/Source/SwiftLintFramework/ASTRule.swift new file mode 100644 index 000000000..d82380205 --- /dev/null +++ b/Source/SwiftLintFramework/ASTRule.swift @@ -0,0 +1,18 @@ +// +// ASTRule.swift +// SwiftLint +// +// Created by JP Simard on 2015-05-16. +// Copyright (c) 2015 Realm. All rights reserved. +// + +import SourceKittenFramework +import SwiftXPC + +protocol ASTRule: Rule { + static func validateFile(file: File, dictionary: XPCDictionary) -> [StyleViolation] + + static func validateFile(file: File, + kind: SwiftDeclarationKind, + dictionary: XPCDictionary) -> [StyleViolation] +} diff --git a/Source/SwiftLintFramework/File+SwiftLint.swift b/Source/SwiftLintFramework/File+SwiftLint.swift index a3ebacee2..c7f0ba231 100644 --- a/Source/SwiftLintFramework/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/File+SwiftLint.swift @@ -49,7 +49,7 @@ extension File { violations.extend(self.astViolationsInDictionary(subDict)) violations.extend(self.validateTypeName(kind, dict: subDict)) violations.extend(self.validateVariableName(kind, dict: subDict)) - violations.extend(self.validateTypeBodyLength(kind, dict: subDict)) + violations.extend(TypeBodyLengthRule.validateFile(self, kind: kind, dictionary: subDict)) violations.extend(self.validateFunctionBodyLength(kind, dict: subDict)) violations.extend(self.validateNesting(kind, dict: subDict)) } @@ -57,34 +57,6 @@ extension File { } } - func validateTypeBodyLength(kind: SwiftDeclarationKind, dict: XPCDictionary) -> - [StyleViolation] { - let typeKinds: [SwiftDeclarationKind] = [ - .Class, - .Struct, - .Enum - ] - if !contains(typeKinds, kind) { - return [] - } - var violations = [StyleViolation]() - if let offset = flatMap(dict["key.offset"] as? Int64, { Int($0) }), - let bodyOffset = flatMap(dict["key.bodyoffset"] as? Int64, { Int($0) }), - let bodyLength = flatMap(dict["key.bodylength"] as? Int64, { Int($0) }) { - let location = Location(file: self, offset: offset) - let startLine = self.contents.lineAndCharacterForByteOffset(bodyOffset) - let endLine = self.contents.lineAndCharacterForByteOffset(bodyOffset + bodyLength) - if let startLine = startLine?.line, let endLine = endLine?.line - where endLine - startLine > 200 { - violations.append(StyleViolation(type: .Length, - location: location, - reason: "Type body should be span 200 lines or less: currently spans " + - "\(endLine - startLine) lines")) - } - } - return violations - } - func validateFunctionBodyLength(kind: SwiftDeclarationKind, dict: XPCDictionary) -> [StyleViolation] { let functionKinds: [SwiftDeclarationKind] = [ diff --git a/Source/SwiftLintFramework/Rules/TypeBodyLengthRule.swift b/Source/SwiftLintFramework/Rules/TypeBodyLengthRule.swift new file mode 100644 index 000000000..097f640ab --- /dev/null +++ b/Source/SwiftLintFramework/Rules/TypeBodyLengthRule.swift @@ -0,0 +1,70 @@ +// +// TypeBodyLengthRule.swift +// SwiftLint +// +// Created by JP Simard on 5/18/15. +// Copyright (c) 2015 Realm. All rights reserved. +// + +import SourceKittenFramework +import SwiftXPC + +struct TypeBodyLengthRule: Rule { + static let identifier = "type_body_length" + static let parameters = [ + RuleParameter(severity: .VeryLow, value: 200), + RuleParameter(severity: .Low, value: 250), + RuleParameter(severity: .Medium, value: 300), + RuleParameter(severity: .High, value: 350), + RuleParameter(severity: .VeryHigh, value: 400) + ] + + 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, + .Enum + ] + if !contains(typeKinds, kind) { + return [] + } + var violations = [StyleViolation]() + if let offset = flatMap(dictionary["key.offset"] as? Int64, { Int($0) }), + let bodyOffset = flatMap(dictionary["key.bodyoffset"] as? Int64, { Int($0) }), + let bodyLength = flatMap(dictionary["key.bodylength"] as? Int64, { Int($0) }) { + let location = Location(file: file, offset: offset) + let startLine = file.contents.lineAndCharacterForByteOffset(bodyOffset) + let endLine = file.contents.lineAndCharacterForByteOffset(bodyOffset + bodyLength) + for parameter in reverse(parameters) { + if let startLine = startLine?.line, let endLine = endLine?.line + where endLine - startLine > parameter.value { + violations.append(StyleViolation(type: .Length, + location: location, + severity: parameter.severity, + reason: "Type body should be span 200 lines or less: currently spans " + + "\(endLine - startLine) lines")) + } + } + } + return violations + } +} diff --git a/Source/SwiftLintFrameworkTests/LinterTests.swift b/Source/SwiftLintFrameworkTests/LinterTests.swift index 893b5b016..c79c3fb16 100644 --- a/Source/SwiftLintFrameworkTests/LinterTests.swift +++ b/Source/SwiftLintFrameworkTests/LinterTests.swift @@ -137,6 +137,7 @@ class LinterTests: XCTestCase { "}\n" XCTAssertEqual(violations(longerTypeBody), [StyleViolation(type: .Length, location: Location(file: nil, line: 1), + severity: .VeryLow, reason: "Type body should be span 200 lines or less: currently spans 201 lines")]) } } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index c8768137a..425956e5c 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ E88DEA861B0991BF00A66CB0 /* TrailingWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA851B0991BF00A66CB0 /* TrailingWhitespaceRule.swift */; }; E88DEA881B09924C00A66CB0 /* TrailingNewlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA871B09924C00A66CB0 /* TrailingNewlineRule.swift */; }; E88DEA8A1B0992B300A66CB0 /* FileLengthRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */; }; + E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA8B1B0999A000A66CB0 /* ASTRule.swift */; }; + E88DEA8E1B0999CD00A66CB0 /* TypeBodyLengthRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.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 */; }; @@ -142,6 +144,8 @@ E88DEA851B0991BF00A66CB0 /* TrailingWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingWhitespaceRule.swift; sourceTree = ""; }; E88DEA871B09924C00A66CB0 /* TrailingNewlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingNewlineRule.swift; sourceTree = ""; }; E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileLengthRule.swift; sourceTree = ""; }; + 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 = ""; }; 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; }; @@ -296,6 +300,7 @@ children = ( E88DEA7A1B098D7300A66CB0 /* Rules */, E88DEA741B09852000A66CB0 /* File+SwiftLint.swift */, + E88DEA8B1B0999A000A66CB0 /* ASTRule.swift */, E88DEA761B098D0C00A66CB0 /* Rule.swift */, E88DEA781B098D4400A66CB0 /* RuleParameter.swift */, E812249B1B04FADC001783D2 /* Linter.swift */, @@ -355,6 +360,7 @@ E88DEA851B0991BF00A66CB0 /* TrailingWhitespaceRule.swift */, E88DEA871B09924C00A66CB0 /* TrailingNewlineRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, + E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */, ); path = Rules; sourceTree = ""; @@ -513,12 +519,14 @@ E88DEA6D1B09842200A66CB0 /* StyleViolationType.swift in Sources */, E88DEA8A1B0992B300A66CB0 /* FileLengthRule.swift in Sources */, E88DEA751B09852000A66CB0 /* File+SwiftLint.swift in Sources */, + E88DEA8E1B0999CD00A66CB0 /* TypeBodyLengthRule.swift in Sources */, E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */, E88DEA881B09924C00A66CB0 /* TrailingNewlineRule.swift in Sources */, E88DEA771B098D0C00A66CB0 /* Rule.swift in Sources */, E88DEA7C1B098D7D00A66CB0 /* LineLengthRule.swift in Sources */, E88DEA801B09903300A66CB0 /* ForceCastRule.swift in Sources */, E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, + E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */, E88DEA821B0990A700A66CB0 /* TodoRule.swift in Sources */, E88DEA6B1B0983FE00A66CB0 /* StyleViolation.swift in Sources */, E88DEA7E1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift in Sources */,