Compare commits
1 Commits
main
...
mf-keypath
Author | SHA1 | Date |
---|---|---|
![]() |
ae9aecffd6 |
|
@ -77,6 +77,7 @@ public let masterRuleList = RuleList(rules: [
|
|||
InertDeferRule.self,
|
||||
IsDisjointRule.self,
|
||||
JoinedDefaultParameterRule.self,
|
||||
KeyPathExpressionAsFunctionRule.self,
|
||||
LargeTupleRule.self,
|
||||
LastWhereRule.self,
|
||||
LeadingWhitespaceRule.self,
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import Foundation
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct KeyPathExpressionAsFunctionRule: ASTRule, ConfigurationProviderRule, AutomaticTestableRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "key_path_expression_as_function",
|
||||
name: "Key Path Expression as Function",
|
||||
description: "Discouraged explicit usage of the default separator.",
|
||||
kind: .idiomatic,
|
||||
minSwiftVersion: .fiveDotTwo,
|
||||
nonTriggeringExamples: [
|
||||
Example("let emails = users.map(\\.email)"),
|
||||
Example("let admins = users.filter(\\.isAdmin)"),
|
||||
Example("let ones = users.filter { _ in 1 }"),
|
||||
Example("let emails = users.map { $0.email() }"),
|
||||
Example("""
|
||||
let emails = users.map { user in
|
||||
user.email()
|
||||
}
|
||||
""")
|
||||
],
|
||||
triggeringExamples: [
|
||||
Example("let emails = users.map ↓{ $0.email }"),
|
||||
Example("let emails = users.map(↓{ $0.email })"),
|
||||
Example("let admins = users.filter(where: ↓{ $0.isAdmin })"),
|
||||
Example("""
|
||||
let emails = users.map ↓{ user in
|
||||
user.email
|
||||
}
|
||||
""")
|
||||
]
|
||||
)
|
||||
|
||||
// MARK: - ASTRule
|
||||
|
||||
public func validate(file: SwiftLintFile,
|
||||
kind: SwiftExpressionKind,
|
||||
dictionary: SourceKittenDictionary) -> [StyleViolation] {
|
||||
guard SwiftVersion.current >= type(of: self).description.minSwiftVersion else {
|
||||
return []
|
||||
}
|
||||
|
||||
return violationRanges(in: file, kind: kind, dictionary: dictionary).map {
|
||||
StyleViolation(ruleDescription: type(of: self).description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func violationRanges(in file: SwiftLintFile,
|
||||
kind: SwiftExpressionKind,
|
||||
dictionary: SourceKittenDictionary) -> [ByteCount] {
|
||||
guard kind == .call else {
|
||||
return []
|
||||
}
|
||||
|
||||
let closures = dictionary.substructure.compactMap { dictionary -> SourceKittenDictionary? in
|
||||
if dictionary.isClosure {
|
||||
return dictionary
|
||||
}
|
||||
|
||||
if dictionary.isClosureArgument {
|
||||
return dictionary.substructure.first
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return closures.compactMap { dictionary in
|
||||
guard !dictionary.containsMethodCall, !dictionary.containsMutedArguments else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return dictionary.offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SourceKittenDictionary {
|
||||
var isClosure: Bool {
|
||||
return kind.flatMap(SwiftExpressionKind.init(rawValue:)) == .closure
|
||||
}
|
||||
|
||||
var isClosureArgument: Bool {
|
||||
return kind.flatMap(SwiftExpressionKind.init(rawValue:)) == .argument &&
|
||||
substructure.count == 1 &&
|
||||
substructure.allSatisfy { $0.isClosure }
|
||||
}
|
||||
|
||||
var containsMethodCall: Bool {
|
||||
return substructure.contains { dictionary in
|
||||
dictionary.kind.flatMap(SwiftExpressionKind.init(rawValue:)) == .call
|
||||
}
|
||||
}
|
||||
|
||||
var containsMutedArguments: Bool {
|
||||
return substructure.contains { dictionary in
|
||||
dictionary.kind.flatMap(SwiftDeclarationKind.init(rawValue:)) == .varParameter &&
|
||||
dictionary.name == nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -286,6 +286,7 @@
|
|||
D42D2B381E09CC0D00CD7A2E /* FirstWhereRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */; };
|
||||
D42DEAAB20D5EE4400E86F31 /* ConvenienceTypeRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42DEAAA20D5EE4400E86F31 /* ConvenienceTypeRule.swift */; };
|
||||
D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */; };
|
||||
D4369C5D2430B1F400505BB9 /* KeyPathExpressionAsFunctionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4369C5C2430B1F400505BB9 /* KeyPathExpressionAsFunctionRule.swift */; };
|
||||
D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */; };
|
||||
D43B04661E071ED3004016AF /* ColonRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04651E071ED3004016AF /* ColonRuleTests.swift */; };
|
||||
D43B04691E072291004016AF /* ColonConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04671E07228D004016AF /* ColonConfiguration.swift */; };
|
||||
|
@ -816,6 +817,7 @@
|
|||
D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstWhereRule.swift; sourceTree = "<group>"; };
|
||||
D42DEAAA20D5EE4400E86F31 /* ConvenienceTypeRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvenienceTypeRule.swift; sourceTree = "<group>"; };
|
||||
D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRuleTests.swift; sourceTree = "<group>"; };
|
||||
D4369C5C2430B1F400505BB9 /* KeyPathExpressionAsFunctionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathExpressionAsFunctionRule.swift; sourceTree = "<group>"; };
|
||||
D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedEnumeratedRule.swift; sourceTree = "<group>"; };
|
||||
D43B04651E071ED3004016AF /* ColonRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColonRuleTests.swift; sourceTree = "<group>"; };
|
||||
D43B04671E07228D004016AF /* ColonConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColonConfiguration.swift; sourceTree = "<group>"; };
|
||||
|
@ -1345,6 +1347,7 @@
|
|||
47FF3BDF1E7C745100187E6D /* ImplicitlyUnwrappedOptionalRule.swift */,
|
||||
8FC9F5101F4B8E48006826C1 /* IsDisjointRule.swift */,
|
||||
62A6E7911F3317E3003A0479 /* JoinedDefaultParameterRule.swift */,
|
||||
D4369C5C2430B1F400505BB9 /* KeyPathExpressionAsFunctionRule.swift */,
|
||||
4DB7815C1CAD690100BC4723 /* LegacyCGGeometryFunctionsRule.swift */,
|
||||
006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */,
|
||||
00B8D9771E2D0FBD004E0EEC /* LegacyConstantRuleExamples.swift */,
|
||||
|
@ -2094,6 +2097,7 @@
|
|||
623675B01F960C5C009BE6F3 /* QuickDiscouragedPendingTestRule.swift in Sources */,
|
||||
287F8B642230843000BDC504 /* NSLocalizedStringRequireBundleRule.swift in Sources */,
|
||||
D47079AD1DFE2FA700027086 /* EmptyParametersRule.swift in Sources */,
|
||||
D4369C5D2430B1F400505BB9 /* KeyPathExpressionAsFunctionRule.swift in Sources */,
|
||||
E87E4A091BFB9CAE00FCFE46 /* SyntaxKind+SwiftLint.swift in Sources */,
|
||||
3B0B14541C505D6300BE82F7 /* SeverityConfiguration.swift in Sources */,
|
||||
827009FF20FE26C500ECA185 /* TypeContentsOrderRule.swift in Sources */,
|
||||
|
|
|
@ -742,6 +742,12 @@ extension JoinedDefaultParameterRuleTests {
|
|||
]
|
||||
}
|
||||
|
||||
extension KeyPathExpressionAsFunctionRuleTests {
|
||||
static var allTests: [(String, (KeyPathExpressionAsFunctionRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
]
|
||||
}
|
||||
|
||||
extension LargeTupleRuleTests {
|
||||
static var allTests: [(String, (LargeTupleRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
|
@ -1747,6 +1753,7 @@ XCTMain([
|
|||
testCase(IntegrationTests.allTests),
|
||||
testCase(IsDisjointRuleTests.allTests),
|
||||
testCase(JoinedDefaultParameterRuleTests.allTests),
|
||||
testCase(KeyPathExpressionAsFunctionRuleTests.allTests),
|
||||
testCase(LargeTupleRuleTests.allTests),
|
||||
testCase(LastWhereRuleTests.allTests),
|
||||
testCase(LegacyCGGeometryFunctionsRuleTests.allTests),
|
||||
|
|
|
@ -294,6 +294,12 @@ class JoinedDefaultParameterRuleTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
class KeyPathExpressionAsFunctionRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(KeyPathExpressionAsFunctionRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class LargeTupleRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(LargeTupleRule.description)
|
||||
|
|
Loading…
Reference in New Issue