Skip to content

Commit

Permalink
Colon rule now catches violations on generic type declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelofabri authored and sjavora committed Mar 9, 2019
1 parent 97aa820 commit 09a9475
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 180 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

#### Bug Fixes

* None.
* `colon` rule now catches violations when declaring generic types with
inheritance or protocol conformance.
[Marcelo Fabri](https://github.com/marcelofabri)
[#2628](https://github.com/realm/SwiftLint/issues/2628)

## 0.31.0: Busy Laundromat

Expand Down
35 changes: 35 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,21 @@ class Foo: Bar {}

```

```swift
class Foo<T>: Bar {}

```

```swift
class Foo<T: Equatable>: Bar {}

```

```swift
class Foo<T, U>: Bar {}

```

```swift
class Foo<T: Equatable> {}

Expand Down Expand Up @@ -2230,6 +2245,26 @@ class ↓Foo:Bar {}

```

```swift
class ↓Foo<T> : Bar {}

```

```swift
class ↓Foo<T>:Bar {}

```

```swift
class ↓Foo<T, U>:Bar {}

```

```swift
class ↓Foo<T: Equatable>:Bar {}

```

```swift
class Foo<↓T:Equatable> {}

Expand Down
87 changes: 48 additions & 39 deletions Source/SwiftLintFramework/Rules/Style/ColonRule+Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,63 @@ internal extension ColonRule {
// If flexible_right_spacing is false or omitted, match 0 or 2+ whitespaces.
let spacingRegex = configuration.flexibleRightSpacing ? "(?:\\s{0})" : "(?:\\s{0}|\\s{2,})"

return "(\\w)" + // Capture an identifier
"(?:" + // start group
"\\s+" + // followed by whitespace
":" + // to the left of a colon
"\\s*" + // followed by any amount of whitespace.
"|" + // or
":" + // immediately followed by a colon
spacingRegex + // followed by right spacing regex
")" + // end group
"(" + // Capture a type identifier
"[\\[|\\(]*" + // which may begin with a series of nested parenthesis or brackets
"\\S)" // lazily to the first non-whitespace character.
return "(\\w)" + // Capture an identifier.
"(<[\\w\\s:\\.,]+>)?" + // Capture a generic parameter clause (optional).
"(?:" + // Start group
"\\s+" + // followed by whitespace
":" + // to the left of a colon
"\\s*" + // followed by any amount of whitespace.
"|" + // or
":" + // immediately followed by a colon
spacingRegex + // followed by right spacing regex
")" + // end group
"(" + // Capture a type identifier
"[\\[|\\(]*" + // which may begin with a series of nested parenthesis or brackets
"\\S)" // lazily to the first non-whitespace character.
}

func typeColonViolationRanges(in file: File, matching pattern: String) -> [NSRange] {
let nsstring = file.contents.bridge()
let commentAndStringKindsSet = SyntaxKind.commentAndStringKinds
return file.rangesAndTokens(matching: pattern).filter { _, syntaxTokens in
let syntaxKinds = syntaxTokens.kinds

guard syntaxKinds.count == 2 else {
return false
}

let validKinds: Bool
switch (syntaxKinds[0], syntaxKinds[1]) {
case (.identifier, .typeidentifier),
(.typeidentifier, .typeidentifier):
validKinds = true
case (.identifier, .keyword),
(.typeidentifier, .keyword):
validKinds = file.isTypeLike(token: syntaxTokens[1])
case (.keyword, .typeidentifier):
validKinds = file.isTypeLike(token: syntaxTokens[0])
default:
validKinds = false
return file.matchesAndTokens(matching: pattern).filter { match, syntaxTokens in
if match.range(at: 2).length > 0 && syntaxTokens.count > 2 { // captured a generic definition
let tokens = [syntaxTokens.first, syntaxTokens.last].compactMap { $0 }
return isValidMatch(syntaxTokens: tokens, file: file)
}

guard validKinds else {
return false
}

return Set(syntaxKinds).isDisjoint(with: commentAndStringKindsSet)
}.compactMap { range, syntaxTokens in
return isValidMatch(syntaxTokens: syntaxTokens, file: file)
}.compactMap { match, syntaxTokens in
let identifierRange = nsstring
.byteRangeToNSRange(start: syntaxTokens[0].offset, length: 0)
return identifierRange.map { NSUnionRange($0, range) }
return identifierRange.map { NSUnionRange($0, match.range) }
}
}

private func isValidMatch(syntaxTokens: [SyntaxToken], file: File) -> Bool {
let syntaxKinds = syntaxTokens.kinds

guard syntaxKinds.count == 2 else {
return false
}

let validKinds: Bool
switch (syntaxKinds[0], syntaxKinds[1]) {
case (.identifier, .typeidentifier),
(.typeidentifier, .typeidentifier):
validKinds = true
case (.identifier, .keyword),
(.typeidentifier, .keyword):
validKinds = file.isTypeLike(token: syntaxTokens[1])
case (.keyword, .typeidentifier):
validKinds = file.isTypeLike(token: syntaxTokens[0])
default:
validKinds = false
}

guard validKinds else {
return false
}

return Set(syntaxKinds).isDisjoint(with: SyntaxKind.commentAndStringKinds)
}
}

Expand Down
144 changes: 4 additions & 140 deletions Source/SwiftLintFramework/Rules/Style/ColonRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,145 +18,9 @@ public struct ColonRule: CorrectableRule, ConfigurationProviderRule {
description: "Colons should be next to the identifier when specifying a type " +
"and next to the key in dictionary literals.",
kind: .style,
nonTriggeringExamples: [
"let abc: Void\n",
"let abc: [Void: Void]\n",
"let abc: (Void, Void)\n",
"let abc: ([Void], String, Int)\n",
"let abc: [([Void], String, Int)]\n",
"let abc: String=\"def\"\n",
"let abc: Int=0\n",
"let abc: Enum=Enum.Value\n",
"func abc(def: Void) {}\n",
"func abc(def: Void, ghi: Void) {}\n",
"let abc: String = \"abc:\"",
"let abc = [Void: Void]()\n",
"let abc = [1: [3: 2], 3: 4]\n",
"let abc = [\"string\": \"string\"]\n",
"let abc = [\"string:string\": \"string\"]\n",
"let abc: [String: Int]\n",
"func foo(bar: [String: Int]) {}\n",
"func foo() -> [String: Int] { return [:] }\n",
"let abc: Any\n",
"let abc: [Any: Int]\n",
"let abc: [String: Any]\n",
"class Foo: Bar {}\n",
"class Foo<T: Equatable> {}\n",
"switch foo {\n" +
"case .bar:\n" +
" _ = something()\n" +
"}\n",
"object.method(x: 5, y: \"string\")\n",
"object.method(x: 5, y:\n" +
" \"string\")",
"object.method(5, y: \"string\")\n",
"func abc() { def(ghi: jkl) }",
"func abc(def: Void) { ghi(jkl: mno) }",
"class ABC { let def = ghi(jkl: mno) } }",
"func foo() { let dict = [1: 1] }"
],
triggeringExamples: [
"let ↓abc:Void\n",
"let ↓abc: Void\n",
"let ↓abc :Void\n",
"let ↓abc : Void\n",
"let ↓abc : [Void: Void]\n",
"let ↓abc : (Void, String, Int)\n",
"let ↓abc : ([Void], String, Int)\n",
"let ↓abc : [([Void], String, Int)]\n",
"let ↓abc: (Void, String, Int)\n",
"let ↓abc: ([Void], String, Int)\n",
"let ↓abc: [([Void], String, Int)]\n",
"let ↓abc :String=\"def\"\n",
"let ↓abc :Int=0\n",
"let ↓abc :Int = 0\n",
"let ↓abc:Int=0\n",
"let ↓abc:Int = 0\n",
"let ↓abc:Enum=Enum.Value\n",
"func abc(↓def:Void) {}\n",
"func abc(↓def: Void) {}\n",
"func abc(↓def :Void) {}\n",
"func abc(↓def : Void) {}\n",
"func abc(def: Void, ↓ghi :Void) {}\n",
"let abc = [Void↓:Void]()\n",
"let abc = [Void↓ : Void]()\n",
"let abc = [Void↓: Void]()\n",
"let abc = [Void↓ : Void]()\n",
"let abc = [1: [3↓ : 2], 3: 4]\n",
"let abc = [1: [3↓ : 2], 3↓: 4]\n",
"let abc: [↓String : Int]\n",
"let abc: [↓String:Int]\n",
"func foo(bar: [↓String : Int]) {}\n",
"func foo(bar: [↓String:Int]) {}\n",
"func foo() -> [↓String : Int] { return [:] }\n",
"func foo() -> [↓String:Int] { return [:] }\n",
"let ↓abc : Any\n",
"let abc: [↓Any : Int]\n",
"let abc: [↓String : Any]\n",
"class ↓Foo : Bar {}\n",
"class ↓Foo:Bar {}\n",
"class Foo<↓T:Equatable> {}\n",
"class Foo<↓T : Equatable> {}\n",
"object.method(x: 5, y↓ : \"string\")\n",
"object.method(x↓:5, y: \"string\")\n",
"object.method(x↓: 5, y: \"string\")\n",
"func abc() { def(ghi↓:jkl) }",
"func abc(def: Void) { ghi(jkl↓:mno) }",
"class ABC { let def = ghi(jkl↓:mno) } }",
"func foo() { let dict = [1↓ : 1] }"
],
corrections: [
"let ↓abc:Void\n": "let abc: Void\n",
"let ↓abc: Void\n": "let abc: Void\n",
"let ↓abc :Void\n": "let abc: Void\n",
"let ↓abc : Void\n": "let abc: Void\n",
"let ↓abc : [Void: Void]\n": "let abc: [Void: Void]\n",
"let ↓abc : (Void, String, Int)\n": "let abc: (Void, String, Int)\n",
"let ↓abc : ([Void], String, Int)\n": "let abc: ([Void], String, Int)\n",
"let ↓abc : [([Void], String, Int)]\n": "let abc: [([Void], String, Int)]\n",
"let ↓abc: (Void, String, Int)\n": "let abc: (Void, String, Int)\n",
"let ↓abc: ([Void], String, Int)\n": "let abc: ([Void], String, Int)\n",
"let ↓abc: [([Void], String, Int)]\n": "let abc: [([Void], String, Int)]\n",
"let ↓abc :String=\"def\"\n": "let abc: String=\"def\"\n",
"let ↓abc :Int=0\n": "let abc: Int=0\n",
"let ↓abc :Int = 0\n": "let abc: Int = 0\n",
"let ↓abc:Int=0\n": "let abc: Int=0\n",
"let ↓abc:Int = 0\n": "let abc: Int = 0\n",
"let ↓abc:Enum=Enum.Value\n": "let abc: Enum=Enum.Value\n",
"func abc(↓def:Void) {}\n": "func abc(def: Void) {}\n",
"func abc(↓def: Void) {}\n": "func abc(def: Void) {}\n",
"func abc(↓def :Void) {}\n": "func abc(def: Void) {}\n",
"func abc(↓def : Void) {}\n": "func abc(def: Void) {}\n",
"func abc(def: Void, ↓ghi :Void) {}\n": "func abc(def: Void, ghi: Void) {}\n",
"let abc = [Void↓:Void]()\n": "let abc = [Void: Void]()\n",
"let abc = [Void↓ : Void]()\n": "let abc = [Void: Void]()\n",
"let abc = [Void↓: Void]()\n": "let abc = [Void: Void]()\n",
"let abc = [Void↓ : Void]()\n": "let abc = [Void: Void]()\n",
"let abc = [1: [3↓ : 2], 3: 4]\n": "let abc = [1: [3: 2], 3: 4]\n",
"let abc = [1: [3↓ : 2], 3↓: 4]\n": "let abc = [1: [3: 2], 3: 4]\n",
"let abc: [↓String : Int]\n": "let abc: [String: Int]\n",
"let abc: [↓String:Int]\n": "let abc: [String: Int]\n",
"func foo(bar: [↓String : Int]) {}\n": "func foo(bar: [String: Int]) {}\n",
"func foo(bar: [↓String:Int]) {}\n": "func foo(bar: [String: Int]) {}\n",
"func foo() -> [↓String : Int] { return [:] }\n": "func foo() -> [String: Int] { return [:] }\n",
"func foo() -> [↓String:Int] { return [:] }\n": "func foo() -> [String: Int] { return [:] }\n",
"let ↓abc : Any\n": "let abc: Any\n",
"let abc: [↓Any : Int]\n": "let abc: [Any: Int]\n",
"let abc: [↓String : Any]\n": "let abc: [String: Any]\n",
"class ↓Foo : Bar {}\n": "class Foo: Bar {}\n",
"class ↓Foo:Bar {}\n": "class Foo: Bar {}\n",
"class Foo<↓T:Equatable> {}\n": "class Foo<T: Equatable> {}\n",
"class Foo<↓T : Equatable> {}\n": "class Foo<T: Equatable> {}\n",
"object.method(x: 5, y↓ : \"string\")\n": "object.method(x: 5, y: \"string\")\n",
"object.method(x↓:5, y: \"string\")\n": "object.method(x: 5, y: \"string\")\n",
"object.method(x↓: 5, y: \"string\")\n": "object.method(x: 5, y: \"string\")\n",
"func abc() { def(ghi↓:jkl) }": "func abc() { def(ghi: jkl) }",
"func abc(def: Void) { ghi(jkl↓:mno) }": "func abc(def: Void) { ghi(jkl: mno) }",
"class ABC { let def = ghi(jkl↓:mno) } }": "class ABC { let def = ghi(jkl: mno) } }",
"func foo() { let dict = [1↓ : 1] }": "func foo() { let dict = [1: 1] }",
"class Foo {\n #if false\n #else\n let bar = [\"key\"↓ : \"value\"]\n #endif\n}":
"class Foo {\n #if false\n #else\n let bar = [\"key\": \"value\"]\n #endif\n}"
]
nonTriggeringExamples: ColonRuleExamples.nonTriggeringExamples,
triggeringExamples: ColonRuleExamples.triggeringExamples,
corrections: ColonRuleExamples.corrections
)

public func validate(file: File) -> [StyleViolation] {
Expand Down Expand Up @@ -193,7 +57,7 @@ public struct ColonRule: CorrectableRule, ConfigurationProviderRule {
contents = regularExpression.stringByReplacingMatches(in: contents,
options: [],
range: range,
withTemplate: "$1: $2")
withTemplate: "$1$2: $3")
case .dictionary, .functionCall:
contents = contents.bridge().replacingCharacters(in: range, with: ": ")
}
Expand Down
Loading

0 comments on commit 09a9475

Please sign in to comment.