From ed2c2d05335e9aeadae36651217ef5ae53b27a15 Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Wed, 28 Apr 2021 22:48:20 -0700 Subject: [PATCH] Fix issues with explicit_type_interface on Swift 5.4 Fixes #3615 --- CHANGELOG.md | 5 +- .../Idiomatic/ExplicitTypeInterfaceRule.swift | 48 ++++++++++++++++++- .../ExplicitTypeInterfaceRuleTests.swift | 43 ++++++++++++++--- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 325aa4beb8..1b68696f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,9 +27,10 @@ [Marcelo Fabri](https://github.com/marcelofabri) [#3562](https://github.com/realm/SwiftLint/issues/3562) -* Fix `unused_capture_list`, `empty_enum_arguments` and `implicit_return` rules - when using Swift 5.4. +* Fix `unused_capture_list`, `empty_enum_arguments`, `implicit_return` and + `explicit_type_interface` rules when using Swift 5.4. [Marcelo Fabri](https://github.com/marcelofabri) + [#3615](https://github.com/realm/SwiftLint/issues/3615) ## 0.43.1: Laundroformat diff --git a/Source/SwiftLintFramework/Rules/Idiomatic/ExplicitTypeInterfaceRule.swift b/Source/SwiftLintFramework/Rules/Idiomatic/ExplicitTypeInterfaceRule.swift index 95f8adb458..86fed7977d 100644 --- a/Source/SwiftLintFramework/Rules/Idiomatic/ExplicitTypeInterfaceRule.swift +++ b/Source/SwiftLintFramework/Rules/Idiomatic/ExplicitTypeInterfaceRule.swift @@ -68,8 +68,11 @@ public struct ExplicitTypeInterfaceRule: OptInRule, ConfigurationProviderRule { ) public func validate(file: SwiftLintFile) -> [StyleViolation] { - return file.structureDictionary.traverseWithParentDepthFirst { parent, subDict in - guard let kind = subDict.declarationKind else { return nil } + return file.structureDictionary.traverseWithParentsDepthFirst { parents, subDict in + guard let kind = subDict.declarationKind, + let parent = parents.lastIgnoringCallAndArgument() else { + return nil + } return validate(file: file, kind: kind, dictionary: subDict, parentStructure: parent) } } @@ -178,3 +181,44 @@ private extension Collection where Element == ByteRange { return contains { $0.contains(index) } } } + +private extension SourceKittenDictionary { + func traverseWithParentsDepthFirst(traverseBlock: ([SourceKittenDictionary], SourceKittenDictionary) -> [T]?) + -> [T] { + var result: [T] = [] + traverseWithParentDepthFirst(collectingValuesInto: &result, + parents: [], + traverseBlock: traverseBlock) + return result + } + + private func traverseWithParentDepthFirst( + collectingValuesInto array: inout [T], + parents: [SourceKittenDictionary], + traverseBlock: ([SourceKittenDictionary], SourceKittenDictionary) -> [T]?) { + var updatedParents = parents + updatedParents.append(self) + + substructure.forEach { subDict in + subDict.traverseWithParentDepthFirst(collectingValuesInto: &array, + parents: updatedParents, + traverseBlock: traverseBlock) + + if let collectedValues = traverseBlock(updatedParents, subDict) { + array += collectedValues + } + } + } +} + +private extension Array where Element == SourceKittenDictionary { + func lastIgnoringCallAndArgument() -> Element? { + guard SwiftVersion.current >= .fiveDotFour else { + return last + } + + return last { element in + element.expressionKind != .call && element.expressionKind != .argument + } + } +} diff --git a/Tests/SwiftLintFrameworkTests/ExplicitTypeInterfaceRuleTests.swift b/Tests/SwiftLintFrameworkTests/ExplicitTypeInterfaceRuleTests.swift index 681385246f..06ebf55b1d 100644 --- a/Tests/SwiftLintFrameworkTests/ExplicitTypeInterfaceRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/ExplicitTypeInterfaceRuleTests.swift @@ -1,11 +1,40 @@ import SwiftLintFramework import XCTest +// swiftlint:disable:next type_body_length class ExplicitTypeInterfaceRuleTests: XCTestCase { func testExplicitTypeInterface() { verifyRule(ExplicitTypeInterfaceRule.description) } + func testLocalVars() { + let nonTriggeringExamples = [ + Example("func foo() {\nlet intVal: Int = 1\n}"), + Example(""" + func foo() { + bar { + let x: Int = 1 + } + } + """) + ] + let triggeringExamples = [ + Example("func foo() {\n↓let intVal = 1\n}"), + Example(""" + func foo() { + bar { + ↓let x = 1 + } + } + """) + ] + let description = ExplicitTypeInterfaceRule.description + .with(triggeringExamples: triggeringExamples) + .with(nonTriggeringExamples: nonTriggeringExamples) + + verifyRule(description) + } + func testExcludeLocalVars() { let nonTriggeringExamples = ExplicitTypeInterfaceRule.description.nonTriggeringExamples + [ Example("func foo() {\nlet intVal = 1\n}") @@ -160,12 +189,14 @@ class ExplicitTypeInterfaceRuleTests: XCTestCase { 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 + } + 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 } } """),