From e4c05d592a22d8a6b52aa2af577b366efe2cea33 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Mon, 18 May 2015 05:58:57 +0200 Subject: [PATCH] FunctionBodyLengthRule --- .../SwiftLintFramework/File+SwiftLint.swift | 41 +--------- .../Rules/FunctionBodyLengthRule.swift | 81 +++++++++++++++++++ .../SwiftLintFrameworkTests/LinterTests.swift | 1 + SwiftLint.xcodeproj/project.pbxproj | 4 + 4 files changed, 87 insertions(+), 40 deletions(-) create mode 100644 Source/SwiftLintFramework/Rules/FunctionBodyLengthRule.swift diff --git a/Source/SwiftLintFramework/File+SwiftLint.swift b/Source/SwiftLintFramework/File+SwiftLint.swift index c7f0ba231..822141ae7 100644 --- a/Source/SwiftLintFramework/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/File+SwiftLint.swift @@ -50,52 +50,13 @@ extension File { violations.extend(self.validateTypeName(kind, dict: subDict)) violations.extend(self.validateVariableName(kind, dict: subDict)) violations.extend(TypeBodyLengthRule.validateFile(self, kind: kind, dictionary: subDict)) - violations.extend(self.validateFunctionBodyLength(kind, dict: subDict)) + violations.extend(FunctionBodyLengthRule.validateFile(self, kind: kind, dictionary: subDict)) violations.extend(self.validateNesting(kind, dict: subDict)) } return violations } } - func validateFunctionBodyLength(kind: SwiftDeclarationKind, dict: XPCDictionary) -> - [StyleViolation] { - let functionKinds: [SwiftDeclarationKind] = [ - .FunctionAccessorAddress, - .FunctionAccessorDidset, - .FunctionAccessorGetter, - .FunctionAccessorMutableaddress, - .FunctionAccessorSetter, - .FunctionAccessorWillset, - .FunctionConstructor, - .FunctionDestructor, - .FunctionFree, - .FunctionMethodClass, - .FunctionMethodInstance, - .FunctionMethodStatic, - .FunctionOperator, - .FunctionSubscript - ] - if !contains(functionKinds, 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 > 40 { - violations.append(StyleViolation(type: .Length, - location: location, - reason: "Function body should be span 40 lines or less: currently spans " + - "\(endLine - startLine) lines")) - } - } - return violations - } - func validateTypeName(kind: SwiftDeclarationKind, dict: XPCDictionary) -> [StyleViolation] { let typeKinds: [SwiftDeclarationKind] = [ .Class, diff --git a/Source/SwiftLintFramework/Rules/FunctionBodyLengthRule.swift b/Source/SwiftLintFramework/Rules/FunctionBodyLengthRule.swift new file mode 100644 index 000000000..f9d2ffe3f --- /dev/null +++ b/Source/SwiftLintFramework/Rules/FunctionBodyLengthRule.swift @@ -0,0 +1,81 @@ +// +// FunctionBodyLengthRule.swift +// SwiftLint +// +// Created by JP Simard on 5/18/15. +// Copyright (c) 2015 Realm. All rights reserved. +// + +import SourceKittenFramework +import SwiftXPC + +struct FunctionBodyLengthRule: Rule { + static let identifier = "function_body_length" + static let parameters = [ + RuleParameter(severity: .VeryLow, value: 40), + RuleParameter(severity: .Low, value: 50), + RuleParameter(severity: .Medium, value: 75), + RuleParameter(severity: .High, value: 100), + RuleParameter(severity: .VeryHigh, value: 200) + ] + + 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 functionKinds: [SwiftDeclarationKind] = [ + .FunctionAccessorAddress, + .FunctionAccessorDidset, + .FunctionAccessorGetter, + .FunctionAccessorMutableaddress, + .FunctionAccessorSetter, + .FunctionAccessorWillset, + .FunctionConstructor, + .FunctionDestructor, + .FunctionFree, + .FunctionMethodClass, + .FunctionMethodInstance, + .FunctionMethodStatic, + .FunctionOperator, + .FunctionSubscript + ] + if !contains(functionKinds, 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: "Function body should be span 40 lines or less: currently spans " + + "\(endLine - startLine) lines")) + } + } + } + return violations + } +} diff --git a/Source/SwiftLintFrameworkTests/LinterTests.swift b/Source/SwiftLintFrameworkTests/LinterTests.swift index c79c3fb16..cb070eb5f 100644 --- a/Source/SwiftLintFrameworkTests/LinterTests.swift +++ b/Source/SwiftLintFrameworkTests/LinterTests.swift @@ -123,6 +123,7 @@ class LinterTests: XCTestCase { "}\n" XCTAssertEqual(violations(longerFunctionBody), [StyleViolation(type: .Length, location: Location(file: nil, line: 1), + severity: .VeryLow, reason: "Function body should be span 40 lines or less: currently spans 41 lines")]) } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 425956e5c..c71ad8778 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 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 */; }; + E88DEA901B099A3100A66CB0 /* FunctionBodyLengthRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.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 */; }; @@ -146,6 +147,7 @@ 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 = ""; }; + E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRule.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; }; @@ -361,6 +363,7 @@ E88DEA871B09924C00A66CB0 /* TrailingNewlineRule.swift */, E88DEA891B0992B300A66CB0 /* FileLengthRule.swift */, E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */, + E88DEA8F1B099A3100A66CB0 /* FunctionBodyLengthRule.swift */, ); path = Rules; sourceTree = ""; @@ -528,6 +531,7 @@ E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */, E88DEA821B0990A700A66CB0 /* TodoRule.swift in Sources */, + E88DEA901B099A3100A66CB0 /* FunctionBodyLengthRule.swift in Sources */, E88DEA6B1B0983FE00A66CB0 /* StyleViolation.swift in Sources */, E88DEA7E1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift in Sources */, );