Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addded TrailingCommaRule #886

Merged
merged 5 commits into from
Nov 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
* `closure_spacing` rule now accepts empty bodies with a space.
[Marcelo Fabri](https://github.com/marcelofabri)
[#875](https://github.com/realm/SwiftLint/issues/875)

* Add `TrailingCommaRule` to enforce/forbid trailing commas in arrays and
dictionaries. The default is to forbid them, but this can be changed with
the `mandatory_comma` configuration.
[Marcelo Fabri](https://github.com/marcelofabri)
[#883](https://github.com/realm/SwiftLint/issues/883)

##### Bug Fixes

Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public let masterRuleList = RuleList(rules:
SwitchCaseOnNewlineRule.self,
SyntacticSugarRule.self,
TodoRule.self,
TrailingCommaRule.self,
TrailingNewlineRule.self,
TrailingSemicolonRule.self,
TrailingWhitespaceRule.self,
Expand Down
6 changes: 3 additions & 3 deletions Source/SwiftLintFramework/Rules/CommaRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ public struct CommaRule: CorrectableRule, ConfigurationProviderRule {
"abc(a: \"string\", b: \"string\"",
"enum a { case a, b, c }",
"func abc(\n a: String, // comment\n bcd: String // comment\n) {\n}\n",
"func abc(\n a: String,\n bcd: String\n) {\n}\n",
"func abc(\n a: String,\n bcd: String\n) {\n}\n"
],
triggeringExamples: [
"func abc(a: String↓ ,b: String) { }",
"func abc(a: String↓ ,b: String↓ ,c: String↓ ,d: String) { }",
"abc(a: \"string\"↓,b: \"string\"",
"enum a { case a↓ ,b }",
"let result = plus(\n first: 3↓ , // #683\n second: 4\n)\n",
"let result = plus(\n first: 3↓ , // #683\n second: 4\n)\n"
],
corrections: [
"func abc(a: String,b: String) {}\n": "func abc(a: String, b: String) {}\n",
"abc(a: \"string\",b: \"string\"\n": "abc(a: \"string\", b: \"string\"\n",
"abc(a: \"string\" , b: \"string\"\n": "abc(a: \"string\", b: \"string\"\n",
"enum a { case a ,b }\n": "enum a { case a, b }\n",
"let a = [1,1]\nlet b = 1\nf(1, b)\n": "let a = [1, 1]\nlet b = 1\nf(1, b)\n",
"let a = [1↓,1↓,1↓,1]\n": "let a = [1, 1, 1, 1]\n",
"let a = [1↓,1↓,1↓,1]\n": "let a = [1, 1, 1, 1]\n"
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct CyclomaticComplexityRule: ASTRule, ConfigurationProviderRule {
"if true {}; if true {}; if true {}; if true {}; if true {}; if true {}\n" +
"func f2() {\n" +
"if true {}; if true {}; if true {}; if true {}; if true {}\n" +
"}}",
"}}"
],
triggeringExamples: [
"func f1() {\n if true {\n if true {\n if false {}\n }\n" +
Expand Down
6 changes: 3 additions & 3 deletions Source/SwiftLintFramework/Rules/ExplicitInitRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public struct ExplicitInitRule: ASTRule, ConfigurationProviderRule, CorrectableR
"struct S { let n: Int }; extension S { init() { self.init(n: 1) } }", // self
"[1].flatMap(String.init)", // pass init as closure
"[String.self].map { $0.init(1) }", // initialize from a metatype value
"[String.self].map { type in type.init(1) }", // initialize from a metatype value
"[String.self].map { type in type.init(1) }" // initialize from a metatype value
],
triggeringExamples: [
"[1].flatMap{String↓.init($0)}",
"[String.self].map { Type in Type↓.init(1) }", // starting with capital assumes as type
"[String.self].map { Type in Type↓.init(1) }" // starting with capital assumes as type
],
corrections: [
"[1].flatMap{String.init($0)}" : "[1].flatMap{String($0)}",
"[1].flatMap{String.init($0)}" : "[1].flatMap{String($0)}"
]
)

Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct ForceUnwrappingRule: OptInRule, ConfigurationProviderRule {
"let unwrapped = optional↓!",
"return cell↓!",
"let url = NSURL(string: \"http://www.google.com\")↓!",
"let dict = [\"Boooo\": \"👻\"]func bla() -> String { return dict[\"Boooo\"]↓! }",
"let dict = [\"Boooo\": \"👻\"]func bla() -> String { return dict[\"Boooo\"]↓! }"
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct FunctionParameterCountRule: ASTRule, ConfigurationProviderRule {
],
triggeringExamples: [
"func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}",
"func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int = 2, g: Int) {}",
"func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int = 2, g: Int) {}"
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct LegacyCGGeometryFunctionsRule: CorrectableRule, ConfigurationProvi
// "rect.divide(atDistance: 10.2, fromEdge: edge)", No correction available for divide
"rect1.contains(rect2)",
"rect.contains(point)",
"rect1.intersects(rect2)",
"rect1.intersects(rect2)"
],
triggeringExamples: [
"↓CGRectGetWidth(rect)",
Expand Down
8 changes: 4 additions & 4 deletions Source/SwiftLintFramework/Rules/LegacyConstructorRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct LegacyConstructorRule: CorrectableRule, ConfigurationProviderRule
"UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 10)",
"UIEdgeInsets(top: aTop, left: aLeft, bottom: aBottom, right: aRight)",
"NSEdgeInsets(top: 0, left: 0, bottom: 10, right: 10)",
"NSEdgeInsets(top: aTop, left: aLeft, bottom: aBottom, right: aRight)",
"NSEdgeInsets(top: aTop, left: aLeft, bottom: aBottom, right: aRight)"
],
triggeringExamples: [
"↓CGPointMake(10, 10)",
Expand All @@ -61,7 +61,7 @@ public struct LegacyConstructorRule: CorrectableRule, ConfigurationProviderRule
"↓UIEdgeInsetsMake(0, 0, 10, 10)",
"↓UIEdgeInsetsMake(top, left, bottom, right)",
"↓NSEdgeInsetsMake(0, 0, 10, 10)",
"↓NSEdgeInsetsMake(top, left, bottom, right)",
"↓NSEdgeInsetsMake(top, left, bottom, right)"
],
corrections: [
"↓CGPointMake(10, 10 )\n": "CGPoint(x: 10, y: 10)\n",
Expand Down Expand Up @@ -93,7 +93,7 @@ public struct LegacyConstructorRule: CorrectableRule, ConfigurationProviderRule
"↓NSEdgeInsetsMake(0, 0, 10, 10)\n":
"NSEdgeInsets(top: 0, left: 0, bottom: 10, right: 10)\n",
"↓NSEdgeInsetsMake(top, left, bottom, right)\n":
"NSEdgeInsets(top: top, left: left, bottom: bottom, right: right)\n",
"NSEdgeInsets(top: top, left: left, bottom: bottom, right: right)\n"
]
)

Expand Down Expand Up @@ -128,7 +128,7 @@ public struct LegacyConstructorRule: CorrectableRule, ConfigurationProviderRule
"UIEdgeInsetsMake\\(\\s*\(twoVarsOrNum)\\s*,\\s*\(twoVarsOrNum)\\s*\\)":
"UIEdgeInsets(top: $1, left: $2, bottom: $3, right: $4)",
"NSEdgeInsetsMake\\(\\s*\(twoVarsOrNum)\\s*,\\s*\(twoVarsOrNum)\\s*\\)":
"NSEdgeInsets(top: $1, left: $2, bottom: $3, right: $4)",
"NSEdgeInsets(top: $1, left: $2, bottom: $3, right: $4)"
]

let description = self.dynamicType.description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public struct LegacyNSGeometryFunctionsRule: CorrectableRule, ConfigurationProvi
// "rect.divide(atDistance: 10.2, fromEdge: edge)", No correction available for divide
"rect1.contains(rect2)",
"rect.contains(point)",
"rect1.intersects(rect2)",
"rect1.intersects(rect2)"
],
triggeringExamples: [
"↓NSWidth(rect)",
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/MarkRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct MarkRule: ConfigurationProviderRule {
"//MARK: - bad",
"//MARK:- bad",
"//MARK: -bad",
"//MARK:-bad",
"//MARK:-bad"
]
)

Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/NimbleOperatorRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule {
"↓expect(10).to(beLessThan(11))\n",
"↓expect(10).to(beLessThanOrEqualTo(10))\n",
"↓expect(x).to(beIdenticalTo(x))\n",
"expect(10) > 2\n ↓expect(10).to(beGreaterThan(2))\n",
"expect(10) > 2\n ↓expect(10).to(beGreaterThan(2))\n"
]
)

Expand Down
4 changes: 2 additions & 2 deletions Source/SwiftLintFramework/Rules/PrivateOutletRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public struct PrivateOutletRule: ASTRule, OptInRule, ConfigurationProviderRule {
"class Foo {\n @IBOutlet private var label: UILabel!\n}\n",
"class Foo {\n var notAnOutlet: UILabel\n}\n",
"class Foo {\n @IBOutlet weak private var label: UILabel?\n}\n",
"class Foo {\n @IBOutlet private weak var label: UILabel?\n}\n",
"class Foo {\n @IBOutlet private weak var label: UILabel?\n}\n"
],
triggeringExamples: [
"class Foo {\n @IBOutlet var label: UILabel?\n}\n",
"class Foo {\n @IBOutlet var label: UILabel!\n}\n",
"class Foo {\n @IBOutlet var label: UILabel!\n}\n"
]
)

Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/PrivateUnitTestRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public struct PrivateUnitTestRule: ASTRule, ConfigurationProviderRule {
"internal func test2() {}\n " +
"public func test3() {}\n " +
"private ↓func test4() {}\n " +
"}",
"}"
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct ReturnArrowWhitespaceRule: CorrectableRule, ConfigurationProviderR
"func abc()\n -> Int {}\n": "func abc()\n -> Int {}\n",
"func abc()\n-> Int {}\n": "func abc()\n-> Int {}\n",
"func abc() ->\n Int {}\n": "func abc() ->\n Int {}\n",
"func abc() ->\nInt {}\n": "func abc() ->\nInt {}\n",
"func abc() ->\nInt {}\n": "func abc() ->\nInt {}\n"
]
)

Expand Down Expand Up @@ -101,7 +101,7 @@ public struct ReturnArrowWhitespaceRule: CorrectableRule, ConfigurationProviderR
"(\(incorrectSpace)\\->\(space)*)",
"(\(space)\\->\(incorrectSpace))",
"\\n\(space)*\\->\(incorrectSpace)",
"\(incorrectSpace)\\->\\n\(space)*",
"\(incorrectSpace)\\->\\n\(space)*"
]

// ex: `func abc()-> Int {` & `func abc() ->Int {`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// TrailingCommaConfiguration.swift
// SwiftLint
//
// Created by Marcelo Fabri on 25/11/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation

public struct TrailingCommaConfiguration: RuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration(.Warning)
private(set) var mandatoryComma: Bool

public var consoleDescription: String {
return severityConfiguration.consoleDescription + ", mandatory_comma: \(mandatoryComma)"
}

public init(mandatoryComma: Bool = false) {
self.mandatoryComma = mandatoryComma
}

public mutating func applyConfiguration(configuration: AnyObject) throws {
guard let configuration = configuration as? [String: AnyObject] else {
throw ConfigurationError.UnknownConfiguration
}

mandatoryComma = (configuration["mandatory_comma"] as? Bool == true)

if let severityString = configuration["severity"] as? String {
try severityConfiguration.applyConfiguration(severityString)
}
}
}

public func == (lhs: TrailingCommaConfiguration,
rhs: TrailingCommaConfiguration) -> Bool {
return lhs.mandatoryComma == rhs.mandatoryComma
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public struct StatementPositionRule: CorrectableRule, ConfigurationProviderRule
"\n\n }\n catch {",
"\"}\nelse{\"",
"struct A { let catchphrase: Int }\nlet a = A(\n catchphrase: 0\n)",
"struct A { let `catch`: Int }\nlet a = A(\n `catch`: 0\n)",
"struct A { let `catch`: Int }\nlet a = A(\n `catch`: 0\n)"
],
triggeringExamples: [
"↓ }else if {",
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/SyntacticSugarRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct SyntacticSugarRule: Rule, ConfigurationProviderRule {
"let x: ↓ImplicitlyUnwrappedOptional<Int>",
"func x(a: ↓Array<Int>, b: Int) -> [Int: Any]",
"func x(a: [Int], b: Int) -> ↓Dictionary<Int, String>",
"func x(a: ↓Array<Int>, b: Int) -> ↓Dictionary<Int, String>",
"func x(a: ↓Array<Int>, b: Int) -> ↓Dictionary<Int, String>"
]
)

Expand Down
111 changes: 111 additions & 0 deletions Source/SwiftLintFramework/Rules/TrailingCommaRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// TrailingCommaRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 21/11/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct TrailingCommaRule: ASTRule, ConfigurationProviderRule {
public var configuration = TrailingCommaConfiguration()

public init() {}

public static let description = RuleDescription(
identifier: "trailing_comma",
name: "Trailing Comma",
description: "Trailing commas in arrays and dictionaries should be avoided/enforced.",
nonTriggeringExamples: [
"let foo = [1, 2, 3]\n",
"let foo = []\n",
"let foo = [:]\n",
"let foo = [1: 2, 2: 3]\n"
],
triggeringExamples: [
"let foo = [1, 2, 3↓,]\n",
"let foo = [1, 2, 3↓, ]\n",
"let foo = [1, 2, 3 ↓,]\n",
"let foo = [1: 2, 2: 3↓, ]\n",
"struct Bar {\n let foo = [1: 2, 2: 3↓, ]\n}\n",
"let foo = [1, 2, 3↓,] + [4, 5, 6↓,]\n"
]
)

public func validateFile(file: File,
kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {

let allowedKinds: [SwiftExpressionKind] = [.Array, .Dictionary]

guard let bodyOffset = (dictionary["key.bodyoffset"] as? Int64).flatMap({ Int($0) }),
bodyLength = (dictionary["key.bodylength"] as? Int64).flatMap({ Int($0) }),
elements = dictionary["key.elements"] as? [SourceKitRepresentable]
where allowedKinds.contains(kind) else {
return []
}

let endPositions = elements.flatMap { element -> Int? in
guard let dictionary = element as? [String: SourceKitRepresentable],
offset = (dictionary["key.offset"] as? Int64).flatMap({ Int($0) }),
length = (dictionary["key.length"] as? Int64).flatMap({ Int($0) }) else {
return nil
}

return offset + length
}

guard let lastPosition = endPositions.maxElement() else {
return []
}

let length = bodyLength + bodyOffset - lastPosition
let contentsAfterLastElement = file.contents
.substringWithByteRange(start: lastPosition, length: length) ?? ""

// if a trailing comma is not present
guard let commaIndex = contentsAfterLastElement.lastIndexOf(",") else {
guard configuration.mandatoryComma else {
return []
}

return violations(file, byteOffset: lastPosition)
}

// trailing comma is present, which is a violation if mandatoryComma is false
guard !configuration.mandatoryComma else {
return []
}

let violationOffset = lastPosition + commaIndex
return violations(file, byteOffset: violationOffset)
}

private func violations(file: File, byteOffset: Int) -> [StyleViolation] {
return [
StyleViolation(ruleDescription: self.dynamicType.description,
severity: configuration.severityConfiguration.severity,
location: Location(file: file, byteOffset: byteOffset)
)
]
}
}

public enum SwiftExpressionKind: String {
case Array = "source.lang.swift.expr.array"
case Dictionary = "source.lang.swift.expr.dictionary"
case Other

public init?(rawValue: String) {
switch rawValue {
case Array.rawValue:
self = .Array
case Dictionary.rawValue:
self = .Dictionary
default:
self = .Other
}
}
}
4 changes: 2 additions & 2 deletions Source/SwiftLintFramework/Rules/ValidDocsRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public struct ValidDocsRule: ConfigurationProviderRule {
"/// docs\n/// - throws: NSError\n/// - returns: false" +
"\nfunc a() throws -> Bool { return true }",
"/// docs\n/// - parameter param: this is a closure\n/// - returns: Bool" +
"\nfunc a(param: (Void throws -> Bool)) -> Bool { return true }",
"\nfunc a(param: (Void throws -> Bool)) -> Bool { return true }"
],
triggeringExamples: [
"/// docs\npublic ↓func a(param: Void) {}\n",
Expand Down Expand Up @@ -223,7 +223,7 @@ public struct ValidDocsRule: ConfigurationProviderRule {
"\nfunc a(param: () -> Void) -> Foo<Void> {return Foo<Void>}",
"/// docs\n/// - parameter param: this is a void closure" +
"\nfunc a(param: () -> Void) -> Foo<[Int]> {return Foo<[Int]>}",
"/// docs\nfunc a() throws -> Bool { return true }",
"/// docs\nfunc a() throws -> Bool { return true }"
]
)

Expand Down