Skip to content

Commit

Permalink
Enum namespace rule (nicklockwood#737)
Browse files Browse the repository at this point in the history
  • Loading branch information
facumenzella authored and nicklockwood committed Oct 13, 2020
1 parent be0d841 commit 9c0e076
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 8 deletions.
5 changes: 5 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* [duplicateImports](#duplicateImports)
* [elseOnSameLine](#elseOnSameLine)
* [emptyBraces](#emptyBraces)
* [enumNamespaces](#enumNamespaces)
* [fileHeader](#fileHeader)
* [hoistPatternLet](#hoistPatternLet)
* [indent](#indent)
Expand Down Expand Up @@ -433,6 +434,10 @@ Remove whitespace inside empty braces.
</details>
<br/>

## enumNamespaces

Converts types used for hosting only static members into enums.

## fileHeader

Use specified source file header template for all files.
Expand Down
2 changes: 1 addition & 1 deletion Snapshots/Layout/Layout/LayoutConsole.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
import UIKit

/// Singleton for managing the Layout debug console interface
public struct LayoutConsole {
public enum LayoutConsole {
private static var errorView = LayoutErrorView()
private static var warningView = LayoutWarningView()

Expand Down
6 changes: 3 additions & 3 deletions Snapshots/Layout/Layout/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ struct UIntOptionSet: OptionSet {
#if !swift(>=4)

extension NSAttributedString {
struct DocumentType {
enum DocumentType {
static let html = NSHTMLTextDocumentType
}

struct DocumentReadingOptionKey {
enum DocumentReadingOptionKey {
static let documentType = NSDocumentTypeDocumentAttribute
static let characterEncoding = NSCharacterEncodingDocumentAttribute
}
Expand All @@ -169,7 +169,7 @@ struct UIntOptionSet: OptionSet {
}

extension UIFontDescriptor {
struct AttributeName {
enum AttributeName {
static let traits = UIFontDescriptorTraitsAttribute
}

Expand Down
61 changes: 61 additions & 0 deletions Sources/Rules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,67 @@ public struct _FormatRules {
}
}

// Converts types used for hosting only static members into enums to avoid instantiation.
public let enumNamespaces = FormatRule(
help: "Converts types used for hosting only static members into enums.",
options: []
) { formatter in
func rangeHostsOnlyStaticMembersAtTopLevel(start startIndex: Int, end endIndex: Int) -> Bool {
// exit for empty declarations
guard formatter.next(.nonSpaceOrCommentOrLinebreak, in: startIndex ..< endIndex) != nil else { return false }

var j = startIndex
while j < endIndex, let token = formatter.token(at: j) {
if token == .startOfScope("{"), let skip = formatter.index(of: .endOfScope,
after: j,
if: { $0 == .endOfScope("}") })
{
j = skip
continue
}
// 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 }

// exit for class as protocol conformance
guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .delimiter(":") 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 rangeHostsOnlyStaticMembersAtTopLevel(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
2 changes: 1 addition & 1 deletion Tests/RulesTests+Organization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ extension RulesTests {
testFormatting(
for: input, output,
rule: FormatRules.organizeDeclarations,
exclude: ["blankLinesAtStartOfScope"]
exclude: ["blankLinesAtStartOfScope", "enumNamespaces"]
)
}

Expand Down
6 changes: 3 additions & 3 deletions Tests/RulesTests+Redundancy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1425,7 +1425,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: ["enumNamespaces"])
}

func testNoRemoveBackticksAroundLetArgument() {
Expand All @@ -1452,7 +1452,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: ["enumNamespaces"])
}

func testNoRemoveBackticksAroundTrueProperty() {
Expand Down Expand Up @@ -1855,7 +1855,7 @@ extension RulesTests {
func testRemoveSelfInStaticFunction() {
let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}"
let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}"
testFormatting(for: input, output, rule: FormatRules.redundantSelf)
testFormatting(for: input, output, rule: FormatRules.redundantSelf, exclude: ["enumNamespaces"])
}

func testRemoveSelfInClassFunctionWithModifiers() {
Expand Down
231 changes: 231 additions & 0 deletions Tests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,237 @@ class RulesTests: XCTestCase {
testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options)
}

// MARK: - enumNamespaces

func testEnumNamespacesClassAsProtocolRestriction() {
let input = """
@objc protocol Foo: class {
@objc static var expressionTypes: [String: RuntimeType] { get }
}
"""
testFormatting(for: input, rule: FormatRules.enumNamespaces)
}

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

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

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

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

func testEnumNamespacesRemovingExtraKeywords() {
let input = """
final class MyNamespace {
static let bar = "bar"
}
"""
let output = """
enum MyNamespace {
static let bar = "bar"
}
"""
testFormatting(for: input, output, rule: FormatRules.enumNamespaces)
}

func testEnumNamespacesNestedTypes() {
let input = """
enum Namespace {}
extension Namespace {
struct Constants {
static let bar = "bar"
}
}
"""
let output = """
enum Namespace {}
extension Namespace {
enum Constants {
static let bar = "bar"
}
}
"""
testFormatting(for: input, output, rule: FormatRules.enumNamespaces)
}

func testEnumNamespacesNestedTypes2() {
let input = """
struct Namespace {
struct NestedNamespace {
static let foo: Int
static let bar: Int
}
}
"""
let output = """
enum Namespace {
enum NestedNamespace {
static let foo: Int
static let bar: Int
}
}
"""
testFormatting(for: input, output, rule: FormatRules.enumNamespaces)
}

func testEnumNamespacesNestedTypes3() {
let input = """
struct Namespace {
struct TypeNestedInNamespace {
let foo: Int
let bar: Int
}
}
"""
let output = """
enum Namespace {
struct TypeNestedInNamespace {
let foo: Int
let bar: Int
}
}
"""
testFormatting(for: input, output, rule: FormatRules.enumNamespaces)
}

func testEnumNamespacesNestedTypes4() {
let input = """
struct Namespace {
static func staticFunction() {
struct NestedType {
init() {}
}
}
}
"""
let output = """
enum Namespace {
static func staticFunction() {
struct NestedType {
init() {}
}
}
}
"""
testFormatting(for: input, output, rule: FormatRules.enumNamespaces)
}

func testEnumNamespacesNestedTypes5() {
let input = """
struct Namespace {
static func staticFunction() {
func nestedFunction() { /* ... */ }
}
}
"""
let output = """
enum Namespace {
static func staticFunction() {
func nestedFunction() { /* ... */ }
}
}
"""
testFormatting(for: input, output, rule: FormatRules.enumNamespaces)
}

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

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

func testEnumNamespacesStaticFunction() {
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.enumNamespaces)
}

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

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

func testEnumNamespaceDoesNothing() {
let input = """
struct Foo {
#if BAR
func something() {}
#else
func something() {}
#endif
}
"""
testFormatting(for: input, rule: FormatRules.enumNamespaces)
}

func testEnumNamespaceDoesNothingEmptyDeclaration() {
let input = """
class Foo {}
"""
testFormatting(for: input, rule: FormatRules.enumNamespaces)
}

// MARK: - trailingSpace

// truncateBlankLines = true

func testUnhoistSingleCaseLet() {
let input = "if case let .foo(bar) = quux {}"
let output = "if case .foo(let bar) = quux {}"
Expand Down
Loading

0 comments on commit 9c0e076

Please sign in to comment.