├── .gitignore ├── .swift-version ├── .swiftfmt.json ├── .travis.yml ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── swiftfmt-core │ ├── AlignmentFormatter.swift │ ├── BlankLineFormatter.swift │ ├── BraceFormatter.swift │ ├── Configuration.swift │ ├── Formatter.swift │ ├── IndentFormatter.swift │ ├── Line.swift │ ├── Processor.swift │ ├── Renderer.swift │ ├── SemicolonFormatter.swift │ ├── SourceFile.swift │ ├── SourceFormatter.swift │ ├── SpaceFormatter.swift │ ├── Token.swift │ ├── TokenVisitor.swift │ └── WrappingFormatter.swift └── swiftfmt │ ├── Errors.swift │ ├── FormatTool.swift │ ├── OptionParser.swift │ └── main.swift └── Tests ├── LinuxMain.swift └── tests ├── AlignmentTests.swift ├── BlankLineTest.swift ├── BraceTests.swift ├── ConfigurationTests.swift ├── IndentationTests.swift ├── SemicolonTests.swift ├── SpaceTests.swift ├── TestRunner.swift └── WrappingTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | swift-DEVELOPMENT-SNAPSHOT-2018-02-23-a 2 | -------------------------------------------------------------------------------- /.swiftfmt.json: -------------------------------------------------------------------------------- 1 | { 2 | "indentation" : { 3 | "useTabCharacter" : false, 4 | "tabSize" : 4, 5 | "indent" : 4, 6 | "continuationIndent" : 8, 7 | "keepIndentsOnEmptyLines" : false, 8 | "indentCaseBranches" : false 9 | }, 10 | "shouldRemoveSemicons" : true, 11 | "spaces" : { 12 | "before" : { 13 | "parentheses" : { 14 | "functionDeclaration" : false, 15 | "functionCall" : false, 16 | "if" : true, 17 | "while" : true, 18 | "switch" : true, 19 | "catch" : true, 20 | "attribute" : false, 21 | }, 22 | "leftBrace" : { 23 | "typeDeclaration" : true, 24 | "function" : true, 25 | "if" : true, 26 | "else" : true, 27 | "for" : true, 28 | "while" : true, 29 | "do" : true, 30 | "switch" : true, 31 | "catch" : true 32 | }, 33 | "keywords" : { 34 | "else" : true, 35 | "while" : true, 36 | "catch" : true 37 | } 38 | }, 39 | "around" : { 40 | "operators" : { 41 | "assignmentOperators" : true, 42 | "logicalOperators" : true, 43 | "equalityOperators" : true, 44 | "relationalOperators" : true, 45 | "bitwiseOperators" : true, 46 | "additiveOperators" : true, 47 | "multiplicativeOperators" : true, 48 | "shiftOperators" : true, 49 | "rangeOperators" : false, 50 | "closureArrow" : true 51 | }, 52 | "colons" : { 53 | "beforeTypeAnnotations" : false, 54 | "afterTypeAnnotations" : true, 55 | "beforeTypeInheritanceClauses" : true, 56 | "afterTypeInheritanceClauses" : true, 57 | "beforeTypeInheritanceClausesInTypeArguments" : false, 58 | "afterTypeInheritanceClausesInTypeArguments" : true, 59 | "beforeDictionaryTypes" : false, 60 | "afterDictionaryTypes" : true, 61 | "beforeDictionaryLiteralKeyValuePair" : false, 62 | "afterDictionaryLiteralKeyValuePair" : true, 63 | "beforeAttributeArguments" : false, 64 | "afterAttributeArguments" : true 65 | } 66 | }, 67 | "within" : { 68 | "codeBraces" : true, 69 | "brackets" : false, 70 | "arrayAndDictionaryLiteralBrackets" : false, 71 | "groupingParentheses" : false, 72 | "functionDeclarationParentheses" : false, 73 | "emptyFunctionDeclarationParentheses" : false, 74 | "functionCallParentheses" : false, 75 | "emptyFunctionCallParentheses" : false, 76 | "ifParentheses" : false, 77 | "whileParentheses" : false, 78 | "switchParentheses" : false, 79 | "catchParentheses" : false, 80 | "attributeParentheses" : false 81 | }, 82 | "inTernaryOperator" : { 83 | "afterQuestionMark" : true, 84 | "afterColon" : true, 85 | "beforeColon" : true 86 | }, 87 | "comma" : { 88 | "before" : false, 89 | "after" : true, 90 | "afterWithinTypeArguments" : true 91 | }, 92 | "semicolon" : { 93 | "before" : false, 94 | "after" : true 95 | } 96 | }, 97 | "braces" : { 98 | "placement" : { 99 | "inTypeDeclarations" : 0, 100 | "inFunctions" : 0, 101 | "inOther" : 0 102 | } 103 | }, 104 | "wrapping" : { 105 | "keepWhenReformatting" : { 106 | "lineBreakes" : true, 107 | "commentAtFirstColumn" : true, 108 | "simpleBlocksAndTrailingClosuresInOneLine" : true, 109 | "simpleClosureArgumentsInOneLine" : true, 110 | "simpleFunctionsInOneLine" : true 111 | }, 112 | "ifStatement" : { 113 | "elseOnNewLine" : false 114 | }, 115 | "guardStatement" : { 116 | "elseOnNewLine" : false 117 | }, 118 | "repeatWhileStatement" : { 119 | "whileOnNewLine" : false 120 | }, 121 | "doStatement" : { 122 | "catchOnNewLine" : false 123 | }, 124 | "modifierList" : { 125 | "wrapAfterModifierList" : false 126 | }, 127 | "alignment" : { 128 | "baseClassAndAdoptedProtocolList" : { 129 | "wrapping" : 0, 130 | "alignWhenMultiline" : false 131 | }, 132 | "functionDeclarationParameters" : { 133 | "placeRightParenthesisOnNewLine" : false, 134 | "wrapping" : 0, 135 | "alignWhenMultiline" : true, 136 | "newLineAfterLeftParenthesis" : false 137 | }, 138 | "functionCallArguments" : { 139 | "placeRightParenthesisOnNewLine" : false, 140 | "wrapping" : 0, 141 | "alignWhenMultiline" : true, 142 | "newLineAfterLeftParenthesis" : false 143 | }, 144 | "chainedMethodCalls" : { 145 | "wrapping" : 0, 146 | "alignWhenMultiline" : false 147 | }, 148 | "closures" : { 149 | "parametersOnNewLineWhenMultiline" : false 150 | } 151 | } 152 | }, 153 | "blankLines" : { 154 | "keepMaximumBlankLines" : { 155 | "inDeclarations" : 2, 156 | "inCode" : 2, 157 | "beforeClosingBrace" : 2 158 | }, 159 | "minimumBlankLines" : { 160 | "beforeImports" : 1, 161 | "afterImports" : 1, 162 | "aroundTypeDeclarations" : 1, 163 | "aroundPropertyInProtocol" : 0, 164 | "aroundProperty" : 0, 165 | "aroundFunctionInProtocol" : 0, 166 | "aroundFunction" : 1, 167 | "beforeFunctionBody" : 0 168 | } 169 | }, 170 | "hardWrapAt" : 120 171 | } 172 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.2 3 | install: 4 | - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" 5 | script: 6 | - swift test 7 | env: 8 | global: 9 | - LANG=en_US.UTF-8 10 | - LC_ALL=en_US.UTF-8 11 | branches: 12 | only: 13 | - master 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwiftPM", 6 | "repositoryURL": "https://github.com/apple/swift-package-manager.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "90abb3c4c9415afee34291e66e655c58a1902784", 10 | "version": "0.1.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "swiftfmt", 8 | dependencies: [ 9 | .package(url: "https://github.com/apple/swift-package-manager.git", from: "0.1.0") 10 | ], 11 | targets: [ 12 | .target( 13 | name: "swiftfmt", 14 | dependencies: ["swiftfmt-core", "Utility"]), 15 | .target( 16 | name: "swiftfmt-core", 17 | dependencies: ["Utility"]), 18 | .testTarget( 19 | name: "tests", 20 | dependencies: ["swiftfmt-core"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftfmt 2 | [![Build Status](https://travis-ci.org/kishikawakatsumi/swiftfmt.svg?branch=master)](https://travis-ci.org/kishikawakatsumi/swiftfmt) 3 | 4 | A tool for formatting Swift code according to style guidelines. 5 | 6 | ### Live Demo 7 | 8 | https://swiftfmt.kishikawakatsumi.com/ 9 | 10 | ### A Work In Progress 11 | swiftfmt is still in active development. 12 | 13 | Requirements 14 | --------------------------------------- 15 | Swiftfmt requires [Swift trunk toolchains](https://swift.org/download/#snapshots). 16 | 17 | Installation 18 | --------------------------------------- 19 | Download and install [the latest trunk Swift development toolchain](https://swift.org/download/#snapshots). 20 | 21 | ```shell 22 | git clone https://github.com/kishikawakatsumi/swiftfmt 23 | ``` 24 | 25 | ```shell 26 | cd swiftfmt 27 | ``` 28 | 29 | ```shell 30 | ~/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swift package update 31 | ``` 32 | 33 | ```shell 34 | ~/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swift build -c release 35 | ``` 36 | 37 | Copy the file (`.build/release/swiftfmt`) to your binary location. 38 | 39 | Getting Started 40 | --------------------------------------- 41 | 42 | ```shell 43 | swiftfmt [file or directory] 44 | ``` 45 | 46 | Usage 47 | --------------------------------------- 48 | 49 | ```shell 50 | swiftfmt . 51 | ``` 52 | 53 | Configurations 54 | --------------------------------------- 55 | 56 | ### Tabs and Indents 57 | 58 | **Use tab character** 59 | 60 | ```diff 61 | git clone https://github.com/kishikawakatsumi/swiftfmt 62 | class Shape { 63 | - var numberOfSides = 0 64 | - func simpleDescription() -> String { 65 | - return "A shape with \(numberOfSides) sides." 66 | - } 67 | + var numberOfSides = 0 68 | + 69 | + func simpleDescription() -> String { 70 | + return "A shape with \(numberOfSides) sides." 71 | + } 72 | } 73 | ``` 74 | 75 | **Indent** 76 | 77 | `"indent" : 2` 78 | 79 | ```diff 80 | class Shape { 81 | - var numberOfSides = 0 82 | - func simpleDescription() -> String { 83 | - return "A shape with \(numberOfSides) sides." 84 | - } 85 | + var numberOfSides = 0 86 | + 87 | + func simpleDescription() -> String { 88 | + return "A shape with \(numberOfSides) sides." 89 | + } 90 | } 91 | ``` 92 | 93 | **Keep indents on empty lines** 94 | 95 | ```diff 96 | class Shape { 97 | var numberOfSides = 0 98 | + 99 | func simpleDescription() -> String { 100 | return "A shape with \(numberOfSides) sides." 101 | } 102 | ``` 103 | 104 | **Indent 'case' branches** 105 | 106 | ```diff 107 | let someCharacter: Character = "z" 108 | switch someCharacter { 109 | -case "a": 110 | - print("The first letter of the alphabet") 111 | -case "z": 112 | - print("The last letter of the alphabet") 113 | -default: 114 | - print("Some other character") 115 | + case "a": 116 | + print("The first letter of the alphabet") 117 | + case "z": 118 | + print("The last letter of the alphabet") 119 | + default: 120 | + print("Some other character") 121 | } 122 | ``` 123 | 124 | ### Spaces 125 | 126 | #### Before Parentheses 127 | 128 | **Method/function declaration parentheses** 129 | 130 | ```diff 131 | class Counter { 132 | var count = 0 133 | - 134 | - func increment() { 135 | + 136 | + func increment () { 137 | count += 1 138 | } 139 | 140 | - func increment(by amount: Int) { 141 | + func increment (by amount: Int) { 142 | count += amount 143 | } 144 | 145 | - func reset() { 146 | + func reset () { 147 | count = 0 148 | } 149 | } 150 | ``` 151 | 152 | **Method/function call parentheses** 153 | 154 | ```diff 155 | struct Point { 156 | var x = 0.0, y = 0.0 157 | + 158 | mutating func moveBy(x deltaX: Double, y deltaY: Double) { 159 | - self = Point(x: x + deltaX, y: y + deltaY) 160 | + self = Point (x: x + deltaX, y: y + deltaY) 161 | } 162 | } 163 | ``` 164 | 165 | **'if' parentheses** 166 | 167 | ```diff 168 | -if(temperatureInFahrenheit <= 32) { 169 | +if (temperatureInFahrenheit <= 32) { 170 | print("It's very cold. Consider wearing a scarf.") 171 | -} else if(temperatureInFahrenheit >= 86) { 172 | +} else if (temperatureInFahrenheit >= 86) { 173 | print("It's really warm. Don't forget to wear sunscreen.") 174 | } 175 | ``` 176 | 177 | **'while' parentheses** 178 | 179 | ```diff 180 | var square = 0 181 | var diceRoll = 0 182 | -while(square < finalSquare) { 183 | +while (square < finalSquare) { 184 | // roll the dice 185 | diceRoll += 1 186 | if diceRoll == 7 { diceRoll = 1 } 187 | ``` 188 | 189 | **'switch' parentheses** 190 | 191 | ```diff 192 | let someCharacter: Character = "z" 193 | -switch(someCharacter) { 194 | +switch (someCharacter) { 195 | case "a": 196 | print("The first letter of the alphabet") 197 | case "z": 198 | ``` 199 | 200 | **'catch' parentheses** 201 | 202 | ```diff 203 | do { 204 | try throwable() 205 | } catch Error.unexpected(let cause) { 206 | print("unexpected error!") 207 | -} catch(Error.unknown) { 208 | +} catch (Error.unknown) { 209 | print("unknown error!") 210 | } 211 | ``` 212 | 213 | **Attribute parentheses** 214 | 215 | ```diff 216 | -@available (swift 3.0.2) 217 | -@available (macOS 10.12, *) 218 | +@available(swift 3.0.2) 219 | +@available(macOS 10.12, *) 220 | struct MyStruct { 221 | // struct definition 222 | } 223 | ``` 224 | 225 | #### Around Operators 226 | 227 | **Assignment Operators (=, +=, ...)** 228 | 229 | ```diff 230 | -let contentHeight=40 231 | -let hasHeader=true 232 | +let contentHeight = 40 233 | +let hasHeader = true 234 | let rowHeight: Int 235 | if hasHeader { 236 | - rowHeight=contentHeight+50 237 | + rowHeight = contentHeight + 50 238 | } else { 239 | - rowHeight=contentHeight+20 240 | + rowHeight = contentHeight + 20 241 | } 242 | ``` 243 | 244 | **Logical Operators (&&, ||)** 245 | 246 | ```diff 247 | -if enteredDoorCode&&passedRetinaScan||hasDoorKey||knowsOverridePassword { 248 | +if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword { 249 | print("Welcome!") 250 | } else { 251 | print("ACCESS DENIED") 252 | ``` 253 | 254 | **Equality Operator (==)** 255 | 256 | ```diff 257 | let name = "world" 258 | -if name=="world" { 259 | +if name == "world" { 260 | print("hello, world") 261 | } else { 262 | print("I'm sorry \(name), but I don't recognize you") 263 | ``` 264 | 265 | **Relational Operators (<, >, <=, >=)** 266 | 267 | ```diff 268 | -2>1 // true because 2 is greater than 1 269 | -1<2 // true because 1 is less than 2 270 | -1>=1 // true because 1 is greater than or equal to 1 271 | -2<=1 // false because 2 is not less than or equal to 1 272 | +2 > 1 // true because 2 is greater than 1 273 | +1 < 2 // true because 1 is less than 2 274 | +1 >= 1 // true because 1 is greater than or equal to 1 275 | +2 <= 1 // false because 2 is not less than or equal to 1 276 | ``` 277 | 278 | **Bitwise Operators (&, |, ^)** 279 | 280 | ```diff 281 | for i in 0..>)** 306 | 307 | ```diff 308 | for i in 0..)** 326 | 327 | ```diff 328 | -func greet(person: String)->String { 329 | +func greet(person: String) -> String { 330 | let greeting = "Hello, " + person + "!" 331 | return greeting 332 | } 333 | ``` 334 | 335 | #### Before Left Brace 336 | 337 | **Type declaration left brace** 338 | 339 | ```diff 340 | -struct Resolution{ 341 | +struct Resolution { 342 | var width = 0 343 | var height = 0 344 | } 345 | -class VideoMode{ 346 | + 347 | +class VideoMode { 348 | var resolution = Resolution() 349 | var interlaced = false 350 | var frameRate = 0.0 351 | ``` 352 | 353 | **Method/function left brace** 354 | 355 | ```diff 356 | -func greet(person: String) -> String{ 357 | +func greet(person: String) -> String { 358 | let greeting = "Hello, " + person + "!" 359 | return greeting 360 | } 361 | ``` 362 | 363 | **'if' left brace** 364 | 365 | ```diff 366 | var temperatureInFahrenheit = 30 367 | -if temperatureInFahrenheit <= 32{ 368 | +if temperatureInFahrenheit <= 32 { 369 | print("It's very cold. Consider wearing a scarf.") 370 | } 371 | ``` 372 | 373 | **'else' left brace** 374 | 375 | ```diff 376 | temperatureInFahrenheit = 40 377 | if temperatureInFahrenheit <= 32 { 378 | print("It's very cold. Consider wearing a scarf.") 379 | -} else{ 380 | +} else { 381 | print("It's not that cold. Wear a t-shirt.") 382 | } 383 | ``` 384 | 385 | **'for' left brace** 386 | 387 | ```diff 388 | let names = ["Anna", "Alex", "Brian", "Jack"] 389 | -for name in names{ 390 | +for name in names { 391 | print("Hello, \(name)!") 392 | } 393 | ``` 394 | 395 | **'while' left brace** 396 | 397 | ```diff 398 | var square = 0 399 | var diceRoll = 0 400 | -while square < finalSquare{ 401 | +while square < finalSquare { 402 | // roll the dice 403 | diceRoll += 1 404 | if diceRoll == 7 { diceRoll = 1 } 405 | ``` 406 | 407 | **'do' left brace** 408 | 409 | ```diff 410 | -do{ 411 | +do { 412 | try throwable() 413 | } catch Error.unexpected(let cause) { 414 | print("unexpected error!") 415 | ``` 416 | 417 | **'switch' left brace** 418 | 419 | ```diff 420 | let someCharacter: Character = "z" 421 | -switch someCharacter{ 422 | +switch someCharacter { 423 | case "a": 424 | print("The first letter of the alphabet") 425 | case "z": 426 | ``` 427 | 428 | **'catch' left brace** 429 | 430 | ```diff 431 | do { 432 | try throwable() 433 | -} catch Error.unexpected(let cause){ 434 | +} catch Error.unexpected(let cause) { 435 | print("unexpected error!") 436 | -} catch (Error.unknown){ 437 | +} catch (Error.unknown) { 438 | print("unknown error!") 439 | } 440 | ``` 441 | 442 | #### Before Keywords 443 | 444 | **'else' keyword** 445 | 446 | ```diff 447 | temperatureInFahrenheit = 40 448 | if temperatureInFahrenheit <= 32 { 449 | print("It's very cold. Consider wearing a scarf.") 450 | -}else { 451 | +} else { 452 | print("It's not that cold. Wear a t-shirt.") 453 | } 454 | ``` 455 | 456 | **'while' keyword** 457 | 458 | ```diff 459 | if diceRoll == 7 { diceRoll = 1 } 460 | // move by the rolled amount 461 | square += diceRoll 462 | -}while square < finalSquare 463 | +} while square < finalSquare 464 | print("Game over!") 465 | ``` 466 | 467 | **'catch' keyword** 468 | 469 | ```diff 470 | do { 471 | try throwable() 472 | -}catch Error.unexpected(let cause) { 473 | +} catch Error.unexpected(let cause) { 474 | print("unexpected error!") 475 | -}catch (Error.unknown) { 476 | +} catch (Error.unknown) { 477 | print("unknown error!") 478 | } 479 | ``` 480 | 481 | #### Within 482 | 483 | **Code braces** 484 | 485 | **Brackets** 486 | 487 | `"brackets" : true` 488 | 489 | ```diff 490 | len = 10 491 | } 492 | repeat { 493 | - text[ext++] = "$" 494 | + text[ ext++ ] = "$" 495 | } while (ext < len) 496 | 497 | len = len > 10000 ? len : 0 498 | ``` 499 | 500 | **Array and dictionary literal brackets** 501 | 502 | `"arrayAndDictionaryLiteralBrackets" : true` 503 | 504 | ```diff 505 | -var shoppingList = ["Eggs", "Milk"] 506 | -shoppingList += ["Baking Powder"] 507 | -shoppingList += ["Chocolate Spread", "Cheese", "Butter"] 508 | +var shoppingList = [ "Eggs", "Milk" ] 509 | +shoppingList += [ "Baking Powder" ] 510 | +shoppingList += [ "Chocolate Spread", "Cheese", "Butter" ] 511 | ``` 512 | 513 | **Grouping parenthesese** 514 | 515 | ```diff 516 | var ext = x 517 | var len = y 518 | for i in 0.. String { 532 | +func greet( person: String ) -> String { 533 | let greeting = "Hello, " + person + "!" 534 | return greeting 535 | } 536 | ``` 537 | 538 | **Empty method/function declaration parenthesese** 539 | 540 | ```diff 541 | -func sayHelloWorld() -> String { 542 | +func sayHelloWorld( ) -> String { 543 | return "hello, world" 544 | } 545 | print(sayHelloWorld()) 546 | ``` 547 | 548 | **Method/function call parenthesese** 549 | 550 | ```diff 551 | func greet(person: String, alreadyGreeted: Bool) -> String { 552 | if alreadyGreeted { 553 | - return greetAgain(person: person) 554 | + return greetAgain( person: person ) 555 | } else { 556 | - return greet(person: person) 557 | + return greet( person: person ) 558 | } 559 | } 560 | ``` 561 | 562 | **Empty method/function call parenthesese** 563 | 564 | ```diff 565 | func sayHelloWorld() -> String { 566 | return "hello, world" 567 | } 568 | -print(sayHelloWorld()) 569 | +print(sayHelloWorld( )) 570 | ``` 571 | 572 | **'if' parenthesese** 573 | 574 | ```diff 575 | while square < finalSquare { 576 | // roll the dice 577 | diceRoll += 1 578 | - if (diceRoll == 7) { diceRoll = 1 } 579 | + if ( diceRoll == 7 ) { diceRoll = 1 } 580 | // move by the rolled amount 581 | square += diceRoll 582 | - if (square < board.count) { 583 | + if ( square < board.count ) { 584 | // if we're still on the board, move up or down for a snake or a ladder 585 | square += board[square] 586 | } 587 | ``` 588 | 589 | **'while' parenthesese** 590 | 591 | ```diff 592 | var square = 0 593 | var diceRoll = 0 594 | -while (square < finalSquare) { 595 | +while ( square < finalSquare ) { 596 | repeat { 597 | // move up or down for a snake or ladder 598 | square += board[square] 599 | @@ -9,5 +9,5 @@ while (square < finalSquare) { 600 | if diceRoll == 7 { diceRoll = 1 } 601 | // move by the rolled amount 602 | square += diceRoll 603 | - } while (square < finalSquare) 604 | + } while ( square < finalSquare ) 605 | } 606 | ``` 607 | 608 | **'switch' parenthesese** 609 | 610 | ```diff 611 | let someCharacter: Character = "z" 612 | -switch (someCharacter) { 613 | +switch ( someCharacter ) { 614 | case "a": 615 | print("The first letter of the alphabet") 616 | case "z": 617 | ``` 618 | 619 | **'catch' parenthesese** 620 | 621 | ```diff 622 | try throwable() 623 | } catch Error.unexpected(let cause) { 624 | print("unexpected error!") 625 | -} catch (Error.unknown) { 626 | +} catch ( Error.unknown ) { 627 | print("unknown error!") 628 | } 629 | ``` 630 | 631 | **Attribute parenthesese** 632 | 633 | ```diff 634 | -@available(swift 3.0.2) 635 | -@available(macOS 10.12, *) 636 | +@available( swift 3.0.2 ) 637 | +@available( macOS 10.12, * ) 638 | struct MyStruct { 639 | // struct definition 640 | } 641 | ``` 642 | 643 | #### In Ternary Operator (:?) 644 | 645 | **After '?'** 646 | 647 | ```diff 648 | let contentHeight = 40 649 | let hasHeader = true 650 | -let rowHeight = contentHeight + (hasHeader ?50:20) 651 | +let rowHeight = contentHeight + (hasHeader ? 50:20) 652 | ``` 653 | 654 | **Before ':'** 655 | 656 | ```diff 657 | let contentHeight = 40 658 | let hasHeader = true 659 | -let rowHeight = contentHeight + (hasHeader ?50:20) 660 | +let rowHeight = contentHeight + (hasHeader ?50: 20) 661 | ``` 662 | 663 | **After ':'** 664 | 665 | ```diff 666 | let contentHeight = 40 667 | let hasHeader = true 668 | -let rowHeight = contentHeight + (hasHeader ?50:20) 669 | +let rowHeight = contentHeight + (hasHeader ?50: 20) 670 | ``` 671 | 672 | #### Around colons 673 | 674 | **Before colon in type annotations** 675 | 676 | ```diff 677 | -func greet(person: String, alreadyGreeted: Bool) -> String { 678 | +func greet(person : String, alreadyGreeted : Bool) -> String { 679 | if alreadyGreeted { 680 | - return greetAgain(person: person) 681 | + return greetAgain(person : person) 682 | } else { 683 | - return greet(person: person) 684 | + return greet(person : person) 685 | } 686 | } 687 | ``` 688 | 689 | **After colon in type annotations** 690 | 691 | ```diff 692 | -func greet(person:String, alreadyGreeted:Bool) -> String { 693 | +func greet(person: String, alreadyGreeted: Bool) -> String { 694 | if alreadyGreeted { 695 | - return greetAgain(person:person) 696 | + return greetAgain(person: person) 697 | } else { 698 | - return greet(person:person) 699 | + return greet(person: person) 700 | } 701 | } 702 | ``` 703 | 704 | **Before colon in type inheritance clauses** 705 | 706 | ```diff 707 | -class Movie: MediaItem { 708 | +class Movie : MediaItem { 709 | var director: String 710 | init(name: String, director: String) { 711 | self.director = director 712 | ``` 713 | 714 | **After colon in type inheritance clauses** 715 | 716 | ``` 717 | -class Movie :MediaItem { 718 | +class Movie : MediaItem { 719 | var director: String 720 | init(name: String, director: String) { 721 | self.director = director 722 | ``` 723 | 724 | **Before colon in dictionary types** 725 | 726 | ```diff 727 | -var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 728 | -var emptyDictionary: [String: Int] = [:] 729 | +var airports: [String : String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 730 | +var emptyDictionary: [String : Int] = [:] 731 | ``` 732 | 733 | **After colon in dictionary types** 734 | 735 | ```diff 736 | -var airports: [String:String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 737 | -var emptyDictionary: [String:Int] = [:] 738 | +var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 739 | +var emptyDictionary: [String: Int] = [:] 740 | ``` 741 | 742 | **Before colon in dictionary literal 'key:value' pair** 743 | 744 | ```diff 745 | -var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 746 | +var airports: [String: String] = ["YYZ" : "Toronto Pearson", "DUB" : "Dublin"] 747 | var emptyDictionary: [String: Int] = [:] 748 | ``` 749 | 750 | **After colon in dictionary literal 'key:value' pair** 751 | 752 | ```diff 753 | -var airports: [String: String] = ["YYZ":"Toronto Pearson", "DUB":"Dublin"] 754 | +var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 755 | var emptyDictionary: [String: Int] = [:] 756 | ``` 757 | 758 | #### Within Type Arguments 759 | 760 | **After comma** 761 | 762 | ```diff 763 | -func allItemsMatch 764 | +func allItemsMatch 765 | (_ someContainer: C1,_ anotherContainer: C2) -> Bool 766 | -where C1.Item == C2.Item,C1.Item:Equatable { 767 | +where C1.Item == C2.Item, C1.Item: Equatable { 768 | // Check that both containers contain the same number of items. 769 | if someContainer.count != anotherContainer.count { 770 | return false 771 | ``` 772 | 773 | #### Other 774 | 775 | **Before comma** 776 | 777 | ```diff 778 | enum State { 779 | - case none, all 780 | + case none , all 781 | } 782 | 783 | -typealias Status = (Int, String) 784 | +typealias Status = (Int , String) 785 | 786 | -func +++(l: String, r: String) -> String { 787 | +func +++(l: String , r: String) -> String { 788 | return "" 789 | } 790 | 791 | -let array = ["One", "Two", "Three", "Four", "Five"] 792 | +let array = ["One" , "Two" , "Three" , "Four" , "Five"] 793 | 794 | -let dictionary = ["One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5] 795 | +let dictionary = ["One": 1 , "Two": 2 , "Three": 3 , "Four": 4 , "Five": 5] 796 | ``` 797 | 798 | **After comma** 799 | 800 | ```diff 801 | enum State { 802 | - case none,all 803 | + case none, all 804 | } 805 | 806 | -typealias Status = (Int,String) 807 | +typealias Status = (Int, String) 808 | 809 | struct S { 810 | } 811 | @@ -15,16 +15,16 @@ struct S { 812 | protocol P { 813 | } 814 | 815 | -func +++(l: String,r: String) -> String { 816 | +func +++(l: String, r: String) -> String { 817 | return "" 818 | } 819 | 820 | -let array = ["One","Two","Three","Four","Five"] 821 | +let array = ["One", "Two", "Three", "Four", "Five"] 822 | var emptyArray = [] 823 | 824 | -let dictionary = ["One": 1,"Two": 2,"Three": 3,"Four": 4,"Five": 5] 825 | +let dictionary = ["One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5] 826 | var emptyDictionary: [String: Int] = [:] 827 | ``` 828 | 829 | **Before semicolon** 830 | 831 | **After semicolon** 832 | 833 | ### Wrapping and Braces 834 | 835 | ### Blank Lines 836 | 837 | #### Keep Maximum Blank Lines 838 | 839 | **In declarations** 840 | 841 | **In code** 842 | 843 | **Before '}'** 844 | 845 | #### Minimum Blank Lines 846 | 847 | **Before imports** 848 | 849 | `"beforeImports" : 1` 850 | 851 | ```diff 852 | // 853 | // Created by Kishikawa Katsumi on 2018/02/14. 854 | // 855 | + 856 | import Foundation 857 | import Basic 858 | import SwiftSyntax 859 | ``` 860 | 861 | **After imports** 862 | 863 | `"afterImports" : 1` 864 | 865 | ```diff 866 | import Foundation 867 | import Basic 868 | import SwiftSyntax 869 | + 870 | public struct Processor { 871 | private let options: [String] 872 | ``` 873 | 874 | **Around type declarations** 875 | 876 | `"aroundTypeDeclarations" : 1` 877 | 878 | ```diff 879 | @@ -9,6 +9,7 @@ fileprivate class Bracket : Indentation { 880 | self.lineNumber = lineNumber 881 | } 882 | } 883 | + 884 | fileprivate class SwitchStatement { 885 | var lineNumber: Int 886 | 887 | @@ -16,6 +17,7 @@ fileprivate class SwitchStatement { 888 | self.lineNumber = lineNumber 889 | } 890 | } 891 | + 892 | fileprivate class CaseBranch { 893 | var lineNumber: Int 894 | 895 | @@ -23,6 +25,7 @@ fileprivate class CaseBranch { 896 | self.lineNumber = lineNumber 897 | } 898 | } 899 | + 900 | protocol Indentation { 901 | var indent: Bool { get set } 902 | var alignment: Int { get set } 903 | ``` 904 | 905 | **Around property in protocol** 906 | 907 | `"aroundPropertyInProtocol" : 0` 908 | 909 | **Around property** 910 | 911 | `"aroundProperty" : 1` 912 | 913 | **Around method/function in protocol** 914 | 915 | `"aroundFunctionInProtocol" : 0` 916 | 917 | **Around method/function** 918 | 919 | `"aroundFunction" : 1` 920 | 921 | **Before method/function body** 922 | 923 | `"beforeFunctionBody" : 0` 924 | 925 | Author 926 | --------------------------------------- 927 | Kishikawa Katsumi, kishikawakatsumi@mac.com 928 | 929 | License 930 | --------------------------------------- 931 | Swiftfmt is available under the Apache 2.0 license. See the LICENSE file for more info. 932 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/AlignmentFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlignmentFormatter.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/21. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public struct AlignmentFormatter : LineFormatter { 12 | public func format(_ lines: [Line], _ configuration: Configuration) -> [Line] { 13 | var formatted = [Line]() 14 | var temp = [Line]() 15 | var iterator = lines.enumerated().makeIterator() 16 | while let (index, line) = iterator.next() { 17 | if let token = ifStatementToken(line: line) { 18 | switch token { 19 | case .code(let code): 20 | if let context = code.contexts.last { 21 | switch context.node { 22 | case (let node) as IfStmtSyntax: 23 | let visitor = TokenVisitor() 24 | _ = visitor.visit(node.conditions) 25 | if visitor.newlines > 0 { 26 | let align = alignment(lines: lines, startIndex: index, target: visitor.tokens[0]) 27 | while let (i, line) = iterator.next() { 28 | temp.append(Line(tokens: line.tokens, indentationLevel: line.indentationLevel, alignment: line.alignment + align)) 29 | if i >= index + visitor.newlines { 30 | break 31 | } 32 | } 33 | } 34 | default: 35 | break 36 | } 37 | } 38 | default: 39 | break 40 | } 41 | } 42 | formatted.append(line) 43 | formatted.append(contentsOf: temp) 44 | temp.removeAll() 45 | } 46 | return formatted 47 | } 48 | 49 | private class TokenVisitor : SyntaxVisitor { 50 | var tokens = [TokenSyntax]() 51 | var newlines = 0 52 | 53 | override func visit(_ token: TokenSyntax) { 54 | tokens.append(token) 55 | token.leadingTrivia.forEach { (piece) in 56 | countNewlines(piece) 57 | } 58 | token.trailingTrivia.forEach { (piece) in 59 | countNewlines(piece) 60 | } 61 | } 62 | 63 | private func countNewlines(_ piece: TriviaPiece) { 64 | switch piece { 65 | case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count): 66 | newlines += count 67 | default: 68 | break 69 | } 70 | } 71 | } 72 | 73 | private func ifStatementToken(line: Line) -> Token? { 74 | for token in line.tokens { 75 | switch token { 76 | case .code(let code): 77 | if let context = code.contexts.last, context.node is IfStmtSyntax, code.text == "if" { 78 | return token 79 | } 80 | default: 81 | break 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | private func alignment(lines: [Line], startIndex: Int, target: TokenSyntax) -> Int { 88 | for i in startIndex.. [Line] { 13 | var formatted = [Line]() 14 | var previousKind = Kind.blank 15 | var isInFunctionBody = false 16 | 17 | var groups = [LineGroup]() 18 | var group = LineGroup() 19 | var previousNode: Syntax? 20 | var pendingLines = [Line]() 21 | for line in lines { 22 | if let node = lineNode(of: line) { 23 | if previousNode == nil { 24 | if lineKind(of: line) == .importDeclaration { 25 | group.lines.append(line) 26 | groups.append(group) 27 | for pendingLine in pendingLines.reversed() { 28 | if pendingLine.isEmpty { 29 | group.lines.insert(pendingLine, at: 0) 30 | } else { 31 | group = LineGroup() 32 | group.lines.insert(pendingLine, at: 0) 33 | groups.insert(group, at: 0) 34 | } 35 | } 36 | } else { 37 | group.lines.append(contentsOf: pendingLines) 38 | group.lines.append(line) 39 | groups.append(group) 40 | } 41 | previousNode = node 42 | pendingLines.removeAll() 43 | continue 44 | } else if !(previousNode! == node) { 45 | group = LineGroup() 46 | groups.append(group) 47 | } 48 | group.lines.append(contentsOf: pendingLines) 49 | group.lines.append(line) 50 | pendingLines.removeAll() 51 | previousNode = node 52 | } else { 53 | pendingLines.append(line) 54 | } 55 | } 56 | if !pendingLines.isEmpty { 57 | group = LineGroup() 58 | groups.append(group) 59 | group.lines.append(contentsOf: pendingLines) 60 | } 61 | 62 | for (index, group) in groups.enumerated() { 63 | let kind = groupKind(of: group) 64 | defer { 65 | if kind != .attribute && kind != .blank && kind != .other { 66 | previousKind = kind 67 | } 68 | } 69 | 70 | if (index == 0) { 71 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 72 | format(formattedLines: &formatted, configuration: configuration.blankLines, currentGroup: group, keepMaximumBlankLines: keepMaximumBlankLines) 73 | continue 74 | } 75 | 76 | switch kind { 77 | case .importDeclaration: 78 | let minimumBlankLines = max(configuration.blankLines.minimumBlankLines.beforeImports, 79 | minimumBlankLinesAfterPreviousDeclaration(previousLineKind: previousKind, configuration: configuration)) 80 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 81 | format(formattedLines: &formatted, 82 | configuration: configuration.blankLines, 83 | currentGroup: group, 84 | previousLineKind: previousKind, 85 | minimumBlankLines: minimumBlankLines, 86 | keepMaximumBlankLines: keepMaximumBlankLines) 87 | isInFunctionBody = false 88 | case .typeDeclaration: 89 | let minimumBlankLines: Int 90 | if !isEndOfCodeBlock(line: group.lines[0]) { 91 | minimumBlankLines = max(configuration.blankLines.minimumBlankLines.aroundTypeDeclarations, 92 | minimumBlankLinesAfterPreviousDeclaration(previousLineKind: previousKind, configuration: configuration)) 93 | } else { 94 | minimumBlankLines = 0 95 | } 96 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 97 | format(formattedLines: &formatted, 98 | configuration: configuration.blankLines, 99 | currentGroup: group, 100 | previousLineKind: previousKind, 101 | minimumBlankLines: minimumBlankLines, 102 | keepMaximumBlankLines: keepMaximumBlankLines) 103 | isInFunctionBody = false 104 | case .propertyDeclarationInProtocol: 105 | let minimumBlankLinesAfterPrevious: Int 106 | if previousKind == .propertyDeclarationInProtocol || previousKind == .functionDeclarationInProtocol { 107 | minimumBlankLinesAfterPrevious = minimumBlankLinesAfterPreviousDeclaration(previousLineKind: previousKind, configuration: configuration) 108 | } else { 109 | minimumBlankLinesAfterPrevious = 0 110 | } 111 | let minimumBlankLines: Int 112 | if !isEndOfCodeBlock(line: group.lines[0]) { 113 | minimumBlankLines = max(configuration.blankLines.minimumBlankLines.aroundPropertyInProtocol, minimumBlankLinesAfterPrevious) 114 | } else { 115 | minimumBlankLines = 0 116 | } 117 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 118 | format(formattedLines: &formatted, 119 | configuration: configuration.blankLines, 120 | currentGroup: group, 121 | previousLineKind: previousKind, 122 | minimumBlankLines: minimumBlankLines, 123 | keepMaximumBlankLines: keepMaximumBlankLines) 124 | isInFunctionBody = false 125 | case .propertyDeclaration: 126 | let minimumBlankLinesAfterPrevious: Int 127 | if previousKind == .propertyDeclaration || previousKind == .functionDeclaration { 128 | minimumBlankLinesAfterPrevious = minimumBlankLinesAfterPreviousDeclaration(previousLineKind: previousKind, configuration: configuration) 129 | } else { 130 | minimumBlankLinesAfterPrevious = 0 131 | } 132 | let minimumBlankLines = max(configuration.blankLines.minimumBlankLines.aroundProperty, minimumBlankLinesAfterPrevious) 133 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 134 | format(formattedLines: &formatted, 135 | configuration: configuration.blankLines, 136 | currentGroup: group, 137 | previousLineKind: previousKind, 138 | minimumBlankLines: minimumBlankLines, 139 | keepMaximumBlankLines: keepMaximumBlankLines) 140 | isInFunctionBody = false 141 | case .functionDeclarationInProtocol: 142 | let minimumBlankLinesAfterPrevious: Int 143 | if previousKind == .propertyDeclarationInProtocol || previousKind == .functionDeclarationInProtocol { 144 | minimumBlankLinesAfterPrevious = minimumBlankLinesAfterPreviousDeclaration(previousLineKind: previousKind, configuration: configuration) 145 | } else { 146 | minimumBlankLinesAfterPrevious = 0 147 | } 148 | let minimumBlankLines = max(configuration.blankLines.minimumBlankLines.aroundFunctionInProtocol, minimumBlankLinesAfterPrevious) 149 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 150 | format(formattedLines: &formatted, 151 | configuration: configuration.blankLines, 152 | currentGroup: group, 153 | previousLineKind: previousKind, 154 | minimumBlankLines: minimumBlankLines, 155 | keepMaximumBlankLines: keepMaximumBlankLines) 156 | isInFunctionBody = false 157 | case .functionDeclaration: 158 | let minimumBlankLinesAfterPrevious: Int 159 | if previousKind == .propertyDeclaration || previousKind == .functionDeclaration { 160 | minimumBlankLinesAfterPrevious = minimumBlankLinesAfterPreviousDeclaration(previousLineKind: previousKind, configuration: configuration) 161 | } else { 162 | minimumBlankLinesAfterPrevious = 0 163 | } 164 | let minimumBlankLines: Int 165 | if previousKind != .typeDeclaration && !isEndOfCodeBlock(line: group.lines[0]) { 166 | minimumBlankLines = max(configuration.blankLines.minimumBlankLines.aroundFunction, minimumBlankLinesAfterPrevious) 167 | } else { 168 | minimumBlankLines = 0 169 | } 170 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inDeclarations 171 | format(formattedLines: &formatted, 172 | configuration: configuration.blankLines, 173 | currentGroup: group, 174 | previousLineKind: previousKind, 175 | minimumBlankLines: minimumBlankLines, 176 | keepMaximumBlankLines: keepMaximumBlankLines) 177 | if !isEndOfCodeBlock(line: group.lines[0]) { 178 | isInFunctionBody = true 179 | } 180 | case .attribute, .blank, .other: 181 | let keepMaximumBlankLines = configuration.blankLines.keepMaximumBlankLines.inCode 182 | format(formattedLines: &formatted, configuration: configuration.blankLines, currentGroup: group, keepMaximumBlankLines: keepMaximumBlankLines) 183 | } 184 | } 185 | 186 | return formatted 187 | } 188 | 189 | private func format(formattedLines: inout [Line], configuration: Configuration.BlankLines, currentGroup: LineGroup, previousLineKind: Kind, 190 | minimumBlankLines: Int, keepMaximumBlankLines: Int) { 191 | var lines = [Line]() 192 | var preservedBlankLines = [Line]() 193 | 194 | var iterator = currentGroup.lines.makeIterator() 195 | while let line = iterator.next() { 196 | if line.isEmpty { 197 | preservedBlankLines.append(line) 198 | } else { 199 | lines.append(line) 200 | break 201 | } 202 | } 203 | 204 | let currentLineKind = lineKind(of: currentGroup.lines.last!) 205 | if currentLineKind != previousLineKind || currentLineKind == .typeDeclaration || 206 | currentLineKind == .propertyDeclarationInProtocol || currentLineKind == .propertyDeclaration || 207 | currentLineKind == .functionDeclarationInProtocol || currentLineKind == .functionDeclaration { 208 | if preservedBlankLines.count < minimumBlankLines { 209 | formattedLines.append(contentsOf: preservedBlankLines) 210 | for _ in 0..<(minimumBlankLines - preservedBlankLines.count) { 211 | formattedLines.append(Line.blank()) 212 | } 213 | } else { 214 | formattedLines.append(contentsOf: preservedBlankLines.prefix(keepMaximumBlankLines)) 215 | } 216 | } 217 | 218 | formattedLines.append(contentsOf: lines) 219 | lines.removeAll() 220 | 221 | preservedBlankLines.removeAll() 222 | while let line = iterator.next() { 223 | if line.isEmpty { 224 | preservedBlankLines.append(line) 225 | } else { 226 | if isEndOfCodeBlock(line: line) { 227 | lines.append(contentsOf: preservedBlankLines.prefix(configuration.keepMaximumBlankLines.beforeClosingBrace)) 228 | } else { 229 | lines.append(contentsOf: preservedBlankLines.prefix(keepMaximumBlankLines)) 230 | } 231 | lines.append(line) 232 | preservedBlankLines.removeAll() 233 | } 234 | } 235 | if !preservedBlankLines.isEmpty { 236 | if let line = lines.last, isEndOfCodeBlock(line: line) { 237 | lines.append(contentsOf: preservedBlankLines.prefix(configuration.keepMaximumBlankLines.beforeClosingBrace)) 238 | } else { 239 | lines.append(contentsOf: preservedBlankLines.prefix(keepMaximumBlankLines)) 240 | } 241 | } 242 | formattedLines.append(contentsOf: lines) 243 | } 244 | 245 | private func format(formattedLines: inout [Line], configuration: Configuration.BlankLines, currentGroup: LineGroup, keepMaximumBlankLines: Int) { 246 | var lines = [Line]() 247 | var preservedBlankLines = [Line]() 248 | var iterator = currentGroup.lines.makeIterator() 249 | while let line = iterator.next() { 250 | if line.isEmpty { 251 | preservedBlankLines.append(line) 252 | } else { 253 | if isEndOfCodeBlock(line: line) { 254 | lines.append(contentsOf: preservedBlankLines.prefix(configuration.keepMaximumBlankLines.beforeClosingBrace)) 255 | } else { 256 | lines.append(contentsOf: preservedBlankLines.prefix(keepMaximumBlankLines)) 257 | } 258 | lines.append(line) 259 | preservedBlankLines.removeAll() 260 | } 261 | } 262 | if !preservedBlankLines.isEmpty { 263 | if let line = lines.last, isEndOfCodeBlock(line: line) { 264 | lines.append(contentsOf: preservedBlankLines.prefix(configuration.keepMaximumBlankLines.beforeClosingBrace)) 265 | } else { 266 | lines.append(contentsOf: preservedBlankLines.prefix(keepMaximumBlankLines)) 267 | } 268 | } 269 | formattedLines.append(contentsOf: lines) 270 | } 271 | 272 | private func minimumBlankLinesAfterPreviousDeclaration(previousLineKind: Kind, configuration: Configuration) -> Int { 273 | switch previousLineKind { 274 | case .importDeclaration: 275 | return configuration.blankLines.minimumBlankLines.afterImports 276 | case .typeDeclaration: 277 | return configuration.blankLines.minimumBlankLines.aroundTypeDeclarations 278 | case .propertyDeclarationInProtocol: 279 | return configuration.blankLines.minimumBlankLines.aroundPropertyInProtocol 280 | case .propertyDeclaration: 281 | return configuration.blankLines.minimumBlankLines.aroundProperty 282 | case .functionDeclarationInProtocol: 283 | return configuration.blankLines.minimumBlankLines.aroundFunctionInProtocol 284 | case .functionDeclaration: 285 | return configuration.blankLines.minimumBlankLines.aroundFunction 286 | case .attribute, .blank, .other: 287 | return 0 288 | } 289 | } 290 | 291 | private func isOneLineDeclarationBlock(line: Line) -> Bool { 292 | let kind = lineKind(of: line) 293 | guard kind == .typeDeclaration || kind == .functionDeclaration || kind == .functionDeclarationInProtocol else { 294 | return false 295 | } 296 | let code = "\(line)".trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: ";", with: "") 297 | return code.hasSuffix("}") && code.filter { $0 == "{" }.count == code.filter { $0 == "}" }.count 298 | } 299 | 300 | private func isEndOfCodeBlock(line: Line) -> Bool { 301 | let code = "\(line)".trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: ";", with: "") 302 | return code.hasSuffix("}") && code.filter { $0 == "{" }.count != code.filter { $0 == "}" }.count // Exclude one line declaration case 303 | } 304 | 305 | private func isClosingBrace(line: Line) -> Bool { 306 | let code = "\(line)".trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: ";", with: "") 307 | return code.hasSuffix("}") 308 | } 309 | 310 | private func lineNode(of line: Line) -> Syntax? { 311 | for token in line.tokens.reversed() { 312 | switch token { 313 | case .code(let code): 314 | if let context = code.contexts.last { 315 | switch context.node { 316 | case is ImportDeclSyntax, is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax, is ProtocolDeclSyntax, 317 | is VariableDeclSyntax, is FunctionDeclSyntax: 318 | return context.node 319 | case is AttributeSyntax: 320 | if let previousContext = code.contexts.dropLast().last { 321 | switch previousContext.node { 322 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax, is ProtocolDeclSyntax, 323 | is VariableDeclSyntax, is FunctionDeclSyntax: 324 | return previousContext.node 325 | default: 326 | return context.node 327 | } 328 | } else { 329 | return context.node 330 | } 331 | default: 332 | return context.node 333 | } 334 | } 335 | case .backtick: 336 | break 337 | case .lineComment, .blockComment: 338 | break 339 | case .whitespace, .newline: 340 | break 341 | } 342 | } 343 | return nil 344 | } 345 | 346 | private func lineKind(of line: Line) -> Kind { 347 | var texts = [Token]() 348 | for token in line.tokens.reversed() { 349 | switch token { 350 | case .code(let code): 351 | if let context = code.contexts.last { 352 | switch context.node { 353 | case is ImportDeclSyntax: 354 | return .importDeclaration 355 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax, is ProtocolDeclSyntax: // UnknownDecl is enum 356 | return .typeDeclaration 357 | case is VariableDeclSyntax: 358 | if let context = code.contexts.dropLast().last { 359 | switch context.node { 360 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax: 361 | return .propertyDeclaration 362 | case is ProtocolDeclSyntax: 363 | return .propertyDeclarationInProtocol 364 | default: 365 | break 366 | } 367 | } else { 368 | return .propertyDeclaration 369 | } 370 | texts.append(token) 371 | case is FunctionDeclSyntax: 372 | if let context = code.contexts.dropLast().last { 373 | switch context.node { 374 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax: 375 | return .functionDeclaration 376 | case is ProtocolDeclSyntax: 377 | return .functionDeclarationInProtocol 378 | default: 379 | break 380 | } 381 | } else { 382 | return .functionDeclaration 383 | } 384 | texts.append(token) 385 | case is AttributeSyntax: 386 | return .attribute 387 | default: 388 | texts.append(token) 389 | } 390 | } else { 391 | texts.append(token) 392 | } 393 | case .backtick: 394 | texts.append(token) 395 | case .lineComment, .blockComment: 396 | texts.append(token) 397 | case .whitespace, .newline: 398 | break 399 | } 400 | } 401 | if texts.isEmpty { 402 | return .blank 403 | } else { 404 | return .other 405 | } 406 | } 407 | 408 | private func groupKind(of group: LineGroup) -> Kind { 409 | var texts = [Token]() 410 | let tokens = group.lines.flatMap { $0.tokens } 411 | for token in tokens.reversed() { 412 | switch token { 413 | case .code(let code): 414 | if let context = code.contexts.last { 415 | switch context.node { 416 | case is ImportDeclSyntax: 417 | return .importDeclaration 418 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax, is ProtocolDeclSyntax: // UnknownDecl is enum 419 | if code.text == "struct" || code.text == "class" || code.text == "enum" || code.text == "extension" || code.text == "protocol" { 420 | return .typeDeclaration 421 | } 422 | case is VariableDeclSyntax: 423 | if code.text == "let" || code.text == "var" { 424 | if let context = code.contexts.dropLast().last { 425 | switch context.node { 426 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax: 427 | return .propertyDeclaration 428 | case is ProtocolDeclSyntax: 429 | return .propertyDeclarationInProtocol 430 | default: 431 | break 432 | } 433 | } else { 434 | return .propertyDeclaration 435 | } 436 | } 437 | texts.append(token) 438 | case is FunctionDeclSyntax: 439 | if code.text == "func" { 440 | if let context = code.contexts.dropLast().last { 441 | switch context.node { 442 | case is StructDeclSyntax, is ClassDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax: 443 | return .functionDeclaration 444 | case is ProtocolDeclSyntax: 445 | return .functionDeclarationInProtocol 446 | default: 447 | break 448 | } 449 | } else { 450 | return .functionDeclaration 451 | } 452 | } 453 | texts.append(token) 454 | case is AttributeSyntax: 455 | return .attribute 456 | default: 457 | texts.append(token) 458 | } 459 | } else { 460 | texts.append(token) 461 | } 462 | case .backtick: 463 | texts.append(token) 464 | case .lineComment, .blockComment: 465 | texts.append(token) 466 | case .whitespace, .newline: 467 | break 468 | } 469 | } 470 | if texts.isEmpty { 471 | return .blank 472 | } else { 473 | return .other 474 | } 475 | } 476 | 477 | private enum Kind { 478 | case importDeclaration 479 | case typeDeclaration 480 | case propertyDeclarationInProtocol 481 | case propertyDeclaration 482 | case functionDeclarationInProtocol 483 | case functionDeclaration 484 | case attribute 485 | case blank 486 | case other 487 | } 488 | 489 | class LineGroup { 490 | var lines = [Line]() 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/BraceFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BraceFormatter.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public struct BraceFormatter : TokenFormatter { 12 | public func format(_ tokens: [Token], _ configuration: Configuration) -> [Token] { 13 | var line = [Token]() 14 | var formatted = [Token]() 15 | for token in tokens { 16 | switch token { 17 | case .code(let code): 18 | switch code.token.tokenKind { 19 | case .leftBrace: 20 | if let context = code.contexts.last { 21 | switch context.node { 22 | case is StructDeclSyntax, is ClassDeclSyntax, is ProtocolDeclSyntax, is UnknownDeclSyntax, is ExtensionDeclSyntax: 23 | switch configuration.braces.placement.inTypeDeclarations { 24 | case .endOfLine: 25 | noWrap(token: token, line: &line, formatted: &formatted) 26 | case .nextLine: 27 | wrap(token: token, code: code, line: &line, formatted: &formatted) 28 | } 29 | case is FunctionDeclSyntax: 30 | switch configuration.braces.placement.inFunctions { 31 | case .endOfLine: 32 | noWrap(token: token, line: &line, formatted: &formatted) 33 | case .nextLine: 34 | wrap(token: token, code: code, line: &line, formatted: &formatted) 35 | } 36 | default: 37 | switch configuration.braces.placement.inOther { 38 | case .endOfLine: 39 | noWrap(token: token, line: &line, formatted: &formatted) 40 | case .nextLine: 41 | wrap(token: token, code: code, line: &line, formatted: &formatted) 42 | } 43 | } 44 | } 45 | case .rightBrace: 46 | let hasOtherText = line.filter { 47 | if case .code = $0 { 48 | return true 49 | } 50 | return false 51 | }.isEmpty 52 | guard !hasOtherText else { 53 | line.append(token) 54 | break 55 | } 56 | 57 | var braces = [Token]() 58 | braces.append(token) 59 | 60 | for token in line.reversed() { 61 | if case .code(let code) = token, case .leftBrace = code.token.tokenKind { 62 | if braces.isEmpty { 63 | break 64 | } else { 65 | braces.removeLast() 66 | } 67 | } else if case .code(let code) = token, case .rightBrace = code.token.tokenKind { 68 | braces.append(token) 69 | } 70 | } 71 | if configuration.wrapping.keepWhenReformatting.simpleBlocksAndTrailingClosuresInOneLine { 72 | // FIXME 73 | } 74 | if configuration.wrapping.keepWhenReformatting.simpleFunctionsInOneLine { 75 | // FIXME 76 | } 77 | if configuration.wrapping.keepWhenReformatting.simpleClosureArgumentsInOneLine { 78 | // FIXME 79 | } 80 | if !braces.isEmpty { 81 | line = removeTrailingWhitespaces(line) 82 | line.append(.newline(Newline(character: "\n", count: 1, triviaPiece: TriviaPiece.newlines(1)))) 83 | } 84 | line.append(token) 85 | default: 86 | line.append(token) 87 | } 88 | case .backtick, .lineComment, .blockComment, .whitespace: 89 | line.append(token) 90 | case .newline: 91 | formatted.append(contentsOf: line) 92 | formatted.append(token) 93 | line.removeAll() 94 | } 95 | } 96 | if !line.isEmpty { 97 | formatted.append(contentsOf: line) 98 | } 99 | return formatted 100 | } 101 | 102 | private func noWrap(token: Token, line: inout [Token], formatted: inout [Token]) { 103 | if line.isEmpty { 104 | formatted = removePreviousNewline(formatted) 105 | formatted.append(token) 106 | line.removeAll() 107 | } else { 108 | line.append(token) 109 | } 110 | } 111 | 112 | private func wrap(token: Token, code: Code, line: inout [Token], formatted: inout [Token]) { 113 | if line.isEmpty { 114 | line.append(token) 115 | } else { 116 | formatted.append(contentsOf: line) 117 | formatted.append(.newline(Newline(character: "\n", count: 1, triviaPiece: TriviaPiece.newlines(1)))) 118 | formatted.append(.code(Code(text: code.text.trimmingCharacters(in: .whitespacesAndNewlines), token: code.token, contexts: code.contexts))) 119 | line.removeAll() 120 | } 121 | } 122 | 123 | private func removePreviousNewline(_ tokens: [Token]) -> [Token] { 124 | var tokens = tokens 125 | while true { 126 | if let last = tokens.last { 127 | tokens = Array(tokens.dropLast()) 128 | switch last { 129 | case .newline: 130 | return tokens 131 | default: 132 | break 133 | } 134 | } 135 | } 136 | } 137 | 138 | private func removeTrailingWhitespaces(_ tokens: [Token]) -> [Token] { 139 | var tokens = tokens 140 | while true { 141 | if let last = tokens.last, case .whitespace = last { 142 | tokens = Array(tokens.dropLast()) 143 | } else { 144 | return tokens 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Configuration : Codable { 11 | public init() {} 12 | 13 | public static func load(file: URL) -> Configuration? { 14 | let decoder = JSONDecoder() 15 | if let data = try? Data(contentsOf: file), let configuration = try? decoder.decode(Configuration.self, from: data) { 16 | return configuration 17 | } 18 | return nil 19 | } 20 | 21 | // FIXME: Not implemented 22 | public var hardWrapAt = 120 23 | 24 | public var indentation = Indentation() 25 | 26 | public var spaces = Spaces() 27 | public var braces = Braces() 28 | public var wrapping = Wrapping() 29 | public var blankLines = BlankLines() 30 | 31 | public var shouldRemoveSemicons = true 32 | 33 | public struct Indentation : Codable { 34 | public var useTabCharacter = false 35 | public var tabSize = 4 36 | public var indent = 4 37 | public var continuationIndent = 8 38 | public var keepIndentsOnEmptyLines = false 39 | public var indentCaseBranches = false 40 | } 41 | 42 | public struct Spaces : Codable { 43 | var before = Before() 44 | var around = Around() 45 | var within = Within() 46 | var inTernaryOperator = InTernaryOperator() 47 | var comma = Comma() 48 | var semicolon = Semicolon() 49 | 50 | public struct Before : Codable { 51 | var parentheses = Parentheses() 52 | var leftBrace = LeftBrace() 53 | var keywords = Keywords() 54 | 55 | public struct Parentheses : Codable { 56 | var functionDeclaration = false 57 | var functionCall = false 58 | var `if` = true 59 | var `while` = true 60 | var `switch` = true 61 | var `catch` = true 62 | var attribute = false 63 | } 64 | 65 | public struct LeftBrace : Codable { 66 | var typeDeclaration = true 67 | var function = true 68 | var `if` = true 69 | var `else` = true 70 | var `for` = true 71 | var `while` = true 72 | var `do` = true 73 | var `switch` = true 74 | var `catch` = true 75 | } 76 | 77 | public struct Keywords : Codable { 78 | var `else` = true 79 | var `while` = true 80 | var `catch` = true 81 | } 82 | } 83 | 84 | public struct Around : Codable { 85 | var operators = Operators() 86 | var colons = Colons() 87 | 88 | public struct Operators : Codable { 89 | var assignmentOperators = true // =, +=, -=, *=, /=, %=, &=, |=, ^= 90 | var logicalOperators = true // &&, || 91 | var equalityOperators = true // ==, ===, !=, !== 92 | var relationalOperators = true // <, >, <=, >= 93 | var bitwiseOperators = true // &, |, ^ 94 | var additiveOperators = true // +, - 95 | var multiplicativeOperators = true // *, /, % 96 | var shiftOperators = true // <<, >> 97 | var rangeOperators = false // ..., ..< 98 | var closureArrow = true // -> 99 | } 100 | 101 | public struct Colons : Codable { 102 | var beforeTypeAnnotations = false 103 | var afterTypeAnnotations = true 104 | var beforeTypeInheritanceClauses = true 105 | var afterTypeInheritanceClauses = true 106 | var beforeTypeInheritanceClausesInTypeArguments = false 107 | var afterTypeInheritanceClausesInTypeArguments = true 108 | var beforeDictionaryTypes = false 109 | var afterDictionaryTypes = true 110 | var beforeDictionaryLiteralKeyValuePair = false 111 | var afterDictionaryLiteralKeyValuePair = true 112 | var beforeAttributeArguments = false 113 | var afterAttributeArguments = true 114 | } 115 | } 116 | 117 | struct Within : Codable { 118 | var codeBraces = true 119 | var brackets = false 120 | var arrayAndDictionaryLiteralBrackets = false 121 | var groupingParentheses = false 122 | var functionDeclarationParentheses = false 123 | var emptyFunctionDeclarationParentheses = false 124 | var functionCallParentheses = false 125 | var emptyFunctionCallParentheses = false 126 | var ifParentheses = false 127 | var whileParentheses = false 128 | var switchParentheses = false 129 | var catchParentheses = false 130 | var attributeParentheses = false 131 | } 132 | 133 | struct InTernaryOperator : Codable { 134 | var afterQuestionMark = true 135 | var afterColon = true 136 | var beforeColon = true 137 | } 138 | 139 | struct Comma : Codable { 140 | var before = false 141 | var after = true 142 | var afterWithinTypeArguments = true 143 | } 144 | 145 | struct Semicolon : Codable { 146 | var before = false 147 | var after = true 148 | } 149 | } 150 | 151 | public struct Braces : Codable { 152 | public var placement = Placement() 153 | 154 | public struct Placement : Codable { 155 | var inTypeDeclarations = BracesPlacementOptions.endOfLine 156 | var inFunctions = BracesPlacementOptions.endOfLine 157 | var inOther = BracesPlacementOptions.endOfLine 158 | } 159 | } 160 | 161 | public struct Wrapping : Codable { 162 | public var keepWhenReformatting = KeepWhenReformatting() 163 | 164 | public var ifStatement = IfStatement() 165 | public var guardStatement = GuardStatement() 166 | public var repeatWhileStatement = RepeatWhileStatement() 167 | public var doStatement = DoStatement() 168 | public var modifierList = ModifierList() 169 | 170 | public var alignment = Alignment() 171 | 172 | public struct IfStatement : Codable { 173 | var elseOnNewLine = false 174 | } 175 | public struct GuardStatement : Codable { 176 | var elseOnNewLine = false 177 | } 178 | public struct RepeatWhileStatement : Codable { 179 | var whileOnNewLine = false 180 | } 181 | public struct DoStatement : Codable { 182 | var catchOnNewLine = false 183 | } 184 | // FIXME: Not implemented yet 185 | public struct ModifierList : Codable { 186 | var wrapAfterModifierList = false 187 | } 188 | 189 | // FIXME: Not implemented yet 190 | public struct KeepWhenReformatting : Codable { 191 | public var lineBreakes = true 192 | public var commentAtFirstColumn = true 193 | public var simpleBlocksAndTrailingClosuresInOneLine = true 194 | public var simpleFunctionsInOneLine = true 195 | public var simpleClosureArgumentsInOneLine = true 196 | } 197 | 198 | // FIXME: Not implemented yet 199 | public struct Alignment : Codable { 200 | public var baseClassAndAdoptedProtocolList = BaseClassAndAdoptedProtocolList() 201 | public var functionDeclarationParameters = FunctionDeclarationParameters() 202 | public var functionCallArguments = FunctionCallArguments() 203 | public var chainedMethodCalls = ChainedMethodCalls() 204 | public var closures = Closures() 205 | 206 | public struct BaseClassAndAdoptedProtocolList : Codable { 207 | public var wrapping = WrappingOptions.doNotWrap 208 | public var alignWhenMultiline = false 209 | } 210 | 211 | public struct FunctionDeclarationParameters : Codable { 212 | public var wrapping = WrappingOptions.doNotWrap // FIXME: Not implemented 213 | public var alignWhenMultiline = true 214 | public var newLineAfterLeftParenthesis = false // FIXME: Not implemented 215 | public var placeRightParenthesisOnNewLine = false // FIXME: Not implemented 216 | } 217 | 218 | public struct FunctionCallArguments : Codable { 219 | public var wrapping = WrappingOptions.doNotWrap // FIXME: Not implemented 220 | public var alignWhenMultiline = true 221 | public var newLineAfterLeftParenthesis = false // FIXME: Not implemented 222 | public var placeRightParenthesisOnNewLine = false // FIXME: Not implemented 223 | } 224 | 225 | public struct ChainedMethodCalls : Codable { 226 | public var wrapping = WrappingOptions.doNotWrap // FIXME: Not implemented 227 | public var alignWhenMultiline = false 228 | } 229 | 230 | public struct Closures : Codable { 231 | public var parametersOnNewLineWhenMultiline = false 232 | } 233 | 234 | public struct ConditionClauses : Codable { 235 | public var wrapping = WrappingOptions.doNotWrap // FIXME: Not implemented 236 | public var alignWhenMultiline = false 237 | } 238 | } 239 | } 240 | 241 | public struct BlankLines : Codable { 242 | public var keepMaximumBlankLines = KeepMaximumBlankLines() 243 | public var minimumBlankLines = MinimumBlankLines() 244 | 245 | public struct KeepMaximumBlankLines : Codable { 246 | public var inDeclarations = 2 247 | public var inCode = 2 248 | public var beforeClosingBrace = 2 249 | } 250 | 251 | public struct MinimumBlankLines : Codable { 252 | public var beforeImports = 1 253 | public var afterImports = 1 254 | public var aroundTypeDeclarations = 1 255 | public var aroundPropertyInProtocol = 0 256 | public var aroundProperty = 0 257 | public var aroundFunctionInProtocol = 0 258 | public var aroundFunction = 1 259 | // FIXME: Not implemented yet 260 | public var beforeFunctionBody = 0 261 | } 262 | } 263 | 264 | public enum WrappingOptions : Int, Codable { 265 | case doNotWrap 266 | case wrapIfLong 267 | case chopDownIfLong 268 | case wrapAlways 269 | } 270 | 271 | public enum BracesPlacementOptions : Int, Codable { 272 | case endOfLine 273 | case nextLine 274 | } 275 | 276 | public enum AlignmentOptions : Int, Codable { 277 | case alignWhenMultiline 278 | case alignParenthesisedWhenMultiline 279 | case alignInColumns 280 | case elseOnNewLine 281 | case specialElseIfTreatment 282 | case indentCaseBranches 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/Formatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Formatter.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol LineFormatter { 11 | func format(_ lines: [Line], _ configuration: Configuration) -> [Line] 12 | } 13 | 14 | public protocol TokenFormatter { 15 | func format(_ tokens: [Token], _ configuration: Configuration) -> [Token] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/IndentFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndentFormatter.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public struct IndentFormatter : LineFormatter { 12 | public func format(_ lines: [Line], _ configuration: Configuration) -> [Line] { 13 | let indentCounter = IndentCounter() 14 | 15 | return lines.enumerated().map { (lineIndex, line) -> Line in 16 | let indentCounterInLine = IndentCounter() 17 | 18 | for (tokenIndex, token) in line.tokens.enumerated() { 19 | switch token { 20 | case .code(let code): 21 | switch code.token.tokenKind { 22 | case .leftBrace: 23 | indentCounterInLine.add(Brace(indent: true, lineNumber: lineIndex)) 24 | case .rightBrace: 25 | if !indentCounterInLine.braces.isEmpty { 26 | indentCounterInLine.removeBrace() 27 | } else { 28 | indentCounter.removeBrace() 29 | } 30 | case .leftParen: 31 | var alignment = 0 32 | let hasText = hasTextAfter(line.tokens, tokenIndex + 1) 33 | let hasOpeningBrace = hasOpeningBraceAfter(line.tokens, tokenIndex + 1) 34 | var shouldIndent = false 35 | if hasText && !hasOpeningBrace { 36 | if let context = code.contexts.last { 37 | if context.node is FunctionDeclSyntax { 38 | if configuration.wrapping.alignment.functionDeclarationParameters.alignWhenMultiline { 39 | alignment = line.tokens.prefix(through: tokenIndex).reduce(0) { $0 + "\($1)".count } 40 | } else { 41 | shouldIndent = true 42 | } 43 | } 44 | if context.node is FunctionCallExprSyntax { 45 | if configuration.wrapping.alignment.functionCallArguments.alignWhenMultiline { 46 | alignment = line.tokens.prefix(through: tokenIndex).reduce(0) { $0 + "\($1)".count } 47 | } else { 48 | shouldIndent = true 49 | } 50 | } 51 | } 52 | } 53 | indentCounterInLine.add(Parenthesis(indent: shouldIndent || !hasText, alignment: alignment, lineNumber: lineIndex)) 54 | case .rightParen, .stringInterpolationAnchor: 55 | if !indentCounterInLine.parentheses.isEmpty { 56 | indentCounterInLine.removeParenthesis() 57 | } else { 58 | indentCounter.removeParenthesis(deleyed: hasTextBefore(line.tokens, tokenIndex - 1)) 59 | } 60 | case .leftSquareBracket: 61 | let alignment: Int 62 | let hasText = hasTextAfter(line.tokens, tokenIndex + 1) 63 | let hasOpeningBrace = hasOpeningBraceAfter(line.tokens, tokenIndex + 1) 64 | if hasText && !hasOpeningBrace { 65 | alignment = line.tokens.prefix(through: tokenIndex).reduce(0) { $0 + "\($1)".count } 66 | } else { 67 | alignment = 0 68 | } 69 | indentCounterInLine.add(Bracket(indent: !hasText, alignment: alignment, lineNumber: lineIndex)) 70 | case .rightSquareBracket: 71 | if !indentCounterInLine.brackets.isEmpty { 72 | indentCounterInLine.brackets.removeLast() 73 | } else { 74 | indentCounter.removeBracket(deleyed: hasTextBefore(line.tokens, tokenIndex - 1)) 75 | } 76 | case .switchKeyword: 77 | indentCounter.add(SwitchStatement(lineNumber: lineIndex)) 78 | case .caseKeyword: 79 | if !indentCounter.switchStatements.isEmpty { 80 | indentCounter.add(CaseBranch(lineNumber: lineIndex)) 81 | } 82 | case .defaultKeyword: 83 | if !indentCounter.switchStatements.isEmpty { 84 | indentCounter.add(CaseBranch(lineNumber: lineIndex)) 85 | } 86 | default: 87 | break 88 | } 89 | case .backtick: 90 | break 91 | case .lineComment: 92 | break 93 | case .blockComment: 94 | break 95 | case .whitespace, .newline: 96 | break 97 | } 98 | } 99 | 100 | var indentLevel = indentCounter.indentLevel - indentCounter.dedent 101 | if let _ = indentCounter.caseBranches.last { 102 | if !configuration.indentation.indentCaseBranches { 103 | indentLevel -= 1 104 | } 105 | indentCounter.removeCaseBranch() 106 | } else { 107 | if let statement = indentCounter.switchStatements.last, statement.lineNumber < lineIndex { 108 | if configuration.indentation.indentCaseBranches { 109 | indentLevel += 1 110 | } 111 | } 112 | } 113 | 114 | assert(indentLevel >= 0, "indentation level should not be negative") 115 | 116 | let alignment = indentCounter.alignment 117 | let formatted = Line(tokens: line.tokens, indentationLevel: indentLevel, alignment: alignment) 118 | 119 | indentCounter.add(indentCounterInLine) 120 | 121 | return formatted 122 | } 123 | } 124 | 125 | private func hasTextBefore(_ tokens: [Token], _ index: Int) -> Bool { 126 | guard index > 0 else { 127 | return false 128 | } 129 | for i in stride(from: index, to: 0, by: -1) { 130 | switch tokens[i] { 131 | case .code, .backtick, .lineComment, .blockComment: 132 | return true 133 | default: 134 | break 135 | } 136 | } 137 | return false 138 | } 139 | 140 | private func hasTextAfter(_ tokens: [Token], _ index: Int) -> Bool { 141 | guard index < tokens.count else { 142 | return false 143 | } 144 | for i in index.. Bool { 156 | guard index < tokens.count else { 157 | return false 158 | } 159 | for i in index.. 0 && indentation.lineNumber == indentations[index - 1].lineNumber { 244 | alignment -= indentations[index - 1].alignment 245 | } 246 | alignment += indentation.alignment 247 | } 248 | self.alignment = alignment 249 | } 250 | 251 | func removeBrace() { 252 | if let last = braces.last { 253 | if last.indent { 254 | dedent += 1 255 | } 256 | braces.removeLast() 257 | 258 | if let switchStatement = switchStatements.last, switchStatement.lineNumber == last.lineNumber { 259 | switchStatements.removeLast() 260 | } 261 | } else { 262 | fatalError("unmatched close brace") 263 | } 264 | } 265 | 266 | func removeParenthesis(deleyed: Bool = false) { 267 | if deleyed { 268 | delayedRemoveParenthesis += 1 269 | return 270 | } 271 | if let last = parentheses.last { 272 | if last.indent { 273 | dedent += 1 274 | } 275 | parentheses.removeLast() 276 | } else { 277 | fatalError("unmatched close parenthesis") 278 | } 279 | } 280 | 281 | func removeBracket(deleyed: Bool = false) { 282 | if deleyed { 283 | delayedRemoveBracket += 1 284 | return 285 | } 286 | if let last = brackets.last { 287 | if last.indent { 288 | dedent += 1 289 | } 290 | brackets.removeLast() 291 | } else { 292 | fatalError("unmatched close bracket") 293 | } 294 | } 295 | 296 | func removeSwitchStatement() { 297 | switchStatements.removeLast() 298 | } 299 | 300 | func removeCaseBranch() { 301 | caseBranches.removeLast() 302 | } 303 | } 304 | 305 | fileprivate class Brace : Indentation { 306 | var indent: Bool 307 | var alignment: Int 308 | var lineNumber: Int 309 | 310 | init(indent: Bool, alignment: Int = 0, lineNumber: Int) { 311 | self.indent = indent 312 | self.alignment = alignment 313 | self.lineNumber = lineNumber 314 | } 315 | } 316 | 317 | fileprivate class Parenthesis : Indentation { 318 | var indent: Bool 319 | var alignment: Int 320 | var lineNumber: Int 321 | 322 | init(indent: Bool, alignment: Int = 0, lineNumber: Int) { 323 | self.indent = indent 324 | self.alignment = alignment 325 | self.lineNumber = lineNumber 326 | } 327 | } 328 | 329 | fileprivate class Bracket : Indentation { 330 | var indent: Bool 331 | var alignment: Int 332 | var lineNumber: Int 333 | 334 | init(indent: Bool, alignment: Int = 0, lineNumber: Int) { 335 | self.indent = indent 336 | self.alignment = alignment 337 | self.lineNumber = lineNumber 338 | } 339 | } 340 | 341 | fileprivate class SwitchStatement { 342 | var lineNumber: Int 343 | 344 | init(lineNumber: Int) { 345 | self.lineNumber = lineNumber 346 | } 347 | } 348 | 349 | fileprivate class CaseBranch { 350 | var lineNumber: Int 351 | 352 | init(lineNumber: Int) { 353 | self.lineNumber = lineNumber 354 | } 355 | } 356 | 357 | fileprivate protocol Indentation { 358 | var indent: Bool { get set } 359 | var alignment: Int { get set } 360 | var lineNumber: Int { get set } 361 | } 362 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/Line.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Line.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public struct Line { 12 | public let tokens: [Token] 13 | public let indentationLevel: Int 14 | public let alignment: Int 15 | 16 | public init(tokens: [Token], indentationLevel: Int, alignment: Int) { 17 | self.tokens = tokens 18 | self.indentationLevel = indentationLevel 19 | self.alignment = alignment 20 | } 21 | 22 | var isEmpty: Bool { 23 | return tokens.filter { 24 | switch $0 { 25 | case .code, .backtick, .lineComment, .blockComment: 26 | return true 27 | case .whitespace, .newline: 28 | return false 29 | } 30 | }.isEmpty 31 | } 32 | 33 | public static func blank() -> Line { 34 | return Line(tokens: [], indentationLevel: 0, alignment: 0) 35 | } 36 | } 37 | 38 | extension Line : CustomStringConvertible { 39 | public var description: String { 40 | return tokens.map { "\($0)" }.joined() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/Processor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Processor.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/14. 6 | // 7 | 8 | import Foundation 9 | import Basic 10 | import SwiftSyntax 11 | 12 | public struct Processor { 13 | public init() {} 14 | 15 | public func processFile(input fileURL: URL, configuration: Configuration, verbose: Bool = false) throws -> String { 16 | let tokens = try tokenize(fileURL: fileURL, configuration: configuration) 17 | 18 | let formatter = SourceFormatter(tokenFormatters: [SemicolonFormatter(), SpaceFormatter(), BraceFormatter(), WrappingFormatter()], 19 | lineFormatters: [BlankLineFormatter(), IndentFormatter(), AlignmentFormatter()]) 20 | let formatted = formatter.format(tokens, configuration) 21 | 22 | let renderer = Renderer() 23 | let result = renderer.render(formatted, configuration) 24 | 25 | return result 26 | } 27 | 28 | private func tokenize(fileURL: URL, configuration: Configuration) throws -> [Token] { 29 | let sourceFile = try SourceFileSyntax.parse(fileURL) 30 | 31 | let tokenizer = TokenVisitor() 32 | _ = tokenizer.visit(sourceFile) 33 | 34 | return tokenizer.tokens 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/Renderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Renderer.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Renderer { 11 | public func render(_ lines: [Line], _ configuration: Configuration) -> String { 12 | return lines.reduce("") { 13 | let indentation = indent($1, $1.indentationLevel, configuration.indentation) 14 | let alignment = String(repeating: " ", count: $1.alignment) 15 | return $0 + "\(indentation)\(alignment)\($1.tokens.map { "\($0)" }.joined())\n" 16 | } 17 | } 18 | 19 | private func indent(_ line: Line, _ level: Int, _ configuration: Configuration.Indentation) -> String { 20 | if !configuration.keepIndentsOnEmptyLines && line.isEmpty { 21 | return "" 22 | } 23 | let indentSize = configuration.indent * level 24 | let tabSize = configuration.tabSize 25 | if configuration.useTabCharacter { 26 | let tabCount = indentSize / tabSize 27 | let ramaining = indentSize % tabSize 28 | return String(repeating: "\t", count: tabCount) + String(repeating: " ", count: ramaining) 29 | } else { 30 | return String(repeating: " ", count: indentSize) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/SemicolonFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemicolonFormatter.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public struct SemicolonFormatter : TokenFormatter { 12 | public func format(_ tokens: [Token], _ configuration: Configuration) -> [Token] { 13 | var results = [Token]() 14 | var whitespaces = [Token]() 15 | var skipWhitespaces = false 16 | for (index, token) in tokens.enumerated() { 17 | switch token { 18 | case .code(let code): 19 | switch code.token.tokenKind { 20 | case .semicolon: 21 | if !configuration.shouldRemoveSemicons { 22 | results.append(token) 23 | } 24 | if !hasNewlineBeforeToken(tokens, index + 1) { 25 | results.append(.newline(Newline(character: "\n", count: 1, triviaPiece: TriviaPiece.newlines(1)))) 26 | } 27 | skipWhitespaces = true 28 | default: 29 | if !skipWhitespaces && !whitespaces.isEmpty { 30 | results.append(contentsOf: whitespaces) 31 | } 32 | results.append(token) 33 | whitespaces.removeAll() 34 | skipWhitespaces = false 35 | } 36 | case .whitespace: 37 | whitespaces.append(token) 38 | default: 39 | if !skipWhitespaces && !whitespaces.isEmpty { 40 | results.append(contentsOf: whitespaces) 41 | } 42 | results.append(token) 43 | whitespaces.removeAll() 44 | skipWhitespaces = false 45 | } 46 | } 47 | return results 48 | } 49 | 50 | private func hasNewlineBeforeToken(_ tokens: [Token], _ index: Int) -> Bool { 51 | guard index < tokens.count else { 52 | return true 53 | } 54 | for i in index.. [Line] { 20 | let formattedTokens = format(tokens: tokens, configuration: configuration) 21 | let lines = SourceFile(tokens: formattedTokens).lines 22 | return format(lines: lines, configuration: configuration) 23 | } 24 | 25 | private func format(tokens: [Token], configuration: Configuration) -> [Token] { 26 | return tokenFormatters.reduce(tokens) { (tokens, formatter) -> [Token] in 27 | return formatter.format(tokens, configuration) 28 | } 29 | } 30 | 31 | private func format(lines: [Line], configuration: Configuration) -> [Line] { 32 | return lineFormatters.reduce(lines) { (lines, formatter) -> [Line] in 33 | return formatter.format(lines, configuration) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public enum Token { 12 | case code(Code) 13 | case backtick(Backtick) 14 | case lineComment(LineComment) 15 | case blockComment(BlockComment) 16 | case whitespace(Whitespace) 17 | case newline(Newline) 18 | } 19 | 20 | extension Token : CustomStringConvertible { 21 | public var description: String { 22 | switch self { 23 | case .code(let code): 24 | return code.text 25 | case .backtick(let backtick): 26 | return String(repeating: backtick.character, count: backtick.count) 27 | case .lineComment(let comment): 28 | return comment.text 29 | case .blockComment(let comment): 30 | return comment.text 31 | case .whitespace(let whitespace): 32 | return String(repeating: whitespace.character, count: whitespace.count) 33 | case .newline(let newline): 34 | return String(repeating: newline.character, count: newline.count) 35 | } 36 | } 37 | } 38 | 39 | public struct Code { 40 | public let text: String 41 | public let token: TokenSyntax 42 | let contexts: [Context] 43 | 44 | init(text: String, token: TokenSyntax, contexts: [Context]) { 45 | self.text = text 46 | self.token = token 47 | self.contexts = contexts 48 | } 49 | } 50 | 51 | public struct Backtick { 52 | public let character: String 53 | public let count: Int 54 | public let triviaPiece: TriviaPiece 55 | } 56 | 57 | public struct BlockComment { 58 | public let text: String 59 | public let triviaPiece: TriviaPiece 60 | } 61 | 62 | public struct LineComment { 63 | public let text: String 64 | public let triviaPiece: TriviaPiece 65 | } 66 | 67 | public struct Whitespace { 68 | public let character: String 69 | public let count: Int 70 | public let triviaPiece: TriviaPiece 71 | } 72 | 73 | public struct Newline { 74 | public let character: String 75 | public let count: Int 76 | public let triviaPiece: TriviaPiece 77 | } 78 | 79 | struct Context { 80 | let node: Syntax 81 | } 82 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/TokenVisitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenVisitor.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | class TokenVisitor : SyntaxVisitor { 12 | var tokens = [Token]() 13 | private var line = [Token]() 14 | private var contexts = [Context]() 15 | 16 | override func visit(_ node: ImportDeclSyntax) { 17 | processNode(node) 18 | } 19 | 20 | override func visit(_ node: ClassDeclSyntax) { 21 | processNode(node) 22 | } 23 | 24 | override func visit(_ node: StructDeclSyntax) { 25 | processNode(node) 26 | } 27 | 28 | override func visit(_ node: ProtocolDeclSyntax) { 29 | processNode(node) 30 | } 31 | 32 | override func visit(_ node: ExtensionDeclSyntax) { 33 | processNode(node) 34 | } 35 | 36 | // FIXME: Workaround for enum declaration (e.g. enum Fruit { ... }) 37 | override func visit(_ node: UnknownDeclSyntax) { 38 | processNode(node) 39 | } 40 | 41 | override func visit(_ node: TypeInheritanceClauseSyntax) { 42 | processNode(node) 43 | } 44 | 45 | override func visit(_ node: VariableDeclSyntax) { 46 | processNode(node) 47 | } 48 | 49 | override func visit(_ node: DictionaryExprSyntax) { 50 | processNode(node) 51 | } 52 | 53 | override func visit(_ node: DictionaryElementSyntax) { 54 | processNode(node) 55 | } 56 | 57 | override func visit(_ node: DictionaryTypeSyntax) { 58 | processNode(node) 59 | } 60 | 61 | override func visit(_ node: TypeAnnotationSyntax) { 62 | processNode(node) 63 | } 64 | 65 | override func visit(_ node: FunctionDeclSyntax) { 66 | processNode(node) 67 | } 68 | 69 | override func visit(_ node: FunctionParameterSyntax) { 70 | processNode(node) 71 | } 72 | 73 | override func visit(_ node: FunctionCallExprSyntax) { 74 | processNode(node) 75 | } 76 | 77 | override func visit(_ node: FunctionCallArgumentSyntax) { 78 | processNode(node) 79 | } 80 | 81 | override func visit(_ node: GenericParameterSyntax) { 82 | processNode(node) 83 | } 84 | 85 | override func visit(_ node: GenericWhereClauseSyntax) { 86 | processNode(node) 87 | } 88 | 89 | override func visit(_ node: ClosureExprSyntax) { 90 | processNode(node) 91 | } 92 | 93 | override func visit(_ node: TernaryExprSyntax) { 94 | processNode(node) 95 | } 96 | 97 | override func visit(_ node: IfStmtSyntax) { 98 | processNode(node) 99 | } 100 | 101 | override func visit(_ node: ElseBlockSyntax) { 102 | processNode(node) 103 | } 104 | 105 | override func visit(_ node: ElseIfContinuationSyntax) { 106 | processNode(node) 107 | } 108 | 109 | override func visit(_ node: GuardStmtSyntax) { 110 | processNode(node) 111 | } 112 | 113 | override func visit(_ node: ForInStmtSyntax) { 114 | processNode(node) 115 | } 116 | 117 | override func visit(_ node: WhileStmtSyntax) { 118 | processNode(node) 119 | } 120 | 121 | override func visit(_ node: RepeatWhileStmtSyntax) { 122 | processNode(node) 123 | } 124 | 125 | override func visit(_ node: DoStmtSyntax) { 126 | processNode(node) 127 | } 128 | 129 | override func visit(_ node: CatchClauseSyntax) { 130 | processNode(node) 131 | } 132 | 133 | // FIXME: Workaround for while, switch and AvailabilityConditionSyntax statements 134 | override func visit(_ node: UnknownStmtSyntax) { 135 | processNode(node) 136 | } 137 | 138 | override func visit(_ node: SwitchStmtSyntax) { 139 | processNode(node) 140 | } 141 | 142 | override func visit(_ node: SwitchCaseSyntax) { 143 | processNode(node) 144 | } 145 | 146 | override func visit(_ node: SwitchCaseLabelSyntax) { 147 | processNode(node) 148 | } 149 | 150 | override func visit(_ node: SwitchDefaultLabelSyntax) { 151 | processNode(node) 152 | } 153 | 154 | override func visit(_ node: AttributeSyntax) { 155 | processNode(node) 156 | } 157 | 158 | override func visit(_ node: CodeBlockItemSyntax) { 159 | processNode(node) 160 | } 161 | 162 | override func visit(_ node: UnknownExprSyntax) { 163 | processNode(node) 164 | } 165 | 166 | override func visit(_ node: AvailabilityConditionSyntax) { 167 | processNode(node) 168 | } 169 | 170 | override func visit(_ token: TokenSyntax) { 171 | token.leadingTrivia.forEach { (piece) in 172 | processTriviaPiece(piece) 173 | } 174 | 175 | processToken(token) 176 | 177 | token.trailingTrivia.forEach { (piece) in 178 | processTriviaPiece(piece) 179 | } 180 | } 181 | 182 | private func processNode(_ node: NodeType) { 183 | let context = Context(node: node) 184 | contexts.append(context) 185 | defer { 186 | if let index = contexts.index(where: { return $0.node == context.node }) { 187 | contexts.remove(at: index) 188 | } 189 | } 190 | for child in node.children { 191 | visit(child) 192 | } 193 | } 194 | 195 | private func processToken(_ token: TokenSyntax) { 196 | line.append(.code(Code(text: token.withoutTrivia().text, token: token, contexts: contexts))) 197 | if token.tokenKind == .eof { 198 | tokens.append(contentsOf: line.dropLast()) 199 | } 200 | } 201 | 202 | private func processTriviaPiece(_ piece: TriviaPiece) { 203 | switch piece { 204 | case .spaces(let count): 205 | if !line.isEmpty { 206 | line.append(.whitespace(Whitespace(character: " ", count: count, triviaPiece: piece))) 207 | } 208 | case .tabs(let count): 209 | if !line.isEmpty { 210 | line.append(.whitespace(Whitespace(character: "\t", count: count, triviaPiece: piece))) 211 | } 212 | case .verticalTabs(let count): 213 | if !line.isEmpty { 214 | line.append(.whitespace(Whitespace(character: "\u{000B}", count: count, triviaPiece: piece))) 215 | } 216 | case .formfeeds(let count): 217 | if !line.isEmpty { 218 | line.append(.whitespace(Whitespace(character: "\u{000C}", count: count, triviaPiece: piece))) 219 | } 220 | case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count): 221 | line.append(.newline(Newline(character: "\n", count: count, triviaPiece: piece))) 222 | tokens.append(contentsOf: line) 223 | line.removeAll() 224 | case .backticks(let count): 225 | line.append(.backtick(Backtick(character: "`", count: count, triviaPiece: piece))) 226 | case .lineComment(let text): 227 | line.append(.lineComment(LineComment(text: text, triviaPiece: piece))) 228 | case .docLineComment(let text), .blockComment(let text), .docBlockComment(let text): 229 | let comments = text.split(separator: "\n", omittingEmptySubsequences: false) 230 | for (index, comment) in comments.enumerated() { 231 | let trimmed: String 232 | if index == 0 { 233 | trimmed = comment.trimmingCharacters(in: .whitespaces) 234 | } else { 235 | trimmed = " " + comment.trimmingCharacters(in: .whitespaces) 236 | } 237 | line.append(.blockComment(BlockComment(text: trimmed, triviaPiece: piece))) 238 | if index < comments.count - 1 { 239 | line.append(.newline(Newline(character: "\n", count: 1, triviaPiece: TriviaPiece.newlines(1)))); 240 | } 241 | } 242 | case .garbageText(let text): 243 | fatalError("garbage text found: \(text)") 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /Sources/swiftfmt-core/WrappingFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WrappingFormatter.swift 3 | // swiftfmt-core 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftSyntax 10 | 11 | public struct WrappingFormatter : TokenFormatter { 12 | public func format(_ tokens: [Token], _ configuration: Configuration) -> [Token] { 13 | var line = [Token]() 14 | var formatted = [Token]() 15 | var iterator = tokens.makeIterator() 16 | while let token = iterator.next() { 17 | switch token { 18 | case .code(let code): 19 | switch code.token.tokenKind { 20 | case .elseKeyword: 21 | if let context = code.contexts.last { 22 | switch context.node { 23 | case is IfStmtSyntax: 24 | if let tokens = findClosingBraceOrToken(line: line) { 25 | if configuration.wrapping.ifStatement.elseOnNewLine { 26 | wrap(token: token, code: code, line: tokens, formatted: &formatted) 27 | } else { 28 | noWrap(token: token, line: tokens, formatted: &formatted) 29 | } 30 | line.removeAll() 31 | } 32 | case is GuardStmtSyntax: 33 | if let tokens = findClosingBraceOrToken(line: line) { 34 | if configuration.wrapping.guardStatement.elseOnNewLine { 35 | wrap(token: token, code: code, line: tokens, formatted: &formatted) 36 | } else { 37 | noWrap(token: token, line: tokens, formatted: &formatted) 38 | } 39 | line.removeAll() 40 | } 41 | default: 42 | line.append(token) 43 | } 44 | } 45 | case .whileKeyword: 46 | if let context = code.contexts.last { 47 | switch context.node { 48 | case is RepeatWhileStmtSyntax: 49 | if let tokens = findClosingBraceOrToken(line: line) { 50 | if configuration.wrapping.repeatWhileStatement.whileOnNewLine { 51 | wrap(token: token, code: code, line: tokens, formatted: &formatted) 52 | } else { 53 | noWrap(token: token, line: tokens, formatted: &formatted) 54 | } 55 | line.removeAll() 56 | } 57 | default: 58 | line.append(token) 59 | } 60 | } 61 | case .catchKeyword: 62 | if let context = code.contexts.last { 63 | switch context.node { 64 | case is CatchClauseSyntax: 65 | if let tokens = findClosingBraceOrToken(line: line) { 66 | if configuration.wrapping.doStatement.catchOnNewLine { 67 | wrap(token: token, code: code, line: tokens, formatted: &formatted) 68 | } else { 69 | noWrap(token: token, line: tokens, formatted: &formatted) 70 | } 71 | line.removeAll() 72 | } 73 | default: 74 | line.append(token) 75 | } 76 | } 77 | default: 78 | line.append(token) 79 | } 80 | case .backtick, .lineComment, .blockComment, .whitespace: 81 | line.append(token) 82 | case .newline: 83 | line.append(token) 84 | } 85 | } 86 | if !line.isEmpty { 87 | formatted.append(contentsOf: line) 88 | } 89 | return formatted 90 | } 91 | 92 | func findClosingBraceOrToken(line: [Token]) -> [Token]? { 93 | var line = line 94 | while let token = line.last { 95 | switch token { 96 | case .code: 97 | return line 98 | case .backtick: 99 | break 100 | case .lineComment, .blockComment: 101 | return nil 102 | case .whitespace, .newline: 103 | line = Array(line.dropLast()) 104 | } 105 | } 106 | return nil 107 | } 108 | 109 | private func noWrap(token: Token, line: [Token], formatted: inout [Token]) { 110 | formatted.append(contentsOf: line) 111 | formatted.append(token) 112 | } 113 | 114 | private func wrap(token: Token, code: Code, line: [Token], formatted: inout [Token]) { 115 | formatted.append(contentsOf: line) 116 | formatted.append(.newline(Newline(character: "\n", count: 1, triviaPiece: TriviaPiece.newlines(1)))) 117 | formatted.append(.code(Code(text: code.text.trimmingCharacters(in: .whitespaces), token: code.token, contexts: code.contexts))) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/swiftfmt/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // swiftfmt 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/14. 6 | // 7 | 8 | import Foundation 9 | import Basic 10 | import Utility 11 | 12 | func handle(error: Any) { 13 | switch error { 14 | case let anyError as AnyError: 15 | handle(error: anyError.underlyingError) 16 | default: 17 | handle(error) 18 | } 19 | } 20 | 21 | private func handle(_ error: Any) { 22 | switch error { 23 | case ArgumentParserError.expectedArguments(let parser, _): 24 | print(error: error) 25 | parser.printUsage(on: stderrStream) 26 | default: 27 | print(error: error) 28 | } 29 | } 30 | 31 | private func print(error: Any) { 32 | let writer = InteractiveWriter.stderr 33 | writer.write("error: ", inColor: .red, bold: true) 34 | writer.write("\(error)") 35 | writer.write("\n") 36 | } 37 | 38 | final class InteractiveWriter { 39 | static let stderr = InteractiveWriter(stream: stderrStream) 40 | 41 | let term: TerminalController? 42 | let stream: OutputByteStream 43 | 44 | init(stream: OutputByteStream) { 45 | self.term = (stream as? LocalFileOutputByteStream).flatMap(TerminalController.init(stream:)) 46 | self.stream = stream 47 | } 48 | 49 | func write(_ string: String, inColor color: TerminalController.Color = .noColor, bold: Bool = false) { 50 | if let term = term { 51 | term.write(string, inColor: color, bold: bold) 52 | } else { 53 | stream <<< string 54 | stream.flush() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/swiftfmt/FormatTool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormatTool.swift 3 | // swiftfmt 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/14. 6 | // 7 | 8 | import Foundation 9 | import Basic 10 | import swiftfmt_core 11 | 12 | struct FormatTool { 13 | private let verbose: Bool 14 | private let fileSystem = Basic.localFileSystem 15 | 16 | init(verbose: Bool) { 17 | self.verbose = verbose 18 | } 19 | 20 | func run(source: URL, configuration: Configuration) throws { 21 | let processor = Processor() 22 | 23 | let path = AbsolutePath(source.absoluteURL.path) 24 | if fileSystem.isFile(path) { 25 | try processFile(path, processor: processor, configuration: configuration) 26 | } else if fileSystem.isDirectory(path) { 27 | try processDirectory(path, processor: processor, configuration: configuration) 28 | } else { 29 | print("no input files or directory") 30 | } 31 | } 32 | 33 | func processFile(_ path: AbsolutePath, processor: Processor, configuration: Configuration) throws { 34 | if let ext = path.extension, ext == "swift" { 35 | let result = try processor.processFile(input: URL(fileURLWithPath: path.asString), configuration: configuration, verbose: verbose) 36 | try fileSystem.writeFileContents(path, bytes: ByteString(encodingAsUTF8: result)) 37 | } 38 | } 39 | 40 | func processDirectory(_ directory: AbsolutePath, processor: Processor, configuration: Configuration) throws { 41 | for relPath in try fileSystem.getDirectoryContents(directory) { 42 | let path = AbsolutePath(directory, relPath) 43 | if fileSystem.isFile(path) { 44 | try processFile(path, processor: processor, configuration: configuration) 45 | } else { 46 | try processDirectory(path, processor: processor, configuration: configuration) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/swiftfmt/OptionParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionParser.swift 3 | // swiftfmt 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/14. 6 | // 7 | 8 | import Utility 9 | import Basic 10 | import POSIX 11 | 12 | struct OptionParser { 13 | let options: Options 14 | private let parser: ArgumentParser 15 | 16 | init(arguments: [String]) { 17 | parser = ArgumentParser(commandName: "swiftfmt", usage: "swiftfmt filename [options]", overview: "Format Swift source code") 18 | 19 | let binder = ArgumentBinder() 20 | binder.bind(positional: parser.add(positional: "filename", kind: String.self)) { $0.filename = $1 } 21 | binder.bind(option: parser.add(option: "--configuration", kind: String.self)) { $0.configurationFile = $1 } 22 | binder.bind(option: parser.add(option: "--version", kind: Bool.self)) { $0.shouldPrintVersion = $1 } 23 | binder.bind(option: parser.add(option: "--verbose", kind: Bool.self, usage: "Show more debugging information")) { $0.verbose = $1 } 24 | 25 | do { 26 | let result = try parser.parse(arguments) 27 | var options = Options() 28 | binder.fill(result, into: &options) 29 | self.options = options 30 | } catch { 31 | handle(error: error) 32 | POSIX.exit(1) 33 | } 34 | } 35 | 36 | func printUsage(on stream: OutputByteStream) { 37 | parser.printUsage(on: stream) 38 | } 39 | } 40 | 41 | struct Options { 42 | var configurationFile = "" 43 | var filename = "" 44 | var verbose = false 45 | var shouldPrintVersion: Bool = false 46 | } 47 | -------------------------------------------------------------------------------- /Sources/swiftfmt/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // swiftfmt 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/14. 6 | // 7 | 8 | import Foundation 9 | import Basic 10 | import swiftfmt_core 11 | 12 | do { 13 | let parser = OptionParser(arguments: Array(CommandLine.arguments.dropFirst())) 14 | let options = parser.options 15 | if options.shouldPrintVersion { 16 | print("0.1.0") 17 | } else { 18 | if options.filename.isEmpty { 19 | parser.printUsage(on: stdoutStream) 20 | } 21 | let configuration: Configuration 22 | if !options.configurationFile.isEmpty, let c = Configuration.load(file: URL(fileURLWithPath: options.configurationFile)) { 23 | configuration = c 24 | } else if let c = Configuration.load(file: URL(fileURLWithPath: ".swiftfmt.json")) { 25 | configuration = c 26 | } else { 27 | configuration = Configuration() 28 | } 29 | 30 | let command = FormatTool(verbose: options.verbose) 31 | try command.run(source: URL(fileURLWithPath: options.filename), configuration: configuration) 32 | } 33 | } catch { 34 | handle(error: error) 35 | } 36 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import swiftfmt_core 3 | 4 | extension AlignmentTests { 5 | static var allTests: [(String, (AlignmentTests) -> () throws -> Void)] = [ 6 | ("testAlignFunctionDeclarationParameters1()", testAlignFunctionDeclarationParameters1), 7 | ("testAlignFunctionDeclarationParameters2()", testAlignFunctionDeclarationParameters2), 8 | ("testAlignFunctionDeclarationParameters3()", testAlignFunctionDeclarationParameters3), 9 | ("testAlignFunctionCallArguments1()", testAlignFunctionCallArguments1), 10 | ("testAlignFunctionCallArguments2()", testAlignFunctionCallArguments2), 11 | ] 12 | } 13 | 14 | extension BlankLineTest { 15 | static var allTests: [(String, (BlankLineTest) -> () throws -> Void)] = [ 16 | ("testFormatBlankLines()", testFormatBlankLines), 17 | ("testImportDeclarationAfterBlankLines()", testImportDeclarationAfterBlankLines), 18 | ("testFormatDeclarationInMultipleLines()", testFormatDeclarationInMultipleLines), 19 | ("testKeepMaximumBlankLines()", testKeepMaximumBlankLines), 20 | ("testShouldNotInsertBlankLineBeforeFunctionsAfterComment()", testShouldNotInsertBlankLineBeforeFunctionsAfterComment), 21 | ("testBlockComments()", testBlockComments), 22 | ("testShouldNotChangeFunctionBody()", testShouldNotChangeFunctionBody), 23 | ] 24 | } 25 | 26 | XCTMain([ 27 | testCase(AlignmentTests.allTests), 28 | testCase(BlankLineTest.allTests), 29 | // testCase(BraceTests.allTests), 30 | // testCase(ConfigurationTests.allTests), 31 | // testCase(IndentationTests.allTests), 32 | // testCase(SemicolonTests.allTests), 33 | // testCase(SpaceTests.allTests), 34 | // testCase(WrappingTests.allTests), 35 | ]) 36 | -------------------------------------------------------------------------------- /Tests/tests/AlignmentTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlignmentTests.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/21. 6 | // 7 | 8 | import XCTest 9 | @testable import swiftfmt_core 10 | 11 | class AlignmentTests : XCTestCase { 12 | func testAlignFunctionDeclarationParameters1() { 13 | let source = """ 14 | func bar(name: String, 15 | _ title: String, 16 | _ order: Int, 17 | label text: String) { 18 | 19 | } 20 | """ 21 | 22 | let expected = """ 23 | func bar(name: String, 24 | _ title: String, 25 | _ order: Int, 26 | label text: String) { 27 | 28 | } 29 | 30 | """ 31 | 32 | let runner = TestRunner() 33 | var configuration = Configuration() 34 | configuration.wrapping.alignment.functionDeclarationParameters.alignWhenMultiline = true 35 | 36 | let result = runner.run(source: source, configuration: configuration) 37 | XCTAssertEqual(result, expected) 38 | } 39 | 40 | func testAlignFunctionDeclarationParameters2() { 41 | let source = """ 42 | func bar(name: String, 43 | _ title: String, 44 | _ order: Int, 45 | label text: String) { 46 | 47 | } 48 | """ 49 | 50 | let expected = """ 51 | func bar(name: String, 52 | _ title: String, 53 | _ order: Int, 54 | label text: String) { 55 | 56 | } 57 | 58 | """ 59 | 60 | let runner = TestRunner() 61 | var configuration = Configuration() 62 | configuration.wrapping.alignment.functionDeclarationParameters.alignWhenMultiline = false 63 | 64 | let result = runner.run(source: source, configuration: configuration) 65 | XCTAssertEqual(result, expected) 66 | } 67 | 68 | func testAlignFunctionDeclarationParameters3() { 69 | let source = """ 70 | func bar( 71 | name: String, 72 | _ title: String, 73 | _ order: Int, 74 | label text: String) { 75 | 76 | } 77 | """ 78 | 79 | let expected = """ 80 | func bar( 81 | name: String, 82 | _ title: String, 83 | _ order: Int, 84 | label text: String) { 85 | 86 | } 87 | 88 | """ 89 | 90 | let runner = TestRunner() 91 | var configuration = Configuration() 92 | configuration.wrapping.alignment.functionDeclarationParameters.alignWhenMultiline = false 93 | 94 | let result = runner.run(source: source, configuration: configuration) 95 | XCTAssertEqual(result, expected) 96 | } 97 | 98 | func testAlignFunctionCallArguments1() { 99 | let source = """ 100 | foobar(name: "name", 101 | "title", 102 | 0, 103 | label: "name") 104 | """ 105 | 106 | let expected = """ 107 | foobar(name: "name", 108 | "title", 109 | 0, 110 | label: "name") 111 | 112 | """ 113 | 114 | let runner = TestRunner() 115 | var configuration = Configuration() 116 | configuration.wrapping.alignment.functionCallArguments.alignWhenMultiline = true 117 | 118 | let result = runner.run(source: source, configuration: configuration) 119 | XCTAssertEqual(result, expected) 120 | } 121 | 122 | func testAlignFunctionCallArguments2() { 123 | let source = """ 124 | foobar(name: "name", 125 | "title", 126 | 0, 127 | label: "name") 128 | """ 129 | 130 | let expected = """ 131 | foobar(name: "name", 132 | "title", 133 | 0, 134 | label: "name") 135 | 136 | """ 137 | 138 | let runner = TestRunner() 139 | var configuration = Configuration() 140 | configuration.wrapping.alignment.functionCallArguments.alignWhenMultiline = false 141 | 142 | let result = runner.run(source: source, configuration: configuration) 143 | XCTAssertEqual(result, expected) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Tests/tests/BlankLineTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlankLineTest.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/19. 6 | // 7 | 8 | import XCTest 9 | @testable import swiftfmt_core 10 | 11 | class BlankLineTests : XCTestCase { 12 | func testFormatBlankLines() { 13 | let source = """ 14 | // swiftfmt 15 | import Foundation 16 | import SwiftSyntax 17 | class Foo { 18 | let string = "string" 19 | let integer = 0 20 | func foo() -> String { 21 | if integer > 0 { 22 | 23 | } 24 | } 25 | func bar() -> Int { 26 | 27 | } 28 | } 29 | protocol FooProtocol { 30 | let string: String 31 | let integer: Int 32 | func foo() -> String 33 | func bar() -> Int 34 | } 35 | struct Bar {} 36 | enum Baz {} 37 | struct FooBar {} 38 | """ 39 | 40 | let expected = """ 41 | // swiftfmt 42 | 43 | import Foundation 44 | import SwiftSyntax 45 | 46 | class Foo { 47 | let string = "string" 48 | let integer = 0 49 | 50 | func foo() -> String { 51 | if integer > 0 { 52 | 53 | } 54 | } 55 | 56 | func bar() -> Int { 57 | 58 | } 59 | } 60 | 61 | protocol FooProtocol { 62 | let string: String 63 | let integer: Int 64 | func foo() -> String 65 | func bar() -> Int 66 | } 67 | 68 | struct Bar {} 69 | 70 | enum Baz {} 71 | 72 | struct FooBar {} 73 | 74 | """ 75 | 76 | let runner = TestRunner() 77 | let configuration = Configuration() 78 | 79 | let result = runner.run(source: source, configuration: configuration) 80 | XCTAssertEqual(result, expected) 81 | } 82 | 83 | func testImportDeclarationAfterBlankLines() { 84 | let source = """ 85 | // swiftfmt 86 | 87 | import Foundation 88 | import SwiftSyntax 89 | class Foo { 90 | let string = "string" 91 | let integer = 0 92 | func foo() -> String { 93 | if integer > 0 { 94 | 95 | } 96 | } 97 | func bar() -> Int { 98 | 99 | } 100 | } 101 | protocol FooProtocol { 102 | let string: String 103 | let integer: Int 104 | func foo() -> String 105 | func bar() -> Int 106 | } 107 | struct Bar {} 108 | enum Baz {} 109 | struct FooBar {} 110 | """ 111 | 112 | let expected = """ 113 | // swiftfmt 114 | 115 | import Foundation 116 | import SwiftSyntax 117 | 118 | class Foo { 119 | let string = "string" 120 | let integer = 0 121 | 122 | func foo() -> String { 123 | if integer > 0 { 124 | 125 | } 126 | } 127 | 128 | func bar() -> Int { 129 | 130 | } 131 | } 132 | 133 | protocol FooProtocol { 134 | let string: String 135 | let integer: Int 136 | func foo() -> String 137 | func bar() -> Int 138 | } 139 | 140 | struct Bar {} 141 | 142 | enum Baz {} 143 | 144 | struct FooBar {} 145 | 146 | """ 147 | 148 | let runner = TestRunner() 149 | let configuration = Configuration() 150 | 151 | let result = runner.run(source: source, configuration: configuration) 152 | XCTAssertEqual(result, expected) 153 | } 154 | 155 | func testFormatDeclarationInMultipleLines() { 156 | let source = """ 157 | import Foundation 158 | import 159 | SwiftSyntax 160 | public 161 | class Foo { 162 | @available(*,unavailable,renamed: "bar()") 163 | func foo() { 164 | 165 | } 166 | 167 | @available(*, unavailable, renamed: "bar()") 168 | func bar() { 169 | 170 | } 171 | 172 | @objc 173 | func baz() { 174 | 175 | } 176 | 177 | public 178 | func 179 | barbaz() { 180 | 181 | } 182 | } 183 | """ 184 | 185 | let expected = """ 186 | import Foundation 187 | import 188 | SwiftSyntax 189 | 190 | public 191 | class Foo { 192 | @available(*, unavailable, renamed: "bar()") 193 | func foo() { 194 | 195 | } 196 | 197 | @available(*, unavailable, renamed: "bar()") 198 | func bar() { 199 | 200 | } 201 | 202 | @objc 203 | func baz() { 204 | 205 | } 206 | 207 | public 208 | func 209 | barbaz() { 210 | 211 | } 212 | } 213 | 214 | """ 215 | 216 | let runner = TestRunner() 217 | let configuration = Configuration() 218 | 219 | let result = runner.run(source: source, configuration: configuration) 220 | XCTAssertEqual(result, expected) 221 | } 222 | 223 | func testKeepMaximumBlankLines() { 224 | let source = """ 225 | // swiftfmt 226 | import Foundation 227 | import SwiftSyntax 228 | 229 | 230 | 231 | 232 | class Foo { 233 | let string = "string" 234 | let integer = 0 235 | 236 | 237 | 238 | 239 | func foo() -> String { 240 | 241 | 242 | 243 | 244 | if integer > 0 { 245 | 246 | 247 | 248 | 249 | let x = integer 250 | let y = integer 251 | 252 | 253 | 254 | 255 | print(x + y) 256 | 257 | 258 | 259 | 260 | } 261 | 262 | 263 | 264 | 265 | } 266 | func bar() -> Int { 267 | 268 | } 269 | } 270 | """ 271 | 272 | let expected = """ 273 | // swiftfmt 274 | 275 | import Foundation 276 | import SwiftSyntax 277 | 278 | 279 | 280 | class Foo { 281 | let string = "string" 282 | let integer = 0 283 | 284 | 285 | 286 | func foo() -> String { 287 | 288 | 289 | if integer > 0 { 290 | 291 | 292 | let x = integer 293 | let y = integer 294 | 295 | 296 | print(x + y) 297 | 298 | } 299 | 300 | } 301 | 302 | func bar() -> Int { 303 | 304 | } 305 | } 306 | 307 | """ 308 | 309 | let runner = TestRunner() 310 | var configuration = Configuration() 311 | configuration.blankLines.keepMaximumBlankLines.inDeclarations = 3 312 | configuration.blankLines.keepMaximumBlankLines.inCode = 2 313 | configuration.blankLines.keepMaximumBlankLines.beforeClosingBrace = 1 314 | 315 | let result = runner.run(source: source, configuration: configuration) 316 | XCTAssertEqual(result, expected) 317 | } 318 | 319 | func testShouldNotInsertBlankLineBeforeFunctionsAfterComment() { 320 | let source = """ 321 | public class Foo : NSObject { 322 | /// Doc comment 323 | let string = "string" 324 | let integer = 0 325 | 326 | /// Doc comment 327 | func foo() -> String { 328 | var x = 1 329 | var y: Int = 1 330 | x += (x ^ 0x123) << 2 331 | } 332 | } 333 | """ 334 | 335 | let expected = """ 336 | public class Foo : NSObject { 337 | /// Doc comment 338 | let string = "string" 339 | let integer = 0 340 | 341 | /// Doc comment 342 | func foo() -> String { 343 | var x = 1 344 | var y: Int = 1 345 | x += (x ^ 0x123) << 2 346 | } 347 | } 348 | 349 | """ 350 | 351 | let runner = TestRunner() 352 | let configuration = Configuration() 353 | 354 | let result = runner.run(source: source, configuration: configuration) 355 | XCTAssertEqual(result, expected) 356 | } 357 | 358 | func testBlockComments() { 359 | let source = """ 360 | public class Foo : NSObject { 361 | /* comment */ 362 | let string = "string" 363 | let integer = 0 364 | 365 | /* 366 | comment 367 | */ 368 | func foo() -> String { 369 | /* 370 | comment 371 | */ 372 | var x = 1 373 | var y: Int = 1 374 | x += (x ^ 0x123) << 2 375 | } 376 | } 377 | """ 378 | let expected = """ 379 | public class Foo : NSObject { 380 | /* comment */ 381 | let string = "string" 382 | let integer = 0 383 | 384 | /* 385 | comment 386 | */ 387 | func foo() -> String { 388 | /* 389 | comment 390 | */ 391 | var x = 1 392 | var y: Int = 1 393 | x += (x ^ 0x123) << 2 394 | } 395 | } 396 | 397 | """ 398 | 399 | let runner = TestRunner() 400 | let configuration = Configuration() 401 | 402 | let result = runner.run(source: source, configuration: configuration) 403 | XCTAssertEqual(result, expected) 404 | } 405 | 406 | func testShouldNotChangeFunctionBody() { 407 | let source = """ 408 | public class Foo : NSObject { 409 | func foo() -> String { 410 | var x = 1 411 | var y: Int = 1 412 | x += (x ^ 0x123) << 2 413 | } 414 | } 415 | """ 416 | 417 | let expected = """ 418 | public class Foo : NSObject { 419 | func foo() -> String { 420 | var x = 1 421 | var y: Int = 1 422 | x += (x ^ 0x123) << 2 423 | } 424 | } 425 | 426 | """ 427 | 428 | let runner = TestRunner() 429 | let configuration = Configuration() 430 | 431 | let result = runner.run(source: source, configuration: configuration) 432 | XCTAssertEqual(result, expected) 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /Tests/tests/BraceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BraceTests.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import XCTest 9 | @testable import swiftfmt_core 10 | 11 | class BraceTests : XCTestCase { 12 | func testWrapLeftBracesInTypeDeclaration() { 13 | let source = """ 14 | public class Foo { 15 | func foo() { 16 | 17 | } 18 | } 19 | """ 20 | 21 | let expected = """ 22 | public class Foo 23 | { 24 | func foo() { 25 | 26 | } 27 | } 28 | 29 | """ 30 | 31 | let runner = TestRunner() 32 | 33 | var configuration = Configuration() 34 | configuration.braces.placement.inTypeDeclarations = .nextLine 35 | 36 | let result = runner.run(source: source, configuration: configuration) 37 | XCTAssertEqual(result, expected) 38 | } 39 | 40 | func testWrapLeftBracesInFunctionDeclaration() { 41 | let source = """ 42 | public class Foo 43 | { 44 | func foo() { 45 | 46 | } 47 | } 48 | """ 49 | 50 | let expected = """ 51 | public class Foo { 52 | func foo() 53 | { 54 | 55 | } 56 | } 57 | 58 | """ 59 | 60 | let runner = TestRunner() 61 | 62 | var configuration = Configuration() 63 | configuration.braces.placement.inTypeDeclarations = .endOfLine 64 | configuration.braces.placement.inFunctions = .nextLine 65 | 66 | let result = runner.run(source: source, configuration: configuration) 67 | XCTAssertEqual(result, expected) 68 | } 69 | 70 | func testWrapLeftBraces() { 71 | let source = """ 72 | public class Foo { 73 | func foo() { 74 | let x = 0 75 | if x > 0 { 76 | 77 | } else if x > 1 { 78 | 79 | } else { 80 | 81 | } 82 | } 83 | } 84 | """ 85 | 86 | let expected = """ 87 | public class Foo 88 | { 89 | func foo() 90 | { 91 | let x = 0 92 | if x > 0 93 | { 94 | 95 | } else if x > 1 96 | { 97 | 98 | } else 99 | { 100 | 101 | } 102 | } 103 | } 104 | 105 | """ 106 | 107 | let runner = TestRunner() 108 | 109 | var configuration = Configuration() 110 | configuration.braces.placement.inTypeDeclarations = .nextLine 111 | configuration.braces.placement.inFunctions = .nextLine 112 | configuration.braces.placement.inOther = .nextLine 113 | 114 | let result = runner.run(source: source, configuration: configuration) 115 | XCTAssertEqual(result, expected) 116 | } 117 | 118 | func testWrapClosingBraces() { 119 | let source = """ 120 | public class Foo { 121 | func foo() { 122 | let x = 0 123 | if x > 0 { 124 | print(x) 125 | print(x) 126 | } else if x > 1 { 127 | 128 | } else { 129 | print(x) 130 | print(x) } } } 131 | """ 132 | 133 | let expected = """ 134 | public class Foo { 135 | func foo() { 136 | let x = 0 137 | if x > 0 { 138 | print(x) 139 | print(x) 140 | } else if x > 1 { 141 | 142 | } else { 143 | print(x) 144 | print(x) 145 | } 146 | } 147 | } 148 | 149 | """ 150 | 151 | let runner = TestRunner() 152 | 153 | var configuration = Configuration() 154 | configuration.braces.placement.inTypeDeclarations = .endOfLine 155 | configuration.braces.placement.inFunctions = .endOfLine 156 | configuration.braces.placement.inOther = .endOfLine 157 | 158 | let result = runner.run(source: source, configuration: configuration) 159 | XCTAssertEqual(result, expected) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Tests/tests/ConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurationTests.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/21. 6 | // 7 | 8 | import XCTest 9 | @testable import swiftfmt_core 10 | 11 | class ConfigurationTests : XCTestCase { 12 | func testSpacesBeforeParentheses1() { 13 | let source = """ 14 | @objc (bar) 15 | public func foo (a: Int) { 16 | func foo (a: Int) { 17 | 18 | } 19 | if(value != nil) { 20 | print (value) 21 | } 22 | while(value != nil) { 23 | print (value) 24 | } 25 | switch(value) { 26 | case 0: 27 | break 28 | default: 29 | break 30 | } 31 | do { 32 | 33 | } catch(let error) { 34 | 35 | } 36 | } 37 | """ 38 | 39 | let expected = """ 40 | @objc(bar) 41 | public func foo(a: Int) { 42 | func foo(a: Int) { 43 | 44 | } 45 | if (value != nil) { 46 | print(value) 47 | } 48 | while (value != nil) { 49 | print(value) 50 | } 51 | switch (value) { 52 | case 0: 53 | break 54 | default: 55 | break 56 | } 57 | do { 58 | 59 | } catch (let error) { 60 | 61 | } 62 | } 63 | 64 | """ 65 | 66 | let runner = TestRunner() 67 | var configuration = Configuration() 68 | configuration.spaces.before.parentheses.functionDeclaration = false 69 | configuration.spaces.before.parentheses.functionCall = false 70 | configuration.spaces.before.parentheses.if = true 71 | configuration.spaces.before.parentheses.while = true 72 | configuration.spaces.before.parentheses.switch = true 73 | configuration.spaces.before.parentheses.catch = true 74 | configuration.spaces.before.parentheses.attribute = false 75 | 76 | let result = runner.run(source: source, configuration: configuration) 77 | XCTAssertEqual(result, expected) 78 | } 79 | 80 | func testSpacesBeforeIfParentheses2() { 81 | let source = """ 82 | @objc(bar) 83 | public func foo(a: Int) { 84 | func foo(a: Int) { 85 | 86 | } 87 | if (value != nil) { 88 | print(value) 89 | } 90 | while (value != nil) { 91 | print(value) 92 | } 93 | switch (value) { 94 | case 0: 95 | break 96 | default: 97 | break 98 | } 99 | do { 100 | 101 | } catch (let error) { 102 | 103 | } 104 | } 105 | """ 106 | 107 | let expected = """ 108 | @objc (bar) 109 | public func foo (a: Int) { 110 | func foo (a: Int) { 111 | 112 | } 113 | if(value != nil) { 114 | print (value) 115 | } 116 | while(value != nil) { 117 | print (value) 118 | } 119 | switch(value) { 120 | case 0: 121 | break 122 | default: 123 | break 124 | } 125 | do { 126 | 127 | } catch(let error) { 128 | 129 | } 130 | } 131 | 132 | """ 133 | 134 | let runner = TestRunner() 135 | var configuration = Configuration() 136 | configuration.spaces.before.parentheses.functionDeclaration = true 137 | configuration.spaces.before.parentheses.functionCall = true 138 | configuration.spaces.before.parentheses.if = false 139 | configuration.spaces.before.parentheses.while = false 140 | configuration.spaces.before.parentheses.switch = false 141 | configuration.spaces.before.parentheses.catch = false 142 | configuration.spaces.before.parentheses.attribute = true 143 | 144 | let result = runner.run(source: source, configuration: configuration) 145 | XCTAssertEqual(result, expected) 146 | } 147 | 148 | func testSpacesBeforeLeftBrace1() { 149 | let source = """ 150 | public struct Foo{ 151 | public func foo(a: Int){ 152 | func foo(a: Int){ 153 | 154 | } 155 | if value != nil{ 156 | print(value) 157 | } 158 | while value != nil{ 159 | print(value) 160 | } 161 | repeat{ 162 | 163 | } while value != nil 164 | for _ in 0.. String { 27 | do 28 | { 29 | let contents = try String(contentsOfFile: "/usr/bin/swift") 30 | if contents.isEmpty { print(contents) } else if contents.count > 10 { 31 | /* 32 | Block 33 | Comment 34 | */ 35 | for char in contents { 36 | // Line Comment 37 | print( 38 | char 39 | ) 40 | _ = contents.map({ (c) -> String in 41 | _ = 42 | String(c).map({ (c) -> String in 43 | return String(c) 44 | }) 45 | return "" 46 | }).map { 47 | return $0 48 | } 49 | } 50 | } else { 51 | switch x { 52 | case 0: 53 | break 54 | case 1: 55 | print(y) 56 | print(z) 57 | default: 58 | break 59 | } 60 | } 61 | } catch { 62 | print(error) 63 | } 64 | 65 | return "bar" 66 | } 67 | 68 | private class Inner { 69 | let x = 0 70 | } 71 | } 72 | 73 | enum Fruit { 74 | case banana 75 | } 76 | """ 77 | 78 | let expected = """ 79 | import Foundation 80 | import SwiftSyntax 81 | 82 | public class Foo : NSObject { 83 | public let x = [1, 3, 5, 7, 9, 11] 84 | 85 | /// Description 86 | /// 87 | /// - Parameters: 88 | /// - a: param a 89 | /// - b: param b 90 | /// - Returns: String value 91 | func foo(a: Bool, x: Int, y: Int, z: Int) -> String { 92 | do { 93 | let contents = try String(contentsOfFile: "/usr/bin/swift") 94 | if contents.isEmpty { print(contents) } else if contents.count > 10 { 95 | /* 96 | Block 97 | Comment 98 | */ 99 | for char in contents { 100 | // Line Comment 101 | print( 102 | char 103 | ) 104 | _ = contents.map({ (c) -> String in 105 | _ = 106 | String(c).map({ (c) -> String in 107 | return String(c) 108 | }) 109 | return "" 110 | }).map { 111 | return $0 112 | } 113 | } 114 | } else { 115 | switch x { 116 | case 0: 117 | break 118 | case 1: 119 | print(y) 120 | print(z) 121 | default: 122 | break 123 | } 124 | } 125 | } catch { 126 | print(error) 127 | } 128 | 129 | return "bar" 130 | } 131 | 132 | private class Inner { 133 | let x = 0 134 | } 135 | } 136 | 137 | enum Fruit { 138 | case banana 139 | } 140 | 141 | """ 142 | 143 | let runner = TestRunner() 144 | let configuration = Configuration() 145 | 146 | let result = runner.run(source: source, configuration: configuration) 147 | XCTAssertEqual(result, expected) 148 | } 149 | 150 | func testIndentSwitchStatementCaseBranches() { 151 | let source = """ 152 | public class Foo { 153 | func foo(x: Int, y: Int, z: Int) { 154 | switch x { 155 | case 0: 156 | break 157 | case 1: 158 | print(y) 159 | print(z) 160 | default: 161 | print(y) 162 | print(z) 163 | } 164 | } 165 | } 166 | """ 167 | 168 | let expected = """ 169 | public class Foo { 170 | func foo(x: Int, y: Int, z: Int) { 171 | switch x { 172 | case 0: 173 | break 174 | case 1: 175 | print(y) 176 | print(z) 177 | default: 178 | print(y) 179 | print(z) 180 | } 181 | } 182 | } 183 | 184 | """ 185 | 186 | let runner = TestRunner() 187 | 188 | var configuration = Configuration() 189 | configuration.indentation.indentCaseBranches = true 190 | 191 | let result = runner.run(source: source, configuration: configuration) 192 | XCTAssertEqual(result, expected) 193 | } 194 | 195 | func testIndentClosureExpressions1() { 196 | let source = """ 197 | public class Foo { 198 | func foo(x: Int, y: Int, z: Int) { 199 | var tokens = ["a", "b"] 200 | tokens.map { 201 | print($0) 202 | }.map { 203 | "c" 204 | } 205 | } 206 | } 207 | """ 208 | 209 | let expected = """ 210 | public class Foo { 211 | func foo(x: Int, y: Int, z: Int) { 212 | var tokens = ["a", "b"] 213 | tokens.map { 214 | print($0) 215 | }.map { 216 | "c" 217 | } 218 | } 219 | } 220 | 221 | """ 222 | 223 | let runner = TestRunner() 224 | let configuration = Configuration() 225 | 226 | let result = runner.run(source: source, configuration: configuration) 227 | XCTAssertEqual(result, expected) 228 | } 229 | 230 | func testIndentClosureExpressions2() { 231 | let source = """ 232 | public class ViewController : UIViewController { 233 | override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { 234 | coordinator.animate(alongsideTransition: { (context) in 235 | self.adjustThumbnailViewHeight() 236 | }, completion: nil) 237 | } 238 | } 239 | """ 240 | 241 | let expected = """ 242 | public class ViewController : UIViewController { 243 | override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { 244 | coordinator.animate(alongsideTransition: { (context) in 245 | self.adjustThumbnailViewHeight() 246 | }, completion: nil) 247 | } 248 | } 249 | 250 | """ 251 | 252 | let runner = TestRunner() 253 | let configuration = Configuration() 254 | 255 | let result = runner.run(source: source, configuration: configuration) 256 | XCTAssertEqual(result, expected) 257 | } 258 | 259 | func testContinuousStatement() { 260 | let source = """ 261 | func actionMenuViewControllerShareDocument(_ actionMenuViewController: ActionMenuViewController) { 262 | let mailComposeViewController = MFMailComposeViewController() 263 | if let lastPathComponent = pdfDocument?.documentURL?.lastPathComponent, 264 | let documentAttributes = pdfDocument?.documentAttributes, 265 | let attachmentData = pdfDocument?.dataRepresentation() { 266 | if let title = documentAttributes["Title"] as? String { 267 | mailComposeViewController.setSubject(title) 268 | } 269 | mailComposeViewController.addAttachmentData(attachmentData, mimeType: "application/pdf", fileName: lastPathComponent) 270 | } 271 | } 272 | 273 | @objc func showSearchView(_ sender: UIBarButtonItem) { 274 | if let searchNavigationController = self.searchNavigationController { 275 | present(searchNavigationController, animated: true, completion: nil) 276 | } else if let navigationController = storyboard?.instantiateViewController(withIdentifier: String(describing: SearchViewController.self)) as? UINavigationController, 277 | let searchViewController = navigationController.topViewController as? SearchViewController { 278 | searchViewController.pdfDocument = pdfDocument 279 | searchViewController.delegate = self 280 | present(navigationController, animated: true, completion: nil) 281 | 282 | searchNavigationController = navigationController 283 | } 284 | } 285 | """ 286 | let expected = """ 287 | func actionMenuViewControllerShareDocument(_ actionMenuViewController: ActionMenuViewController) { 288 | let mailComposeViewController = MFMailComposeViewController() 289 | if let lastPathComponent = pdfDocument?.documentURL?.lastPathComponent, 290 | let documentAttributes = pdfDocument?.documentAttributes, 291 | let attachmentData = pdfDocument?.dataRepresentation() { 292 | if let title = documentAttributes["Title"] as? String { 293 | mailComposeViewController.setSubject(title) 294 | } 295 | mailComposeViewController.addAttachmentData(attachmentData, mimeType: "application/pdf", fileName: lastPathComponent) 296 | } 297 | } 298 | 299 | @objc func showSearchView(_ sender: UIBarButtonItem) { 300 | if let searchNavigationController = self.searchNavigationController { 301 | present(searchNavigationController, animated: true, completion: nil) 302 | } else if let navigationController = storyboard?.instantiateViewController(withIdentifier: String(describing: SearchViewController.self)) as? UINavigationController, 303 | let searchViewController = navigationController.topViewController as? SearchViewController { 304 | searchViewController.pdfDocument = pdfDocument 305 | searchViewController.delegate = self 306 | present(navigationController, animated: true, completion: nil) 307 | 308 | searchNavigationController = navigationController 309 | } 310 | } 311 | 312 | """ 313 | 314 | let runner = TestRunner() 315 | let configuration = Configuration() 316 | 317 | let result = runner.run(source: source, configuration: configuration) 318 | XCTAssertEqual(result, expected) 319 | } 320 | 321 | func testStringInterpolation() { 322 | let source = """ 323 | open class AcknowListViewController: UITableViewController { 324 | // MARK: - Paths 325 | 326 | class func acknowledgementsPlistPath(name:String) -> String? { 327 | return Bundle.main.path(forResource: name, ofType: "plist") 328 | } 329 | 330 | class func defaultAcknowledgementsPlistPath() -> String? { 331 | guard let bundleName = Bundle.main.infoDictionary?["CFBundleName"] as? String else { 332 | return nil 333 | } 334 | 335 | let defaultAcknowledgementsPlistName = "Pods-\\(bundleName)-acknowledgements" 336 | let defaultAcknowledgementsPlistPath = self.acknowledgementsPlistPath(name: defaultAcknowledgementsPlistName) 337 | 338 | if let defaultAcknowledgementsPlistPath = defaultAcknowledgementsPlistPath, 339 | FileManager.default.fileExists(atPath: defaultAcknowledgementsPlistPath) == true { 340 | return defaultAcknowledgementsPlistPath 341 | } 342 | else { 343 | // Legacy value 344 | return self.acknowledgementsPlistPath(name: "Pods-acknowledgements") 345 | } 346 | } 347 | } 348 | """ 349 | let expected = """ 350 | open class AcknowListViewController : UITableViewController { 351 | // MARK: - Paths 352 | 353 | class func acknowledgementsPlistPath(name: String) -> String? { 354 | return Bundle.main.path(forResource: name, ofType: "plist") 355 | } 356 | 357 | class func defaultAcknowledgementsPlistPath() -> String? { 358 | guard let bundleName = Bundle.main.infoDictionary?["CFBundleName"] as? String else { 359 | return nil 360 | } 361 | 362 | let defaultAcknowledgementsPlistName = "Pods-\\(bundleName)-acknowledgements" 363 | let defaultAcknowledgementsPlistPath = self.acknowledgementsPlistPath(name: defaultAcknowledgementsPlistName) 364 | 365 | if let defaultAcknowledgementsPlistPath = defaultAcknowledgementsPlistPath, 366 | FileManager.default.fileExists(atPath: defaultAcknowledgementsPlistPath) == true { 367 | return defaultAcknowledgementsPlistPath 368 | } else { 369 | // Legacy value 370 | return self.acknowledgementsPlistPath(name: "Pods-acknowledgements") 371 | } 372 | } 373 | } 374 | 375 | """ 376 | 377 | let runner = TestRunner() 378 | let configuration = Configuration() 379 | 380 | let result = runner.run(source: source, configuration: configuration) 381 | XCTAssertEqual(result, expected) 382 | } 383 | 384 | func testAligmentClosureArguments1() { 385 | let source = """ 386 | open class AcknowListViewController : UITableViewController { 387 | @discardableResult 388 | public func setImage(with resource: Resource?, 389 | placeholder: Placeholder? = nil, 390 | options: KingfisherOptionsInfo? = nil, 391 | progressBlock: DownloadProgressBlock? = nil, 392 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask { 393 | UIView.transition(with: strongBase, duration: 0.0, options: [], animations: { maybeIndicator?.stopAnimatingView() }, completion: { _ in 394 | self.placeholder = nil 395 | UIView.transition(with: strongBase, duration: transition.duration, options: [transition.animationOptions, .allowUserInteraction], animations: { 396 | // Set image property in the animation. 397 | transition.animations?(strongBase, image) 398 | }, completion: { finished in 399 | transition.completion?(finished) 400 | completionHandler?(image, error, cacheType, imageURL) 401 | }) 402 | }) 403 | } 404 | } 405 | """ 406 | let expected = """ 407 | open class AcknowListViewController : UITableViewController { 408 | @discardableResult 409 | public func setImage(with resource: Resource?, 410 | placeholder: Placeholder? = nil, 411 | options: KingfisherOptionsInfo? = nil, 412 | progressBlock: DownloadProgressBlock? = nil, 413 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask { 414 | UIView.transition(with: strongBase, duration: 0.0, options: [], animations: { maybeIndicator?.stopAnimatingView() }, completion: { _ in 415 | self.placeholder = nil 416 | UIView.transition(with: strongBase, duration: transition.duration, options: [transition.animationOptions, .allowUserInteraction], animations: { 417 | // Set image property in the animation. 418 | transition.animations?(strongBase, image) 419 | }, completion: { finished in 420 | transition.completion?(finished) 421 | completionHandler?(image, error, cacheType, imageURL) 422 | }) 423 | }) 424 | } 425 | } 426 | 427 | """ 428 | 429 | let runner = TestRunner() 430 | let configuration = Configuration() 431 | 432 | let result = runner.run(source: source, configuration: configuration) 433 | XCTAssertEqual(result, expected) 434 | } 435 | 436 | func testAligmentClosureArguments2() { 437 | let source = """ 438 | open class AcknowListViewController : UITableViewController { 439 | @discardableResult 440 | public func setImage(with resource: Resource?, 441 | placeholder: Placeholder? = nil, 442 | options: KingfisherOptionsInfo? = nil, 443 | progressBlock: DownloadProgressBlock? = nil, 444 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask { 445 | UIView.transition(with: strongBase, duration: 0.0, options: [], 446 | animations: { maybeIndicator?.stopAnimatingView() }, 447 | completion: { _ in 448 | self.placeholder = nil 449 | UIView.transition(with: strongBase, duration: transition.duration, 450 | options: [transition.animationOptions, .allowUserInteraction], 451 | animations: { 452 | // Set image property in the animation. 453 | transition.animations?(strongBase, image) 454 | }, 455 | completion: { finished in 456 | transition.completion?(finished) 457 | completionHandler?(image, error, cacheType, imageURL) 458 | }) 459 | }) 460 | } 461 | } 462 | """ 463 | let expected = """ 464 | open class AcknowListViewController : UITableViewController { 465 | @discardableResult 466 | public func setImage(with resource: Resource?, 467 | placeholder: Placeholder? = nil, 468 | options: KingfisherOptionsInfo? = nil, 469 | progressBlock: DownloadProgressBlock? = nil, 470 | completionHandler: CompletionHandler? = nil) -> RetrieveImageTask { 471 | UIView.transition(with: strongBase, duration: 0.0, options: [], 472 | animations: { maybeIndicator?.stopAnimatingView() }, 473 | completion: { _ in 474 | self.placeholder = nil 475 | UIView.transition(with: strongBase, duration: transition.duration, 476 | options: [transition.animationOptions, .allowUserInteraction], 477 | animations: { 478 | // Set image property in the animation. 479 | transition.animations?(strongBase, image) 480 | }, 481 | completion: { finished in 482 | transition.completion?(finished) 483 | completionHandler?(image, error, cacheType, imageURL) 484 | }) 485 | }) 486 | } 487 | } 488 | 489 | """ 490 | 491 | let runner = TestRunner() 492 | let configuration = Configuration() 493 | 494 | let result = runner.run(source: source, configuration: configuration) 495 | XCTAssertEqual(result, expected) 496 | } 497 | 498 | func testAlignment() { 499 | let source = """ 500 | public func object(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element? { 501 | return unsafeBitCast(RLMGetObject(rlmRealm, (type as Object.Type).className(), 502 | dynamicBridgeCast(fromSwift: key)) as! RLMObjectBase?, 503 | to: Optional.self) 504 | } 505 | """ 506 | let expected = """ 507 | public func object(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element? { 508 | return unsafeBitCast(RLMGetObject(rlmRealm, (type as Object.Type).className(), 509 | dynamicBridgeCast(fromSwift: key)) as! RLMObjectBase?, 510 | to: Optional.self) 511 | } 512 | 513 | """ 514 | 515 | let runner = TestRunner() 516 | let configuration = Configuration() 517 | 518 | let result = runner.run(source: source, configuration: configuration) 519 | XCTAssertEqual(result, expected) 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /Tests/tests/SemicolonTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SemicolonTests.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/19. 6 | // 7 | 8 | import XCTest 9 | @testable import swiftfmt_core 10 | 11 | class SemicolonTests : XCTestCase { 12 | func testFormatSemicolons() { 13 | let source = """ 14 | import Foundation; import SwiftSyntax; class Foo {}; protocol FooProtocol {}; 15 | 16 | struct Bar {}; 17 | enum Baz {}; struct FooBar {}; 18 | """ 19 | 20 | let expected = """ 21 | import Foundation; 22 | import SwiftSyntax; 23 | 24 | class Foo {}; 25 | 26 | protocol FooProtocol {}; 27 | 28 | struct Bar {}; 29 | 30 | enum Baz {}; 31 | 32 | struct FooBar {}; 33 | 34 | """ 35 | 36 | let runner = TestRunner() 37 | var configuration = Configuration() 38 | configuration.shouldRemoveSemicons = false 39 | 40 | let result = runner.run(source: source, configuration: configuration) 41 | XCTAssertEqual(result, expected) 42 | } 43 | 44 | func testRemoveSemicolons() { 45 | let source = """ 46 | import Foundation; import SwiftSyntax; class Foo {}; protocol FooProtocol {}; 47 | 48 | struct Bar {}; 49 | enum Baz {}; struct FooBar {}; 50 | """ 51 | 52 | let expected = """ 53 | import Foundation 54 | import SwiftSyntax 55 | 56 | class Foo {} 57 | 58 | protocol FooProtocol {} 59 | 60 | struct Bar {} 61 | 62 | enum Baz {} 63 | 64 | struct FooBar {} 65 | 66 | """ 67 | 68 | let runner = TestRunner() 69 | var configuration = Configuration() 70 | configuration.shouldRemoveSemicons = true 71 | 72 | let result = runner.run(source: source, configuration: configuration) 73 | XCTAssertEqual(result, expected) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/tests/TestRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestRunner.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/18. 6 | // 7 | 8 | import Foundation 9 | import Basic 10 | @testable import swiftfmt_core 11 | 12 | class TestRunner { 13 | func run(source: String, configuration: Configuration) -> String { 14 | let temporaryDirectory = try! TemporaryDirectory(prefix: "com.kishikawakatsumi.swiftfmt", removeTreeOnDeinit: true) 15 | let temporaryFile = try! TemporaryFile(dir: temporaryDirectory.path, prefix: "Tests", suffix: ".swift").path 16 | let sourceFilePath = temporaryFile.asString 17 | 18 | try! source.write(toFile: sourceFilePath, atomically: true, encoding: .utf8) 19 | 20 | let processor = Processor() 21 | let result = try! processor.processFile(input: URL(fileURLWithPath: sourceFilePath), configuration: configuration, verbose: true) 22 | print(result) 23 | 24 | return result 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/tests/WrappingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WrappingTests.swift 3 | // tests 4 | // 5 | // Created by Kishikawa Katsumi on 2018/02/19. 6 | // 7 | 8 | import XCTest 9 | @testable import swiftfmt_core 10 | 11 | class WrappingTests : XCTestCase { 12 | func testWrappingIfStatementElse() { 13 | let source = """ 14 | public class Foo { 15 | func foo(value: String?) { 16 | let defValue: String? = "" 17 | 18 | if (value != nil) { 19 | print(value) 20 | } else if (defValue != nil) { 21 | print(defValue) 22 | } else { 23 | print("Error") 24 | } 25 | guard value ?? defValue != nil else { 26 | return 27 | } 28 | } 29 | } 30 | """ 31 | 32 | let expected = """ 33 | public class Foo { 34 | func foo(value: String?) { 35 | let defValue: String? = "" 36 | 37 | if (value != nil) { 38 | print(value) 39 | } 40 | else if (defValue != nil) { 41 | print(defValue) 42 | } 43 | else { 44 | print("Error") 45 | } 46 | guard value ?? defValue != nil 47 | else { 48 | return 49 | } 50 | } 51 | } 52 | 53 | """ 54 | 55 | let runner = TestRunner() 56 | 57 | var configuration = Configuration() 58 | configuration.wrapping.ifStatement.elseOnNewLine = true 59 | configuration.wrapping.guardStatement.elseOnNewLine = true 60 | 61 | let result = runner.run(source: source, configuration: configuration) 62 | XCTAssertEqual(result, expected) 63 | } 64 | 65 | func testWrappingRepeatWhileStatementWhile() { 66 | let source = """ 67 | public class Foo { 68 | func foo(value: Int) { 69 | var value = value 70 | 71 | repeat { 72 | value += 1 73 | } while value > 0 74 | } 75 | } 76 | """ 77 | 78 | let expected = """ 79 | public class Foo { 80 | func foo(value: Int) { 81 | var value = value 82 | 83 | repeat { 84 | value += 1 85 | } 86 | while value > 0 87 | } 88 | } 89 | 90 | """ 91 | 92 | let runner = TestRunner() 93 | 94 | var configuration = Configuration() 95 | configuration.wrapping.ifStatement.elseOnNewLine = true 96 | configuration.wrapping.guardStatement.elseOnNewLine = true 97 | configuration.wrapping.repeatWhileStatement.whileOnNewLine = true 98 | 99 | let result = runner.run(source: source, configuration: configuration) 100 | XCTAssertEqual(result, expected) 101 | } 102 | 103 | func testWrappingDoStatementCatch() { 104 | let source = """ 105 | public class Foo { 106 | func foo() throws { 107 | do { 108 | try foo() 109 | } catch { 110 | print(error) 111 | } 112 | } 113 | } 114 | """ 115 | 116 | let expected = """ 117 | public class Foo { 118 | func foo() throws { 119 | do { 120 | try foo() 121 | } 122 | catch { 123 | print(error) 124 | } 125 | } 126 | } 127 | 128 | """ 129 | 130 | let runner = TestRunner() 131 | 132 | var configuration = Configuration() 133 | configuration.wrapping.ifStatement.elseOnNewLine = true 134 | configuration.wrapping.guardStatement.elseOnNewLine = true 135 | configuration.wrapping.repeatWhileStatement.whileOnNewLine = true 136 | configuration.wrapping.doStatement.catchOnNewLine = true 137 | 138 | let result = runner.run(source: source, configuration: configuration) 139 | XCTAssertEqual(result, expected) 140 | } 141 | 142 | func testWrapSwitchStatementCaseBranches() { 143 | let source = """ 144 | public class Foo { 145 | func foo(x: Int, y: Int, z: Int) { 146 | switch x { 147 | case 0: break case 1: print(y) 148 | print(z) 149 | default: 150 | print(y) 151 | print(z) 152 | } 153 | } 154 | } 155 | """ 156 | 157 | let expected = """ 158 | public class Foo { 159 | func foo(x: Int, y: Int, z: Int) { 160 | switch x { 161 | case 0: break case 1: print(y) 162 | print(z) 163 | default: 164 | print(y) 165 | print(z) 166 | } 167 | } 168 | } 169 | 170 | """ 171 | 172 | let runner = TestRunner() 173 | 174 | var configuration = Configuration() 175 | configuration.indentation.indentCaseBranches = true 176 | 177 | let result = runner.run(source: source, configuration: configuration) 178 | XCTAssertEqual(result, expected) 179 | } 180 | 181 | func testElseOnSameline() { 182 | let source = """ 183 | if (headerFooter.header == DefaultHeaderText) { 184 | self.headerText = nil 185 | } 186 | else if (headerFooter.header != "") { 187 | self.headerText = headerFooter.header 188 | } 189 | """ 190 | 191 | let expected = """ 192 | if (headerFooter.header == DefaultHeaderText) { 193 | self.headerText = nil 194 | } else if (headerFooter.header != "") { 195 | self.headerText = headerFooter.header 196 | } 197 | 198 | """ 199 | 200 | let runner = TestRunner() 201 | let configuration = Configuration() 202 | 203 | let result = runner.run(source: source, configuration: configuration) 204 | XCTAssertEqual(result, expected) 205 | } 206 | } 207 | --------------------------------------------------------------------------------