Skip to content

Commit

Permalink
Merge 7d27b23 into eb9aac3
Browse files Browse the repository at this point in the history
  • Loading branch information
calda committed Jul 19, 2020
2 parents eb9aac3 + 7d27b23 commit 3979123
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 1 deletion.
52 changes: 52 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [andOperator](#andOperator)
* [anyObjectProtocol](#anyObjectProtocol)
* [attributes](#attributes)
* [blankLinesAroundMark](#blankLinesAroundMark)
* [blankLinesAtEndOfScope](#blankLinesAtEndOfScope)
* [blankLinesAtStartOfScope](#blankLinesAtStartOfScope)
Expand Down Expand Up @@ -116,6 +117,57 @@ swift version is set to 4.1 or above.
</details>
<br/>

## attributes

Wrap @ attrubutes onto a separate line, or keep them on the same line.

Option | Description
--- | ---
`--funcattributes` | Function @ attributes: "preserve", "new-line", or "same-line"
`--typeattributes` | Type @ attributes: "preserve", "new-line", or "same-line"

<details>
<summary>Examples</summary>

`--funcattributes new-line`

```diff
- @objc func foo() {}

+ @objc
+ func foo() { }
```

`--funcattributes same-line`

```diff
- @objc
- func foo() { }

+ @objc func foo() {}
```

`--typeattributes new-line`

```diff
- @objc class Foo {}

+ @objc
+ class Foo { }
```

`--typeattributes same-line`

```diff
- @objc
- enum Foo { }

+ @objc enun Foo {}
```

</details>
<br/>

## blankLinesAroundMark

Insert blank line before and after `MARK:` comments.
Expand Down
38 changes: 38 additions & 0 deletions Sources/Examples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1036,4 +1036,42 @@ private struct Examples {
+ let bar = maybeBar else { ... }
```
"""

let attributes = """
`--funcattributes new-line`
```diff
- @objc func foo() {}
+ @objc
+ func foo() { }
```
`--funcattributes same-line`
```diff
- @objc
- func foo() { }
+ @objc func foo() {}
```
`--typeattributes new-line`
```diff
- @objc class Foo {}
+ @objc
+ class Foo { }
```
`--typeattributes same-line`
```diff
- @objc
- enum Foo { }
+ @objc enun Foo {}
```
"""
}
13 changes: 13 additions & 0 deletions Sources/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ public enum ArgumentStrippingMode: String {
case all = "always"
}

// Wrap mode for @ attributes
public enum AttributeMode: String {
case newLine = "new-line"
case sameLine = "same-line"
case preserve
}

/// Version number wrapper
public struct Version: RawRepresentable, Comparable, ExpressibleByStringLiteral, CustomStringConvertible {
public let rawValue: String
Expand Down Expand Up @@ -284,6 +291,8 @@ public struct FormatOptions: CustomStringConvertible {
public var noWrapOperators: Set<String>
public var specifierOrder: [String]
public var shortOptionals: OptionalsMode
public var funcAttributes: AttributeMode
public var typeAttributes: AttributeMode

// Deprecated
public var indentComments: Bool
Expand Down Expand Up @@ -339,6 +348,8 @@ public struct FormatOptions: CustomStringConvertible {
noWrapOperators: Set<String> = [],
specifierOrder: [String] = [],
shortOptionals: OptionalsMode = .always,
funcAttributes: AttributeMode = .preserve,
typeAttributes: AttributeMode = .preserve,
// Doesn't really belong here, but hard to put elsewhere
fragment: Bool = false,
ignoreConflictMarkers: Bool = false,
Expand Down Expand Up @@ -386,6 +397,8 @@ public struct FormatOptions: CustomStringConvertible {
self.noWrapOperators = noWrapOperators
self.specifierOrder = specifierOrder
self.shortOptionals = shortOptionals
self.funcAttributes = funcAttributes
self.typeAttributes = typeAttributes
// Doesn't really belong here, but hard to put elsewhere
self.fragment = fragment
self.ignoreConflictMarkers = ignoreConflictMarkers
Expand Down
16 changes: 16 additions & 0 deletions Sources/OptionsDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ extension FormatOptions.Descriptor {
noWrapOperators,
specifierOrder,
shortOptionals,
funcAttributes,
typeAttributes,

// Deprecated
indentComments,
Expand Down Expand Up @@ -681,6 +683,20 @@ extension FormatOptions.Descriptor {
help: "The version of Swift used in the files being formatted",
keyPath: \.swiftVersion
)
static let funcAttributes = FormatOptions.Descriptor(
argumentName: "funcattributes",
propertyName: "funcAttributes",
displayName: "Function Attributes",
help: "Function @ attributes: \"preserve\", \"new-line\", or \"same-line\"",
keyPath: \.funcAttributes
)
static let typeAttributes = FormatOptions.Descriptor(
argumentName: "typeattributes",
propertyName: "typeAttributes",
displayName: "Type Attributes",
help: "Type @ attributes: \"preserve\", \"new-line\", or \"same-line\"",
keyPath: \.typeAttributes
)

// MARK: - DEPRECATED

Expand Down
67 changes: 67 additions & 0 deletions Sources/Rules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4384,4 +4384,71 @@ public struct _FormatRules {
formatter.removeTokens(inRange: startIndex + 1 ..< endOfLine)
}
}

/// Strip header comments from the file
public let attributes = FormatRule(
help: "Wrap @ attrubutes onto a separate line, or keep them on the same line.",
options: ["funcattributes", "typeattributes"],
sharedOptions: ["linebreaks"]
) { formatter in
formatter.forEach(.identifierOrKeyword) { i, _ in
guard
formatter.token(at: i)?.string.starts(with: "@") == true,
let nextOpenBracket = formatter.index(of: .startOfScope("{"), after: i) else {
return
}

// Check the declaration for func or class/struct/enum to determine
// which `AttributesMode` option to use.
let declaration = CountableRange<Int>(i ... nextOpenBracket)
let attributeMode: AttributeMode

if formatter.index(of: .keyword("func"), in: declaration) != nil {
attributeMode = formatter.options.funcAttributes
} else if (formatter.index(of: .keyword("class"), in: declaration)
?? formatter.index(of: .keyword("struct"), in: declaration)
?? formatter.index(of: .keyword("enum"), in: declaration)) != nil {
attributeMode = formatter.options.typeAttributes
} else {
return
}

// Determine the end index of the attrubute
// - Attributes like `@objc` are single tokens,
// but attributes like `@available(iOS 14.0, *)` are multiple tokens.
let attributeEndIndex: Int
if let argumentStartIndex = formatter.index(of: .nonSpaceOrComment, after: i),
formatter.token(at: argumentStartIndex) == .startOfScope("("),
let endOfArgumentScope = formatter.endOfScope(at: argumentStartIndex) {
attributeEndIndex = endOfArgumentScope
} else {
attributeEndIndex = i
}

// Apply the `AttributeMode`
switch attributeMode {
case .preserve:
return
case .newLine:
// Make sure there's a newline immediately following the attribute
if let nextTokenIndex = formatter.index(of: .nonSpaceOrComment, after: attributeEndIndex),
formatter.token(at: nextTokenIndex)?.isLinebreak != true {
formatter.insertLinebreak(at: nextTokenIndex)
// Remove any trailing whitespace left on the line with the attributes
if let previousToken = formatter.token(at: nextTokenIndex - 1),
previousToken.isSpace {
formatter.removeToken(at: nextTokenIndex - 1)
}
}
case .sameLine:
// Make sure there _isn't_ a newline immediately following the attributes
if let nextTokenIndex = formatter.index(of: .nonSpaceOrComment, after: attributeEndIndex),
formatter.token(at: nextTokenIndex)?.isLinebreak != false {
// Replace the newline with a space so the attribute doesn't
// merge with the next token.
formatter.replaceToken(at: nextTokenIndex, with: .space(" "))
}
}
}
}
}
2 changes: 1 addition & 1 deletion Tests/ArgumentsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class ArgumentsTests: XCTestCase {
}

func testCommandLineArgumentsAreCorrect() {
let output = ["allman": "false", "wraparguments": "preserve", "wrapparameters": "preserve", "stripunusedargs": "always", "self": "remove", "header": "ignore", "importgrouping": "alphabetized", "fractiongrouping": "disabled", "binarygrouping": "4,8", "octalgrouping": "4,8", "indentcase": "false", "trimwhitespace": "always", "decimalgrouping": "3,6", "exponentgrouping": "disabled", "patternlet": "hoist", "commas": "always", "wrapcollections": "preserve", "semicolons": "inline", "indent": "4", "exponentcase": "lowercase", "operatorfunc": "spaced", "symlinks": "ignore", "elseposition": "same-line", "empty": "void", "hexliteralcase": "uppercase", "linebreaks": "lf", "hexgrouping": "4,8", "ifdef": "indent", "closingparen": "balanced", "selfrequired": "", "trailingclosures": "", "xcodeindentation": "disabled", "fragment": "false", "conflictmarkers": "reject", "tabwidth": "unspecified", "maxwidth": "none", "nospaceoperators": "", "nowrapoperators": "", "specifierorder": "", "minversion": "0", "shortoptionals": "always"]
let output = ["allman": "false", "wraparguments": "preserve", "wrapparameters": "preserve", "stripunusedargs": "always", "self": "remove", "header": "ignore", "importgrouping": "alphabetized", "fractiongrouping": "disabled", "binarygrouping": "4,8", "octalgrouping": "4,8", "indentcase": "false", "trimwhitespace": "always", "decimalgrouping": "3,6", "exponentgrouping": "disabled", "patternlet": "hoist", "commas": "always", "wrapcollections": "preserve", "semicolons": "inline", "indent": "4", "exponentcase": "lowercase", "operatorfunc": "spaced", "symlinks": "ignore", "elseposition": "same-line", "empty": "void", "hexliteralcase": "uppercase", "linebreaks": "lf", "hexgrouping": "4,8", "ifdef": "indent", "closingparen": "balanced", "selfrequired": "", "trailingclosures": "", "xcodeindentation": "disabled", "fragment": "false", "conflictmarkers": "reject", "tabwidth": "unspecified", "maxwidth": "none", "nospaceoperators": "", "nowrapoperators": "", "specifierorder": "", "minversion": "0", "shortoptionals": "always", "funcattributes": "preserve", "typeattributes": "preserve"]
XCTAssertEqual(argumentsFor(.default), output)
}

Expand Down
99 changes: 99 additions & 0 deletions Tests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11993,4 +11993,103 @@ class RulesTests: XCTestCase {
"""
testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces)
}

// MARK: - attributes

func testPreserveWrappedFuncAttributeByDefault() {
let input = """
@objc
func foo() {}
"""
testFormatting(for: input, rule: FormatRules.attributes)
}

func testPreserveUnwrappedFuncAttributeByDefault() {
let input = """
@objc func foo() {}
"""
testFormatting(for: input, rule: FormatRules.attributes)
}

func testWrapFuncAttribute() {
let input = """
@available(iOS 14.0, *) func foo() {}
"""
let output = """
@available(iOS 14.0, *)
func foo() {}
"""
let options = FormatOptions(funcAttributes: .newLine)
testFormatting(for: input, output, rule: FormatRules.attributes, options: options)
}

func testFuncAttributeStaysWrapped() {
let input = """
@available(iOS 14.0, *)
func foo() {}
"""
let options = FormatOptions(funcAttributes: .newLine)
testFormatting(for: input, rule: FormatRules.attributes, options: options)
}

func testUnwrapFuncAttribute() {
let input = """
@available(iOS 14.0, *)
func foo() {}
"""
let output = """
@available(iOS 14.0, *) func foo() {}
"""
let options = FormatOptions(funcAttributes: .sameLine)
testFormatting(for: input, output, rule: FormatRules.attributes, options: options)
}

func testFuncAttributeStaysUnwrapped() {
let input = """
@objc func foo() {}
"""
let options = FormatOptions(funcAttributes: .sameLine)
testFormatting(for: input, rule: FormatRules.attributes, options: options)
}

func testWrapTypeAttribute() {
let input = """
@available(iOS 14.0, *) class Foo {}
"""
let output = """
@available(iOS 14.0, *)
class Foo {}
"""
let options = FormatOptions(typeAttributes: .newLine)
testFormatting(for: input, output, rule: FormatRules.attributes, options: options)
}

func testTypeAttributeStaysWrapped() {
let input = """
@available(iOS 14.0, *)
struct Foo {}
"""
let options = FormatOptions(typeAttributes: .newLine)
testFormatting(for: input, rule: FormatRules.attributes, options: options)
}

func testUnwrapTypeAttribute() {
let input = """
@available(iOS 14.0, *)
enum Foo {}
"""
let output = """
@available(iOS 14.0, *) enum Foo {}
"""
let options = FormatOptions(typeAttributes: .sameLine)
testFormatting(for: input, output, rule: FormatRules.attributes, options: options)
}

func testTypeAttributeStaysUnwrapped() {
let input = """
@objc class Foo {}
"""
let options = FormatOptions(typeAttributes: .sameLine)
testFormatting(for: input, rule: FormatRules.attributes, options: options)
}
}
Loading

0 comments on commit 3979123

Please sign in to comment.