diff --git a/CHANGELOG.md b/CHANGELOG.md
index c55768229dc..5de5fc63410 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,11 @@
[Ornithologist Coder](https://github.com/ornithocoder)
[#1370](https://github.com/realm/SwiftLint/issues/1370)
+* Add `joined_default_parameter` opt-in rule to discourage
+ explicit use of default separator.
+ [Ornithologist Coder](https://github.com/ornithocoder)
+ [#1093](https://github.com/realm/SwiftLint/issues/1093)
+
##### Bug Fixes
* Fix false positive on `force_unwrapping` rule when declaring
diff --git a/Rules.md b/Rules.md
index d7f6ee118a6..3676d7b4865 100644
--- a/Rules.md
+++ b/Rules.md
@@ -41,6 +41,7 @@
* [Implicit Getter](#implicit-getter)
* [Implicit Return](#implicit-return)
* [Implicitly Unwrapped Optional](#implicitly-unwrapped-optional)
+* [Joined Default Parameter](#joined-default-parameter)
* [Large Tuple](#large-tuple)
* [Leading Whitespace](#leading-whitespace)
* [Legacy CGGeometry Functions](#legacy-cggeometry-functions)
@@ -4819,6 +4820,43 @@ func foo(int: Int!) {}
+## Joined Default Parameter
+
+Identifier | Enabled by default | Supports autocorrection | Kind
+--- | --- | --- | ---
+`joined_default_parameter` | Disabled | No | idiomatic
+
+Discouraged explicit use of default separator.
+
+### Examples
+
+
+Non Triggering Examples
+
+```swift
+let foo = bar.joined()
+```
+
+```swift
+let foo = bar.joined(separator: ",")
+```
+
+```swift
+let foo = bar.joined(separator: toto)
+```
+
+
+
+Triggering Examples
+
+```swift
+let foo = bar.joined(separator: "")
+```
+
+
+
+
+
## Large Tuple
Identifier | Enabled by default | Supports autocorrection | Kind
diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift
index 3b20b6f1fb6..b1ce8c90ce6 100644
--- a/Source/SwiftLintFramework/Models/MasterRuleList.swift
+++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift
@@ -49,6 +49,7 @@ public let masterRuleList = RuleList(rules: [
ImplicitGetterRule.self,
ImplicitReturnRule.self,
ImplicitlyUnwrappedOptionalRule.self,
+ JoinedDefaultParameterRule.self,
LargeTupleRule.self,
LeadingWhitespaceRule.self,
LegacyCGGeometryFunctionsRule.self,
diff --git a/Source/SwiftLintFramework/Rules/JoinedDefaultRule.swift b/Source/SwiftLintFramework/Rules/JoinedDefaultRule.swift
new file mode 100644
index 00000000000..7d78f868703
--- /dev/null
+++ b/Source/SwiftLintFramework/Rules/JoinedDefaultRule.swift
@@ -0,0 +1,58 @@
+//
+// JoinedDefaultRule.swift
+// SwiftLint
+//
+// Created by Ornithologist Coder on 8/3/17.
+// Copyright © 2017 Realm. All rights reserved.
+//
+
+import Foundation
+import SourceKittenFramework
+
+public struct JoinedDefaultParameterRule: ASTRule, ConfigurationProviderRule, OptInRule {
+ public var configuration = SeverityConfiguration(.warning)
+
+ public init() {}
+
+ public static let description = RuleDescription(
+ identifier: "joined_default_parameter",
+ name: "Joined Default Parameter",
+ description: "Discouraged explicit usage of the default separator.",
+ kind: .idiomatic,
+ nonTriggeringExamples: [
+ "let foo = bar.joined()",
+ "let foo = bar.joined(separator: \",\")",
+ "let foo = bar.joined(separator: toto)"
+ ],
+ triggeringExamples: [
+ "let foo = bar.joined(separator: \"\")"
+ ]
+ )
+
+ public func validate(file: File,
+ kind: SwiftExpressionKind,
+ dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
+ guard
+ kind == .call,
+ let offset = dictionary.offset,
+ let name = dictionary.name,
+ name.hasSuffix(".joined"),
+ passesDefaultSeparator(dictionary: dictionary, file: file)
+ else {
+ return []
+ }
+
+ return [StyleViolation(ruleDescription: type(of: self).description,
+ severity: configuration.severity,
+ location: Location(file: file, byteOffset: offset))]
+ }
+
+ private func passesDefaultSeparator(dictionary: [String: SourceKitRepresentable], file: File) -> Bool {
+ guard
+ let bodyOffset = dictionary.bodyOffset,
+ let bodyLength = dictionary.bodyLength else { return false }
+
+ let body = file.contents.bridge().substringWithByteRange(start: bodyOffset, length: bodyLength)
+ return body == "separator: \"\""
+ }
+}
diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj
index e3bd2817625..b9e25b3a1e4 100644
--- a/SwiftLint.xcodeproj/project.pbxproj
+++ b/SwiftLint.xcodeproj/project.pbxproj
@@ -70,6 +70,7 @@
62622F6B1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */; };
626D02971F31CBCC0054788D /* XCTFailMessageRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */; };
62A498561F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */; };
+ 62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */; };
67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */; };
67EB4DFA1E4CC111004E9ACD /* CyclomaticComplexityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */; };
67EB4DFC1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */; };
@@ -377,6 +378,7 @@
62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRule.swift; sourceTree = ""; };
626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTFailMessageRule.swift; sourceTree = ""; };
62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = ""; };
+ 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = ""; };
62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRuleTests.swift; sourceTree = ""; };
65454F451B14D73800319A6C /* ControlStatementRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlStatementRule.swift; sourceTree = ""; };
67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CyclomaticComplexityConfigurationTests.swift; sourceTree = ""; };
@@ -964,6 +966,7 @@
D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */,
47FF3BDF1E7C745100187E6D /* ImplicitlyUnwrappedOptionalRule.swift */,
D4470D561EB69225008A1B2E /* ImplicitReturnRule.swift */,
+ 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */,
D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */,
E88DEA7D1B098F2A00A66CB0 /* LeadingWhitespaceRule.swift */,
4DB7815C1CAD690100BC4723 /* LegacyCGGeometryFunctionsRule.swift */,
@@ -1440,6 +1443,7 @@
C328A2F71E6759AE00A9E4D7 /* ExplicitTypeInterfaceRule.swift in Sources */,
93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift in Sources */,
D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */,
+ 62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */,
D4FD4C851F2A260A00DD8AA8 /* BlockBasedKVORule.swift in Sources */,
7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */,
E88DEA771B098D0C00A66CB0 /* Rule.swift in Sources */,
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
index a72c043f30f..40c9e004fec 100644
--- a/Tests/LinuxMain.swift
+++ b/Tests/LinuxMain.swift
@@ -367,6 +367,7 @@ extension RulesTests {
("testImplicitGetter", testImplicitGetter),
("testImplicitlyUnwrappedOptional", testImplicitlyUnwrappedOptional),
("testImplicitReturn", testImplicitReturn),
+ ("testJoinedDefaultParameter", testJoinedDefaultParameter),
("testLargeTuple", testLargeTuple),
("testLeadingWhitespace", testLeadingWhitespace),
("testLegacyCGGeometryFunctions", testLegacyCGGeometryFunctions),
diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift
index 24330993762..3c0713b3d46 100644
--- a/Tests/SwiftLintFrameworkTests/RulesTests.swift
+++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift
@@ -142,6 +142,10 @@ class RulesTests: XCTestCase {
verifyRule(ImplicitReturnRule.description)
}
+ func testJoinedDefaultParameter() {
+ verifyRule(JoinedDefaultParameterRule.description)
+ }
+
func testLargeTuple() {
verifyRule(LargeTupleRule.description)
}