Skip to content

Commit

Permalink
Fix ExplicitTypeInterfaceRule in groups and statements
Browse files Browse the repository at this point in the history
  • Loading branch information
dirtydanee committed Jan 24, 2019
1 parent 407d9a7 commit f233061
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 20 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

#### Bug Fixes

* None.
* Fix `explicit_type_interface` when used in statements.
[Daniel Metzing](https://github.com/dirtydanee)
[#2154](https://github.com/realm/SwiftLint/issues/2154)

## 0.30.1: Localized Stain Remover

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import SourceKittenFramework

public struct ExplicitTypeInterfaceRule: ASTRule, OptInRule, ConfigurationProviderRule {
public struct ExplicitTypeInterfaceRule: OptInRule, ConfigurationProviderRule {
public var configuration = ExplicitTypeInterfaceConfiguration()

public init() {}
Expand Down Expand Up @@ -67,12 +67,36 @@ public struct ExplicitTypeInterfaceRule: ASTRule, OptInRule, ConfigurationProvid
]
)

public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
public func validate(file: File) -> [StyleViolation] {
return validate(file: file, dictionary: file.structure.dictionary, parentStructure: nil)
}

private func validate(file: File, dictionary: [String: SourceKitRepresentable],
parentStructure: [String: SourceKitRepresentable]?) -> [StyleViolation] {
return dictionary.substructure.flatMap({ subDict -> [StyleViolation] in
var violations = validate(file: file, dictionary: subDict, parentStructure: dictionary)

if let kindString = subDict.kind,
let kind = SwiftDeclarationKind(rawValue: kindString) {
violations += validate(file: file, kind: kind, dictionary: subDict, parentStructure: dictionary)
}

return violations
})
}

private func validate(file: File,
kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable],
parentStructure: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard configuration.allowedKinds.contains(kind),
!containsType(dictionary: dictionary),
(!configuration.allowRedundancy || !assigneeIsInitCall(file: file, dictionary: dictionary)),
let offset = dictionary.offset else {
let offset = dictionary.offset,
!dictionary.containsType,
(!configuration.allowRedundancy || !dictionary.isInitCall(file: file)),
!parentStructure.contains([.forEach, .guard]),
!parentStructure.caseStatementPatternRanges.contains(offset),
!parentStructure.caseExpressionRanges.contains(offset),
!file.captureGroupByteRanges.contains(offset) else {
return []
}

Expand All @@ -82,25 +106,70 @@ public struct ExplicitTypeInterfaceRule: ASTRule, OptInRule, ConfigurationProvid
location: Location(file: file, byteOffset: offset))
]
}
}

private func containsType(dictionary: [String: SourceKitRepresentable]) -> Bool {
return dictionary.typeName != nil
private extension Dictionary where Key == String, Value == SourceKitRepresentable {
var containsType: Bool {
return typeName != nil
}

private func assigneeIsInitCall(file: File, dictionary: [String: SourceKitRepresentable]) -> Bool {
func isInitCall(file: File) -> Bool {
guard
let nameOffset = dictionary.nameOffset,
let nameLength = dictionary.nameLength,
let afterNameRange = file.contents.bridge().byteRangeToNSRange(start: nameOffset + nameLength, length: 0)
else {
return false
let nameOffset = nameOffset,
let nameLength = nameLength,
case let contents = file.contents.bridge(),
let afterNameRange = contents.byteRangeToNSRange(start: nameOffset + nameLength, length: 0)
else {
return false
}

let contentAfterName = file.contents.bridge().substring(from: afterNameRange.location)
let initCallRegex = regex(
"^\\s*=\\s*(?:try[!?]?\\s+)?\\[?\\p{Lu}[^\\(\\s<]*(?:<[^\\>]*>)?(?::\\s*[^\\(\\n]+)?\\]?\\("
)
let contentAfterName = contents.substring(from: afterNameRange.location)
let initCallRegex =
regex("^\\s*=\\s*(?:try[!?]?\\s+)?\\[?\\p{Lu}[^\\(\\s<]*(?:<[^\\>]*>)?(?::\\s*[^\\(\\n]+)?\\]?\\(")

return initCallRegex.firstMatch(in: contentAfterName, options: [], range: contentAfterName.fullNSRange) != nil
}

var caseStatementPatternRanges: [NSRange] {
return ranges(with: StatementKind.case.rawValue, for: "source.lang.swift.structure.elem.pattern")
}

var caseExpressionRanges: [NSRange] {
return ranges(with: "source.lang.swift.expr.tuple", for: "source.lang.swift.structure.elem.expr")
}

func contains(_ statements: Set<StatementKind>) -> Bool {
guard let kind = kind,
let statement = StatementKind(rawValue: kind) else {
return false
}
return statements.contains(statement)
}

func ranges(with parentKind: String, for elementKind: String) -> [NSRange] {
guard parentKind == kind else {
return []
}

return elements
.filter { elementKind == $0.kind }
.compactMap {
guard let location = $0.offset, let length = $0.length else { return nil }
return NSRange(location: location, length: length)
}
}
}

private extension File {
var captureGroupByteRanges: [NSRange] {
return match(pattern: "\\{\\s*\\[(\\s*\\w+\\s+\\w+,*)+\\]",
excludingSyntaxKinds: SyntaxKind.commentKinds)
.compactMap { contents.bridge().NSRangeToByteRange(start: $0.location, length: $0.length) }
}
}

private extension Collection where Element == NSRange {
func contains(_ index: Int) -> Bool {
return contains { $0.contains(index) }
}
}
6 changes: 5 additions & 1 deletion Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,11 @@ extension ExplicitTypeInterfaceRuleTests {
("testExplicitTypeInterface", testExplicitTypeInterface),
("testExcludeLocalVars", testExcludeLocalVars),
("testExcludeClassVars", testExcludeClassVars),
("testAllowRedundancy", testAllowRedundancy)
("testAllowRedundancy", testAllowRedundancy),
("testEmbededInStatements", testEmbededInStatements),
("testCaptureGroup", testCaptureGroup),
("testFastEnumerationDeclaration", testFastEnumerationDeclaration),
("testSwitchCaseDeclarations", testSwitchCaseDeclarations)
]
}

Expand Down
149 changes: 149 additions & 0 deletions Tests/SwiftLintFrameworkTests/ExplicitTypeInterfaceRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,153 @@ class ExplicitTypeInterfaceRuleTests: XCTestCase {

verifyRule(description, ruleConfiguration: ["allow_redundancy": true])
}

func testEmbededInStatements() {
let nonTriggeringExamples = [
"""
func foo() {
var bar: String?
guard let strongBar = bar else {
return
}
}
""",
"""
struct SomeError: Error {}
var error: Error?
switch error {
case let error as SomeError: break
default: break
}
"""
]
let triggeringExamples = ExplicitTypeInterfaceRule.description.triggeringExamples
let description = ExplicitTypeInterfaceRule.description
.with(triggeringExamples: triggeringExamples)
.with(nonTriggeringExamples: nonTriggeringExamples)

verifyRule(description)
}

func testCaptureGroup() {
let nonTriggeringExamples = [
"""
var k: Int = 0
_ = { [weak k] in
print(k)
}
""",
"""
var k: Int = 0
_ = { [unowned k] in
print(k)
}
""",
"""
class Foo {
func bar() {
var k: Int = 0
_ = { [weak self, weak k] in
guard let strongSelf = self else { return }
}
}
}
"""
]
let triggeringExamples = ExplicitTypeInterfaceRule.description.triggeringExamples
let description = ExplicitTypeInterfaceRule.description
.with(triggeringExamples: triggeringExamples)
.with(nonTriggeringExamples: nonTriggeringExamples)

verifyRule(description)
}

func testFastEnumerationDeclaration() {
let nonTriggeringExaples = [
"""
func foo() {
let elements: [Int] = [1, 2]
for element in elements {}
}
""",
"""
func foo() {
let elements: [Int] = [1, 2]
for (index, element) in elements.enumerated() {}
}
"""
]

let triggeringExamples = ExplicitTypeInterfaceRule.description.triggeringExamples
let description = ExplicitTypeInterfaceRule.description
.with(triggeringExamples: triggeringExamples)
.with(nonTriggeringExamples: nonTriggeringExaples)
verifyRule(description)
}

//swiftlint:disable function_body_length
func testSwitchCaseDeclarations() {
guard SwiftVersion.current >= .fourDotOne else {
return
}

let nonTriggeringExamples = [
"""
enum Foo {
case failure(Any)
case success(Any)
}
func bar() {
let foo: Foo = .success(1)
switch foo {
case .failure(let error): let bar: Int = 1
case .success(let result): let bar: Int = 2
}
}
""",
"""
enum Foo {
case failure(Any, Any)
}
func foo() {
switch foo {
case var (x, y): break
}
}
"""
]

let triggeringExamples = [
"""
enum Foo {
case failure(Any)
case success(Any)
}
func bar() {
let foo: Foo = .success(1)
switch foo {
case .failure(let error): ↓let fooBar = 1
case .success(let result): ↓let fooBar = 1
}
}
""",
"""
enum Foo {
case failure(Any, Any)
}
func foo() {
let foo: Foo = .failure(1, 1)
switch foo {
case var .failure(x, y): ↓let fooBar = 1
default: ↓let fooBar = 1
}
}
"""
]

let description = ExplicitTypeInterfaceRule.description
.with(triggeringExamples: triggeringExamples)
.with(nonTriggeringExamples: nonTriggeringExamples)
verifyRule(description)
}
}

0 comments on commit f233061

Please sign in to comment.