Skip to content

Commit

Permalink
- Added convenienceType rule
Browse files Browse the repository at this point in the history
- Added test suite
  • Loading branch information
facumenzella committed Sep 12, 2020
1 parent 0d61559 commit a6765d3
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 16 deletions.
5 changes: 5 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [braces](#braces)
* [consecutiveBlankLines](#consecutiveBlankLines)
* [consecutiveSpaces](#consecutiveSpaces)
* [convenienceType](#convenienceType)
* [duplicateImports](#duplicateImports)
* [elseOnSameLine](#elseOnSameLine)
* [emptyBraces](#emptyBraces)
Expand Down Expand Up @@ -330,6 +331,10 @@ Replace consecutive spaces with a single space.
</details>
<br/>

## convenienceType

Converts types used for hosting only static members into enums.

## duplicateImports

Remove duplicate import statements.
Expand Down
49 changes: 49 additions & 0 deletions Sources/Rules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,55 @@ public struct _FormatRules {
}
}

// Converts types used for hosting only static members into enums to avoid instantiation.
public let convenienceType = FormatRule(
help: "Converts types used for hosting only static members into enums.",
options: []
) { formatter in

func rangeHostsOnlyStaticMembers(start startIndex: Int, end endIndex: Int) -> Bool {
var j = startIndex
while j < endIndex, let token = formatter.token(at: j) {
// exit if there's a explicit init
if token == .keyword("init") {
return false
} else if [.keyword("let"),
.keyword("var"),
.keyword("func")].contains(token),
!formatter.modifiersForType(at: j, contains: "static")
{
return false
}
j += 1
}
return true
}

formatter.forEachToken(where: { $0 == .keyword("class") || $0 == .keyword("struct") }) { i, _ in
guard formatter.last(.keyword, before: i) != .keyword("import") else { return }
// exit if class is a type modifier
guard let next = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i), !next.isKeyword else { return }

guard let braceIndex = formatter.index(after: i, where: { $0 == .startOfScope("{") }) else { return }

// exit if type is conforming any types
guard !formatter.tokens[i ... braceIndex].contains(.delimiter(":")) else { return }

guard let endIndex = formatter.index(after: braceIndex, where: { $0 == .endOfScope("}") }) else { return }

if rangeHostsOnlyStaticMembers(start: braceIndex + 1, end: endIndex) {
formatter.replaceToken(at: i, with: [.keyword("enum")])

let start = formatter.startOfModifiers(at: i)
if formatter.modifiersForType(at: i, contains: "final"),
let finalIndex = formatter.lastIndex(in: start ..< i, where: { $0 == .identifier("final") })
{
formatter.removeTokens(in: finalIndex ... finalIndex + 1)
}
}
}
}

/// Remove trailing space from the end of lines, as it has no semantic
/// meaning and leads to noise in commits.
public let trailingSpace = FormatRule(
Expand Down
11 changes: 8 additions & 3 deletions Tests/RulesTests+Braces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension RulesTests {
// foo
}
"""
testFormatting(for: input, rule: FormatRules.braces)
testFormatting(for: input, rule: FormatRules.braces, exclude: ["convenienceType"])
}

func testKnRExtraSpaceNotAddedBeforeBrace() {
Expand Down Expand Up @@ -187,7 +187,7 @@ extension RulesTests {
// foo
}
"""
testFormatting(for: input, output, rule: FormatRules.braces)
testFormatting(for: input, output, rule: FormatRules.braces, exclude: ["convenienceType"])
}

func testBracesForInit() {
Expand Down Expand Up @@ -336,7 +336,12 @@ extension RulesTests {
}
"""
let options = FormatOptions(allmanBraces: true)
testFormatting(for: input, output, rule: FormatRules.braces, options: options)
testFormatting(
for: input, output,
rule: FormatRules.braces,
options: options,
exclude: ["convenienceType"]
)
}

func testAllmanBracesForInit() {
Expand Down
2 changes: 1 addition & 1 deletion Tests/RulesTests+Organization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ extension RulesTests {
for: input, output,
rule: FormatRules.organizeDeclarations,
options: FormatOptions(beforeMarks: ["typealias", "struct"]),
exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]
exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "convenienceType"]
)
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/RulesTests+Redundancy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,7 @@ extension RulesTests {

func testNoRemoveBackticksAroundTypeInsideType() {
let input = "struct Foo {\n enum `Type` {}\n}"
testFormatting(for: input, rule: FormatRules.redundantBackticks)
testFormatting(for: input, rule: FormatRules.redundantBackticks, exclude: ["convenienceType"])
}

func testNoRemoveBackticksAroundLetArgument() {
Expand All @@ -1435,7 +1435,7 @@ extension RulesTests {

func testNoRemoveBackticksAroundTypePropertyInsideType() {
let input = "struct Foo {\n enum `Type` {}\n}"
testFormatting(for: input, rule: FormatRules.redundantBackticks)
testFormatting(for: input, rule: FormatRules.redundantBackticks, exclude: ["convenienceType"])
}

func testNoRemoveBackticksAroundTrueProperty() {
Expand Down
14 changes: 10 additions & 4 deletions Tests/RulesTests+Wrapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2229,7 +2229,13 @@ extension RulesTests {
class Foo {}
"""
let options = FormatOptions(typeAttributes: .prevLine)
testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options)
testFormatting(
for: input,
output,
rule: FormatRules.wrapAttributes,
options: options,
exclude: ["convenienceType"]
)
}

func testTypeAttributeStaysWrapped() {
Expand All @@ -2238,7 +2244,7 @@ extension RulesTests {
struct Foo {}
"""
let options = FormatOptions(typeAttributes: .prevLine)
testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options)
testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options, exclude: ["convenienceType"])
}

func testUnwrapTypeAttribute() {
Expand All @@ -2258,7 +2264,7 @@ extension RulesTests {
@objc class Foo {}
"""
let options = FormatOptions(typeAttributes: .sameLine)
testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options)
testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options, exclude: ["convenienceType"])
}

func testTestableImportIsNotWrapped() {
Expand All @@ -2269,7 +2275,7 @@ extension RulesTests {
class Foo {}
"""
let options = FormatOptions(typeAttributes: .prevLine)
testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options)
testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options, exclude: ["convenienceType"])
}

func testModifiersDontAffectAttributeWrapping() {
Expand Down
122 changes: 118 additions & 4 deletions Tests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,120 @@ class RulesTests: XCTestCase {
testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options)
}

// MARK: - convenienceType

func testConvenienceTypeConformingOtherType() {
let input = "private final class CustomUITableViewCell: UITableViewCell {}"
testFormatting(for: input, rule: FormatRules.convenienceType)
}

func testConvenienceTypeImportClass() {
let input = "import class MyUIKit.AutoHeightTableView"
testFormatting(for: input, rule: FormatRules.convenienceType)
}

func testConvenienceTypeImportStruct() {
let input = "import struct Core.CurrencyFormatter"
testFormatting(for: input, rule: FormatRules.convenienceType)
}

func testConvenienceTypeClassFunction() {
let input = """
class Container {
class func bar() {}
}
"""
testFormatting(for: input, rule: FormatRules.convenienceType)
}

func testConvenienceTypeRemovingExtraKeywords() {
let input = "final class MyNamespace {}"
let output = "enum MyNamespace {}"
testFormatting(for: input, output, rule: FormatRules.convenienceType)
}

func testConvenienceTypeNestedTypes() {
let input = """
enum Namespace {}
extension Namespace {
struct Constants {
static let us = "us"
}
}
"""
let output = """
enum Namespace {}
extension Namespace {
enum Constants {
static let us = "us"
}
}
"""
testFormatting(for: input, output, rule: FormatRules.convenienceType)
}

func testConvenienceTypeStaticVariable() {
let input = """
struct Constants {
static let β = 0, 5
}
"""
let output = """
enum Constants {
static let β = 0, 5
}
"""
testFormatting(for: input, output, rule: FormatRules.convenienceType)
}

func testConvenienceTypeStaticAndInstanceVariable() {
let input = """
struct Constants {
static let β = 0, 5
let Ɣ = 0, 3
}
"""
testFormatting(for: input, rule: FormatRules.convenienceType)
}

func testConvenienceTypeStaticFunction() {
let input = """
struct Constants {
static func remoteConfig() -> Int {
return 10
}
}
"""
let output = """
enum Constants {
static func remoteConfig() -> Int {
return 10
}
}
"""
testFormatting(for: input, output, rule: FormatRules.convenienceType)
}

func testConvenienceTypeStaticAndInstanceFunction() {
let input = """
struct Constants {
static func remoteConfig() -> Int {
return 10
}
func instanceConfig(offset: Int) -> Int {
return offset + 10
}
}
"""

testFormatting(for: input, rule: FormatRules.convenienceType)
}

// MARK: - trailingSpace

// truncateBlankLines = true

func testUnhoistSingleCaseLet() {
let input = "if case let .foo(bar) = quux {}"
let output = "if case .foo(let bar) = quux {}"
Expand Down Expand Up @@ -1582,14 +1696,14 @@ class RulesTests: XCTestCase {
func testNoStripHeaderDocWithNewlineBeforeCode() {
let input = "/// Header doc\n\nclass Foo {}"
let options = FormatOptions(fileHeader: "")
testFormatting(for: input, rule: FormatRules.fileHeader, options: options)
testFormatting(for: input, rule: FormatRules.fileHeader, options: options, exclude: ["convenienceType"])
}

func testNoDuplicateHeaderIfMissingTrailingBlankLine() {
let input = "// Header comment\nclass Foo {}"
let output = "// Header comment\n\nclass Foo {}"
let options = FormatOptions(fileHeader: "Header comment")
testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options)
testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options, exclude: ["convenienceType"])
}

func testFileHeaderYearReplacement() {
Expand Down Expand Up @@ -2256,7 +2370,7 @@ class RulesTests: XCTestCase {
func testClassNotReplacedByAnyObjectIfSwiftVersionLessThan4_1() {
let input = "protocol Foo: class {}"
let options = FormatOptions(swiftVersion: "4.0")
testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options)
testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options, exclude: ["convenienceType"])
}

// MARK: - strongifiedSelf
Expand Down Expand Up @@ -2841,7 +2955,7 @@ class RulesTests: XCTestCase {
testFormatting(for: input, output, rule: FormatRules.preferKeyPath,
options: options)
}

func testNoMapSelfToKeyPath() {
let input = "let foo = bar.map { $0 }"
let options = FormatOptions(swiftVersion: "5.2")
Expand Down
12 changes: 10 additions & 2 deletions Tests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,16 @@ extension RulesTests {
("testConsecutiveSpacesRemovedBetweenComments", testConsecutiveSpacesRemovedBetweenComments),
("testConsecutiveWraps", testConsecutiveWraps),
("testConstantAfterNullCoalescingNonYodaCondition", testConstantAfterNullCoalescingNonYodaCondition),
("testConvenienceTypeClassFunction", testConvenienceTypeClassFunction),
("testConvenienceTypeConformingOtherType", testConvenienceTypeConformingOtherType),
("testConvenienceTypeImportClass", testConvenienceTypeImportClass),
("testConvenienceTypeImportStruct", testConvenienceTypeImportStruct),
("testConvenienceTypeNestedTypes", testConvenienceTypeNestedTypes),
("testConvenienceTypeRemovingExtraKeywords", testConvenienceTypeRemovingExtraKeywords),
("testConvenienceTypeStaticAndInstanceFunction", testConvenienceTypeStaticAndInstanceFunction),
("testConvenienceTypeStaticAndInstanceVariable", testConvenienceTypeStaticAndInstanceVariable),
("testConvenienceTypeStaticFunction", testConvenienceTypeStaticFunction),
("testConvenienceTypeStaticVariable", testConvenienceTypeStaticVariable),
("testCorrectWrapIndentForNestedArguments", testCorrectWrapIndentForNestedArguments),
("testCountEqualsZero", testCountEqualsZero),
("testCountEqualsZeroAfterOptionalCallOnPreviousLine", testCountEqualsZeroAfterOptionalCallOnPreviousLine),
Expand Down Expand Up @@ -1098,7 +1108,6 @@ extension RulesTests {
("testNoMapPropertyToKeyPathForFunctionCalls", testNoMapPropertyToKeyPathForFunctionCalls),
("testNoMapPropertyToKeyPathForOptionalChaining", testNoMapPropertyToKeyPathForOptionalChaining),
("testNoMapPropertyToKeyPathForSwiftLessThan5_2", testNoMapPropertyToKeyPathForSwiftLessThan5_2),
("testNoMapSelfToKeyPath", testNoMapSelfToKeyPath),
("testNoMarkFunctionArgument", testNoMarkFunctionArgument),
("testNoMarkNamedFunctionArgument", testNoMarkNamedFunctionArgument),
("testNoMarkProtocolFunctionArgument", testNoMarkProtocolFunctionArgument),
Expand Down Expand Up @@ -1423,7 +1432,6 @@ extension RulesTests {
("testParensRemovedBeforeTrailingClosure3", testParensRemovedBeforeTrailingClosure3),
("testParensRemovedBeforeTrailingClosureInsideHashIf", testParensRemovedBeforeTrailingClosureInsideHashIf),
("testParensRemovedOnLineAfterSelectorIdentifier", testParensRemovedOnLineAfterSelectorIdentifier),
("testParenthesizedMapPropertyToKeyPath", testParenthesizedMapPropertyToKeyPath),
("testParsesPropertiesWithBodies", testParsesPropertiesWithBodies),
("testPartiallyMarkedUnusedArguments", testPartiallyMarkedUnusedArguments),
("testPartiallyMarkedUnusedArguments2", testPartiallyMarkedUnusedArguments2),
Expand Down

0 comments on commit a6765d3

Please sign in to comment.