From e5fd97c803267fd8622367d986b7927f650b3348 Mon Sep 17 00:00:00 2001 From: Donatas Stundys Date: Fri, 25 Mar 2016 01:03:27 +0200 Subject: [PATCH] Add SwiftLint We need to enforce clean code. Add [SwiftLint](https://github.com/realm/SwiftLint) to automatically check code style. * Run SwiftLint autocorrect and fix minor violations * Add .swiftlint.yml config * Add Xcode build phase to run linter on each build --- .swiftlint.yml | 9 ++++++++ .travis.yml | 2 ++ Sources/Swifton/Controller.swift | 21 ++++++++--------- Sources/Swifton/CookiesMiddleware.swift | 2 +- Sources/Swifton/Extensions.swift | 25 ++++++++++---------- Sources/Swifton/JSON.swift | 8 +++---- Sources/Swifton/MemoryModel.swift | 27 +++++++++++----------- Sources/Swifton/Middleware.swift | 2 +- Sources/Swifton/MimeType.swift | 5 ++-- Sources/Swifton/ParametersMiddleware.swift | 6 ++--- Sources/Swifton/Router.swift | 20 ++++++++-------- Sources/Swifton/View.swift | 14 +++++------ Swifton.xcodeproj/project.pbxproj | 24 ++++++++++++++++--- Tests/Swifton/MemoryModelTests.swift | 2 +- Tests/Swifton/SwiftonTest.swift | 2 +- Tests/Swifton/TestModelsController.swift | 12 +++++----- install_swiftlint.sh | 24 +++++++++++++++++++ 17 files changed, 129 insertions(+), 76 deletions(-) create mode 100644 .swiftlint.yml create mode 100755 install_swiftlint.sh diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..69e2258 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,9 @@ +included: + - Sources/Swifton + - Tests +disabled_rules: + - force_cast + - force_try +line_length: 120 +variable_name: + min_length: 2 diff --git a/.travis.yml b/.travis.yml index 39219ba..33331dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ sudo: required dist: trusty osx_image: xcode7.3 install: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./install_swiftlint.sh; fi - curl -sL https://gist.github.com/kylef/5c0475ff02b7c7671d2a/raw/621ef9b29bbb852fdfd2e10ed147b321d792c1e4/swiftenv-install.sh | bash script: - . ~/.swiftenv/init @@ -14,3 +15,4 @@ script: - rm -rf Packages/*/Tests - swift build - swift test +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then swiftlint; fi diff --git a/Sources/Swifton/Controller.swift b/Sources/Swifton/Controller.swift index 3563c3d..5bbc4da 100644 --- a/Sources/Swifton/Controller.swift +++ b/Sources/Swifton/Controller.swift @@ -12,10 +12,10 @@ public class Controller { var filters = [String: Filter]() var beforeFilters = FilterCollection() var afterFilters = FilterCollection() - public let next:Response? = nil + public let next: Response? = nil public init() { controller() } - + public func controller() {} public func action(name: String, body: Action) { @@ -29,10 +29,10 @@ public class Controller { public subscript(actionName: String) -> Action { get { return { (request) in - guard let action = self.actions[actionName] else { + guard let action = self.actions[actionName] else { return Response(.NotFound, contentType: "text/plain; charset=utf8", body: "Action Not Found") } - + if let filterResponse = self.runFilters(request, actionName, self.beforeFilters) { return filterResponse } @@ -65,7 +65,7 @@ public class Controller { } } } - return nil + return nil } func runFilter(filter: Filter, _ actionName: String, _ request: Request, _ options: FilterOptions) -> Response? { @@ -101,7 +101,7 @@ public class Controller { } public func afterAction(filter: String, _ options: FilterOptions = nil) -> Void { - afterFilters[filter] = options + afterFilters[filter] = options } } @@ -111,7 +111,7 @@ public func render(template: String) -> Response { } public func render(template: String, _ object: HTMLRenderable?) -> Response { - var body:String + var body: String if let obj = object { body = StencilView(template, obj.renderableAttributes()).render() } else { @@ -121,13 +121,13 @@ public func render(template: String, _ object: HTMLRenderable?) -> Response { } public func render(template: String, _ context: [String: Any]) -> Response { - var body:String + var body: String body = StencilView(template, context).render() return Response(.Ok, contentType: "text/html; charset=utf8", body: body) } public func renderJSON(object: JSONRenderable?) -> Response { - var body:String + var body: String if let obj = object { body = JSONView(obj.renderableJSONAttributes()).render() } else { @@ -137,7 +137,7 @@ public func renderJSON(object: JSONRenderable?) -> Response { } public func renderJSON(context: [String: Any]? = nil) -> Response { - var body:String + var body: String body = JSONView(context).render() return Response(.Ok, contentType: "application/json; charset=utf8", body: body) } @@ -155,4 +155,3 @@ public func respondTo(request: Request, _ responders: [String: () -> Response]) } return Response(.NotAcceptable) } - diff --git a/Sources/Swifton/CookiesMiddleware.swift b/Sources/Swifton/CookiesMiddleware.swift index 6fa216d..233f4b6 100644 --- a/Sources/Swifton/CookiesMiddleware.swift +++ b/Sources/Swifton/CookiesMiddleware.swift @@ -15,5 +15,5 @@ public class CookiesMiddleware: Middleware { response["Set-Cookie"] = response.cookies.map { $0 + "=" + $1 }.joinWithSeparator(";") return response - } + } } diff --git a/Sources/Swifton/Extensions.swift b/Sources/Swifton/Extensions.swift index b13f300..8822e1b 100644 --- a/Sources/Swifton/Extensions.swift +++ b/Sources/Swifton/Extensions.swift @@ -16,39 +16,40 @@ extension String { public func split(separator: Character) -> [String] { return self.characters.split { $0 == separator }.map(String.init) } - + public func split(maxSplit: Int = Int.max, separator: Character) -> [String] { return self.characters.split(maxSplit) { $0 == separator }.map(String.init) } - + public func replace(old: Character, _ new: Character) -> String { var buffer = [Character]() self.characters.forEach { buffer.append($0 == old ? new : $0) } return String(buffer) } - + public func unquote() -> String { - var scalars = self.unicodeScalars; + var scalars = self.unicodeScalars if scalars.first == "\"" && scalars.last == "\"" && scalars.count >= 2 { - scalars.removeFirst(); - scalars.removeLast(); + scalars.removeFirst() + scalars.removeLast() return String(scalars) } return self } - + public func trim() -> String { var scalars = self.unicodeScalars while let _ = scalars.first?.asWhitespace() { scalars.removeFirst() } while let _ = scalars.last?.asWhitespace() { scalars.removeLast() } return String(scalars) } - + public static func fromUInt8(array: [UInt8]) -> String { - // Apple changes the definition of String(data: .... ) every release so let's stay with 'fromUInt8(...)' wrapper. + // Apple changes the definition of String(data: .... ) every release so let's stay + // with 'fromUInt8(...)' wrapper. return array.reduce("", combine: { $0.0 + String(UnicodeScalar($0.1)) }) } - + public func removePercentEncoding() -> String { var scalars = self.unicodeScalars var output = "" @@ -84,7 +85,7 @@ extension String { } extension UnicodeScalar { - + public func asWhitespace() -> UInt8? { if self.value >= 9 && self.value <= 13 { return UInt8(self.value) @@ -94,7 +95,7 @@ extension UnicodeScalar { } return nil } - + public func asAlpha() -> UInt8? { if self.value >= 48 && self.value <= 57 { return UInt8(self.value) - 48 diff --git a/Sources/Swifton/JSON.swift b/Sources/Swifton/JSON.swift index 48a2d0c..598a076 100644 --- a/Sources/Swifton/JSON.swift +++ b/Sources/Swifton/JSON.swift @@ -1,4 +1,4 @@ -// http://stackoverflow.com/questions/35246542/serialize-stringany-to-json +// http://stackoverflow.com/questions/35246542/serialize-stringany-to-json protocol JSONSerializable { func toJSON() -> String? @@ -24,7 +24,7 @@ extension Double : JSONSerializable { extension Array : JSONSerializable { func toJSON() -> String? { - var out : [String] = [] + var out: [String] = [] for element in self { if let json_element = element as? JSONSerializable, let string = json_element.toJSON() { out.append(string) @@ -33,12 +33,12 @@ extension Array : JSONSerializable { } } return "[\(out.joinWithSeparator(", "))]" - } + } } extension Dictionary : JSONSerializable { func toJSON() -> String? { - var out : [String] = [] + var out: [String] = [] for (k, v) in self { if let json_element = v as? JSONSerializable, let string = json_element.toJSON() { out.append("\"\(k)\": \(string)") diff --git a/Sources/Swifton/MemoryModel.swift b/Sources/Swifton/MemoryModel.swift index a81f619..d45fc8d 100644 --- a/Sources/Swifton/MemoryModel.swift +++ b/Sources/Swifton/MemoryModel.swift @@ -1,8 +1,8 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { static var id = 1 - public static var all = [MemoryModel]() + public static var all = [MemoryModel]() public var attributes = [String: Any]() - public var id:Int { + public var id: Int { get { return attributes["id"] as! Int } @@ -12,7 +12,7 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { } required public init(_ attributes: [String: Any]) { - self.attributes = attributes + self.attributes = attributes self.attributes["id"] = self.dynamicType.id self.dynamicType.id += 1 } @@ -24,7 +24,7 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { set(newValue) { attributes[name] = newValue } - } + } public static func create(attributes: [String: String]) -> Self { let resolvedAttributes = self.resolveAttributes(attributes) @@ -40,7 +40,7 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { } public static func find(id: Int?) -> MemoryModel? { - return all.filter{ $0.id == id }.first + return all.filter { $0.id == id }.first } public static func destroy(model: MemoryModel?) { @@ -52,7 +52,7 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { public static func allAttributes() -> Any { var items = [Any]() for model in all { - var attrs = model.attributes + var attrs = model.attributes attrs["id"] = String(model.id) items.append(attrs as Any) } @@ -60,7 +60,7 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { } public static func reset() { - all = [MemoryModel]() + all = [MemoryModel]() id = 1 } @@ -71,9 +71,9 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { static func resolveAttributes(attributes: [String: String]) -> [String: Any] { var attrs = [String: Any]() for (key, value) in attributes { - if let integer:Int = Int(value) { + if let integer: Int = Int(value) { attrs[key] = integer - } else if let double:Double = Double(value) { + } else if let double: Double = Double(value) { attrs[key] = double } else { attrs[key] = value @@ -81,18 +81,17 @@ public class MemoryModel: HTMLRenderable, JSONRenderable, Equatable { } return attrs } - + public func renderableAttributes() -> [String: Any] { - return self.attributes + return self.attributes } public func renderableJSONAttributes() -> [String: Any] { - return self.attributes + return self.attributes } } -public func ==(lhs: MemoryModel, rhs: MemoryModel) -> Bool { +public func == (lhs: MemoryModel, rhs: MemoryModel) -> Bool { return lhs.id == rhs.id } - diff --git a/Sources/Swifton/Middleware.swift b/Sources/Swifton/Middleware.swift index 9408091..4216413 100644 --- a/Sources/Swifton/Middleware.swift +++ b/Sources/Swifton/Middleware.swift @@ -1,5 +1,5 @@ import Inquiline public protocol Middleware { - func call(request: Request, _ closure: Request -> Response) -> Response + func call(request: Request, _ closure: Request -> Response) -> Response } diff --git a/Sources/Swifton/MimeType.swift b/Sources/Swifton/MimeType.swift index 5028496..9b851d3 100644 --- a/Sources/Swifton/MimeType.swift +++ b/Sources/Swifton/MimeType.swift @@ -1,5 +1,6 @@ +// swiftlint:disable:next line_length // https://gist.githubusercontent.com/ngs/918b07f448977789cf69/raw/05bbb2103a41e0ed5d5579b3666d61c0891f91c0/MimeType.swift -internal let DEFAULT_MIME_TYPE = "application/octet-stream" +internal let defaultMimeType = "application/octet-stream" internal let mimeTypes = [ "html": "text/html", @@ -111,7 +112,7 @@ internal func MimeType(ext: String?) -> String { if ext != nil && mimeTypes.contains({ $0.0 == ext!.lowercaseString }) { return mimeTypes[ext!.lowercaseString]! } - return DEFAULT_MIME_TYPE + return defaultMimeType } extension String { diff --git a/Sources/Swifton/ParametersMiddleware.swift b/Sources/Swifton/ParametersMiddleware.swift index a65d8c2..e847dbd 100644 --- a/Sources/Swifton/ParametersMiddleware.swift +++ b/Sources/Swifton/ParametersMiddleware.swift @@ -3,13 +3,13 @@ import Inquiline public class ParametersMiddleware: Middleware { public func call(request: Request, _ closure: Request -> Response) -> Response { var newRequest = request - var queryString:String = "" + var queryString: String = "" if Method(rawValue: request.method) == .GET { let elements = request.path.split(1, separator: "?") if elements.count > 1 { queryString = request.path.split(1, separator: "?").last! } - } else { + } else { queryString = request.body! } @@ -21,7 +21,7 @@ public class ParametersMiddleware: Middleware { } newRequest.method = self.resolveMethod(newRequest) return closure(newRequest) - } + } func resolveMethod(request: Request) -> String { if request.method == "POST" { diff --git a/Sources/Swifton/Router.swift b/Sources/Swifton/Router.swift index d3550c6..7b43f89 100644 --- a/Sources/Swifton/Router.swift +++ b/Sources/Swifton/Router.swift @@ -4,7 +4,7 @@ import Foundation import PathKit import URITemplate -public enum Method:String { +public enum Method: String { case DELETE = "DELETE" case GET = "GET" case HEAD = "HEAD" @@ -19,7 +19,7 @@ public class Router { typealias Route = (URITemplate, Method, Action) var routes = [Route]() - + public init() {} public var notFound: Nest.Application = { request in @@ -74,15 +74,17 @@ public class Router { } public func respond(requestType: RequestType) -> ResponseType { - let request = requestType as? Request ?? Request(method: requestType.method, path: requestType.path, headers: requestType.headers, body: requestType.body) - return ParametersMiddleware().call(request) { - CookiesMiddleware().call($0, self.resolveRoute) + let request = requestType as? Request ?? Request(method: requestType.method, path: requestType.path, + headers: requestType.headers, body: requestType.body) + + return ParametersMiddleware().call(request) { + CookiesMiddleware().call($0, self.resolveRoute) } } public func resolveRoute(request: Request) -> Response { var newRequest = request - + for (template, method, handler) in routes { if newRequest.method == method.rawValue { if let variables = template.extract(newRequest.path) { @@ -109,7 +111,7 @@ public class Router { if filePath.exists { if filePath.isReadable { do { - let contents:NSData? = try filePath.read() + let contents: NSData? = try filePath.read() if let body = String(data:contents!, encoding: NSUTF8StringEncoding) { return Response(.Ok, contentType: "text/plain; charset=utf8", body: body) } @@ -119,9 +121,9 @@ public class Router { } else { return permissionDenied(request) } - } + } } - } + } return nil } } diff --git a/Sources/Swifton/View.swift b/Sources/Swifton/View.swift index 7e3c78f..3e24eb8 100644 --- a/Sources/Swifton/View.swift +++ b/Sources/Swifton/View.swift @@ -2,13 +2,13 @@ import Stencil import PathKit protocol View { - init(_ path: String, _ context: [String: Any]?) - func render() -> String + init(_ path: String, _ context: [String: Any]?) + func render() -> String } struct StencilView { var template: Template? - var context: [String: Any]? + var context: [String: Any]? init(_ path: String, _ context: [String: Any]? = nil) { let defaultTemplateLoader = TemplateLoader(paths: [Path(SwiftonConfig.viewsDirectory)]) @@ -19,9 +19,9 @@ struct StencilView { self.context = ["loader": defaultTemplateLoader] } - var templatePath = path.characters.split{$0 == "/"}.map(String.init) + var templatePath = path.characters.split {$0 == "/"}.map(String.init) let templateName = templatePath.removeLast() - + let appPath = Path(SwiftonConfig.viewsDirectory) let paths = [appPath + templatePath.joinWithSeparator("/")] let templateLoader = TemplateLoader(paths: paths) @@ -36,7 +36,7 @@ struct StencilView { } struct JSONView { - var context: [String: Any]? + var context: [String: Any]? init(_ context: [String: Any]? = nil) { self.context = context @@ -48,5 +48,3 @@ struct JSONView { return json! } } - - diff --git a/Swifton.xcodeproj/project.pbxproj b/Swifton.xcodeproj/project.pbxproj index ff26136..161614a 100644 --- a/Swifton.xcodeproj/project.pbxproj +++ b/Swifton.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ "_____Product_PathKit" /* libPathKit.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libPathKit.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; "_____Product_Stencil" /* libStencil.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libStencil.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; "_____Product_Swifton" /* libSwifton.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libSwifton.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - "_____Product_SwiftonTestSuite" /* SwiftonTestSuite.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; name = SwiftonTestSuite.xctest; path = Swifton.testsuite.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + "_____Product_SwiftonTestSuite" /* Swifton.testsuite.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = Swifton.testsuite.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; "_____Product_URITemplate" /* libURITemplate.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libURITemplate.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -228,7 +228,7 @@ TestProducts_ /* Tests */ = { isa = PBXGroup; children = ( - "_____Product_SwiftonTestSuite" /* SwiftonTestSuite.xctest */, + "_____Product_SwiftonTestSuite" /* Swifton.testsuite.xctest */, ); name = Tests; sourceTree = ""; @@ -416,6 +416,7 @@ buildPhases = ( CompilePhase_PathKit /* Sources */, "___LinkPhase_PathKit" /* Frameworks */, + 1475A8671CA4A5F8004179B5 /* SwiftLint */, ); buildRules = ( ); @@ -483,7 +484,7 @@ ); name = Swifton.testsuite; productName = SwiftonTestSuite; - productReference = "_____Product_SwiftonTestSuite" /* SwiftonTestSuite.xctest */; + productReference = "_____Product_SwiftonTestSuite" /* Swifton.testsuite.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; "______Target_URITemplate" /* URITemplate */ = { @@ -533,6 +534,23 @@ }; /* End PBXProject section */ +/* Begin PBXShellScriptBuildPhase section */ + 1475A8671CA4A5F8004179B5 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ CompilePhase_Inquiline /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Tests/Swifton/MemoryModelTests.swift b/Tests/Swifton/MemoryModelTests.swift index 93f5caa..5f5a722 100644 --- a/Tests/Swifton/MemoryModelTests.swift +++ b/Tests/Swifton/MemoryModelTests.swift @@ -18,7 +18,7 @@ class MemoryModelTests: SwiftonTest { override func doSetUp() { super.doSetUp() - + TestModel.all = [MemoryModel]() record = TestModel.create(["name": "Saulius", "surname": "Grigaitis"]) } diff --git a/Tests/Swifton/SwiftonTest.swift b/Tests/Swifton/SwiftonTest.swift index b40c22c..f1b3d03 100644 --- a/Tests/Swifton/SwiftonTest.swift +++ b/Tests/Swifton/SwiftonTest.swift @@ -23,4 +23,4 @@ class SwiftonTest: XCTestCase { SwiftonConfig.publicDirectory = (Path(#file).parent() + "Fixtures").description } -} \ No newline at end of file +} diff --git a/Tests/Swifton/TestModelsController.swift b/Tests/Swifton/TestModelsController.swift index 84d03f1..60c2dd8 100644 --- a/Tests/Swifton/TestModelsController.swift +++ b/Tests/Swifton/TestModelsController.swift @@ -1,9 +1,9 @@ import Swifton -class TestModelsController: TestApplicationController { - var testModel: TestModel? +class TestModelsController: TestApplicationController { + var testModel: TestModel? - override func controller() { + override func controller() { beforeAction("setTestModel", ["only": ["show", "edit", "update", "destroy"]]) beforeAction("reset", ["only": ["show"]]) @@ -27,7 +27,7 @@ class TestModelsController: TestApplicationController { action("edit") { request in return render("TestModels/Edit", self.testModel) - } + } action("create") { request in TestModel.create(request.params) return redirectTo("/testModels") @@ -44,13 +44,13 @@ class TestModelsController: TestApplicationController { } filter("setTestModel") { request in - guard let t = TestModel.find(request.params["id"]) else { return redirectTo("/testModels") } + guard let t = TestModel.find(request.params["id"]) else { return redirectTo("/testModels") } self.testModel = t as? TestModel return self.next } filter("crash") { request in - let crash:String? = nil + let crash: String? = nil let _ = crash! return self.next } diff --git a/install_swiftlint.sh b/install_swiftlint.sh new file mode 100755 index 0000000..b688d64 --- /dev/null +++ b/install_swiftlint.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Installs the SwiftLint package. +# Tries to get the precompiled .pkg file from Github, but if that +# fails just recompiles from source. + +set -e + +SWIFTLINT_PKG_PATH="/tmp/SwiftLint.pkg" +SWIFTLINT_PKG_URL="https://github.com/realm/SwiftLint/releases/download/0.9.2/SwiftLint.pkg" + +wget --output-document=$SWIFTLINT_PKG_PATH $SWIFTLINT_PKG_URL + +if [ -f $SWIFTLINT_PKG_PATH ]; then + echo "SwiftLint package exists! Installing it..." + sudo installer -pkg $SWIFTLINT_PKG_PATH -target / +else + echo "SwiftLint package doesn't exist. Compiling from source..." && + git clone https://github.com/realm/SwiftLint.git /tmp/SwiftLint && + cd /tmp/SwiftLint && + git submodule update --init --recursive && + sudo make install +fi +