Compare commits

...

1 Commits

Author SHA1 Message Date
Marcelo Fabri ae9aecffd6 WIP 2020-03-30 02:15:33 -07:00
5 changed files with 124 additions and 0 deletions

View File

@ -77,6 +77,7 @@ public let masterRuleList = RuleList(rules: [
InertDeferRule.self,
IsDisjointRule.self,
JoinedDefaultParameterRule.self,
KeyPathExpressionAsFunctionRule.self,
LargeTupleRule.self,
LastWhereRule.self,
LeadingWhitespaceRule.self,

View File

@ -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
}
}
}

View File

@ -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 */,

View File

@ -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),

View File

@ -294,6 +294,12 @@ class JoinedDefaultParameterRuleTests: XCTestCase {
}
}
class KeyPathExpressionAsFunctionRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(KeyPathExpressionAsFunctionRule.description)
}
}
class LargeTupleRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(LargeTupleRule.description)