From 6ce3bd52c1975568b4b457bbada110c61e1f5c8e Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 29 Jul 2024 09:31:37 -0700 Subject: [PATCH] Implement each rule in a separate file (#1782) --- PerformanceTests/PerformanceTests.swift | 51 +- Sources/DeclarationHelpers.swift | 2 +- Sources/FormatRule.swift | 185 + Sources/FormattingHelpers.swift | 2 +- Sources/RuleRegistry.generated.swift | 122 + Sources/Rules.swift | 8427 ------------ Sources/Rules/Acronyms.swift | 76 + Sources/Rules/AndOperator.swift | 85 + Sources/Rules/AnyObjectProtocol.swift | 31 + Sources/Rules/ApplicationMain.swift | 32 + Sources/Rules/AssertionFailures.swift | 43 + Sources/Rules/BlankLineAfterImports.swift | 40 + Sources/Rules/BlankLineAfterSwitchCase.swift | 43 + Sources/Rules/BlankLinesAroundMark.swift | 38 + Sources/Rules/BlankLinesAtEndOfScope.swift | 63 + Sources/Rules/BlankLinesAtStartOfScope.swift | 53 + .../BlankLinesBetweenChainedFunctions.swift | 43 + Sources/Rules/BlankLinesBetweenImports.swift | 32 + Sources/Rules/BlankLinesBetweenScopes.swift | 109 + Sources/Rules/BlockComments.swift | 138 + Sources/Rules/Braces.swift | 89 + Sources/Rules/ConditionalAssignment.swift | 251 + Sources/Rules/ConsecutiveBlankLines.swift | 32 + Sources/Rules/ConsecutiveSpaces.swift | 48 + .../Rules/ConsistentSwitchCaseSpacing.swift | 47 + Sources/Rules/DocComments.swift | 177 + .../Rules/DocCommentsBeforeAttributes.swift | 42 + Sources/Rules/DuplicateImports.swift | 35 + Sources/Rules/ElseOnSameLine.swift | 118 + Sources/Rules/EmptyBraces.swift | 41 + Sources/Rules/EnumNamespaces.swift | 124 + Sources/Rules/ExtensionAccessControl.swift | 121 + Sources/Rules/FileHeader.swift | 83 + Sources/Rules/GenericExtensions.swift | 127 + Sources/Rules/HeaderFileName.swift | 34 + Sources/Rules/HoistAwait.swift | 27 + Sources/Rules/HoistPatternLet.swift | 167 + Sources/Rules/HoistTry.swift | 28 + Sources/Rules/Indent.swift | 798 ++ Sources/Rules/InitCoderUnavailable.swift | 60 + Sources/Rules/IsEmpty.swift | 95 + Sources/Rules/LeadingDelimiters.swift | 37 + Sources/Rules/LinebreakAtEndOfFile.swift | 34 + Sources/Rules/Linebreaks.swift | 21 + Sources/Rules/MarkTypes.swift | 254 + Sources/Rules/ModifierOrder.swift | 74 + Sources/Rules/NoExplicitOwnership.swift | 47 + Sources/Rules/NumberFormatting.swift | 105 + Sources/Rules/OpaqueGenericParameters.swift | 296 + Sources/Rules/OrganizeDeclarations.swift | 45 + Sources/Rules/PreferForLoop.swift | 291 + Sources/Rules/PreferKeyPath.swift | 79 + Sources/Rules/PropertyType.swift | 208 + Sources/Rules/RedundantBackticks.swift | 23 + Sources/Rules/RedundantBreak.swift | 31 + Sources/Rules/RedundantClosure.swift | 193 + Sources/Rules/RedundantExtensionACL.swift | 35 + Sources/Rules/RedundantFileprivate.swift | 203 + Sources/Rules/RedundantGet.swift | 34 + Sources/Rules/RedundantInit.swift | 68 + Sources/Rules/RedundantInternal.swift | 42 + Sources/Rules/RedundantLet.swift | 42 + Sources/Rules/RedundantLetError.swift | 28 + Sources/Rules/RedundantNilInit.swift | 76 + Sources/Rules/RedundantObjc.swift | 87 + Sources/Rules/RedundantOptionalBinding.swift | 43 + Sources/Rules/RedundantParens.swift | 228 + Sources/Rules/RedundantPattern.swift | 72 + Sources/Rules/RedundantProperty.swift | 52 + Sources/Rules/RedundantRawValues.swift | 50 + Sources/Rules/RedundantReturn.swift | 232 + Sources/Rules/RedundantSelf.swift | 21 + Sources/Rules/RedundantStaticSelf.swift | 18 + Sources/Rules/RedundantType.swift | 190 + Sources/Rules/RedundantTypedThrows.swift | 41 + Sources/Rules/RedundantVoidReturnType.swift | 58 + Sources/Rules/Semicolons.swift | 52 + Sources/Rules/SortDeclarations.swift | 155 + Sources/Rules/SortImports.swift | 54 + Sources/Rules/SortSwitchCases.swift | 92 + Sources/Rules/SortTypealiases.swift | 135 + Sources/Rules/SortedImports.swift | 23 + Sources/Rules/SortedSwitchCases.swift | 19 + Sources/Rules/SpaceAroundBraces.swift | 39 + Sources/Rules/SpaceAroundBrackets.swift | 72 + Sources/Rules/SpaceAroundComments.swift | 46 + Sources/Rules/SpaceAroundGenerics.swift | 24 + Sources/Rules/SpaceAroundOperators.swift | 142 + Sources/Rules/SpaceAroundParens.swift | 111 + Sources/Rules/SpaceInsideBraces.swift | 35 + Sources/Rules/SpaceInsideBrackets.swift | 31 + Sources/Rules/SpaceInsideComments.swift | 56 + Sources/Rules/SpaceInsideGenerics.swift | 29 + Sources/Rules/SpaceInsideParens.swift | 31 + Sources/Rules/Specifiers.swift | 21 + Sources/Rules/StrongOutlets.swift | 35 + Sources/Rules/StrongifiedSelf.swift | 28 + Sources/Rules/Todos.swift | 69 + Sources/Rules/TrailingClosures.swift | 68 + Sources/Rules/TrailingCommas.swift | 55 + Sources/Rules/TrailingSpace.swift | 27 + Sources/Rules/TypeSugar.swift | 105 + Sources/Rules/UnusedArguments.swift | 315 + Sources/Rules/UnusedPrivateDeclaration.swift | 66 + Sources/Rules/Void.swift | 138 + Sources/Rules/Wrap.swift | 68 + Sources/Rules/WrapArguments.swift | 24 + Sources/Rules/WrapAttributes.swift | 113 + Sources/Rules/WrapConditionalBodies.swift | 24 + Sources/Rules/WrapEnumCases.swift | 70 + Sources/Rules/WrapLoopBodies.swift | 29 + .../WrapMultilineConditionalAssignment.swift | 60 + .../Rules/WrapMultilineStatementBraces.swift | 35 + Sources/Rules/WrapSingleLineComments.swift | 65 + Sources/Rules/WrapSwitchCases.swift | 36 + Sources/Rules/YodaConditions.swift | 139 + Sources/SwiftFormat.swift | 4 +- Sources/Tokenizer.swift | 2 +- SwiftFormat.xcodeproj/project.pbxproj | 1634 ++- Tests/CommandLineTests.swift | 4 +- Tests/FormatterTests.swift | 6 +- Tests/GlobsTests.swift | 8 +- Tests/MetadataTests.swift | 343 +- Tests/ReporterTests.swift | 2 +- Tests/Rules/AcronymsTests.swift | 78 + Tests/Rules/AndOperatorTests.swift | 184 + Tests/Rules/AnyObjectProtocolTests.swift | 52 + Tests/Rules/ApplicationMainTests.swift | 47 + Tests/Rules/AssertionFailuresTests.swift | 62 + Tests/Rules/BlankLineAfterImportsTests.swift | 110 + .../Rules/BlankLineAfterSwitchCaseTests.swift | 213 + Tests/Rules/BlankLinesAroundMarkTests.swift | 123 + Tests/Rules/BlankLinesAtEndOfScopeTests.swift | 107 + .../Rules/BlankLinesAtStartOfScopeTests.swift | 106 + ...ankLinesBetweenChainedFunctionsTests.swift | 64 + .../Rules/BlankLinesBetweenImportsTests.swift | 75 + .../Rules/BlankLinesBetweenScopesTests.swift | 218 + Tests/Rules/BlockCommentsTests.swift | 298 + .../BracesTests.swift} | 114 +- Tests/Rules/ConditionalAssignmentTests.swift | 828 ++ Tests/Rules/ConsecutiveBlankLinesTests.swift | 86 + Tests/Rules/ConsecutiveSpacesTests.swift | 56 + .../ConsistentSwitchCaseSpacingTests.swift | 327 + .../DocCommentsBeforeAttributesTests.swift | 197 + Tests/Rules/DocCommentsTests.swift | 579 + Tests/Rules/DuplicateImportsTests.swift | 64 + Tests/Rules/ElseOnSameLineTests.swift | 382 + Tests/Rules/EmptyBracesTests.swift | 111 + Tests/Rules/EnumNamespacesTests.swift | 455 + Tests/Rules/ExtensionAccessControlTests.swift | 463 + Tests/Rules/FileHeaderTests.swift | 514 + Tests/Rules/GenericExtensionsTests.swift | 121 + Tests/Rules/HeaderFileNameTests.swift | 27 + Tests/Rules/HoistAwaitTests.swift | 234 + Tests/Rules/HoistPatternLetTests.swift | 423 + Tests/Rules/HoistTryTests.swift | 440 + .../IndentTests.swift} | 764 +- Tests/Rules/InitCoderUnavailableTests.swift | 143 + Tests/Rules/IsEmptyTests.swift | 159 + Tests/Rules/LeadingDelimitersTests.swift | 48 + Tests/Rules/LinebreakAtEndOfFileTests.swift | 24 + Tests/Rules/LinebreaksTests.swift | 36 + Tests/Rules/MarkTypesTests.swift | 838 ++ Tests/Rules/ModifierOrderTests.swift | 98 + Tests/Rules/NoExplicitOwnershipTests.swift | 64 + Tests/Rules/NumberFormattingTests.swift | 203 + .../Rules/OpaqueGenericParametersTests.swift | 694 + Tests/Rules/OrganizeDeclarationsTests.swift | 2980 +++++ Tests/Rules/PreferForLoopTests.swift | 409 + Tests/Rules/PreferKeyPathTests.swift | 113 + Tests/Rules/PropertyTypeTests.swift | 560 + Tests/Rules/RedundantBackticksTests.swift | 214 + Tests/Rules/RedundantBreakTests.swift | 79 + Tests/Rules/RedundantClosureTests.swift | 1040 ++ Tests/Rules/RedundantExtensionACLTests.swift | 48 + Tests/Rules/RedundantFileprivateTests.swift | 575 + Tests/Rules/RedundantGetTests.swift | 56 + Tests/Rules/RedundantInitTests.swift | 223 + Tests/Rules/RedundantInternalTests.swift | 108 + Tests/Rules/RedundantLetErrorTests.swift | 24 + Tests/Rules/RedundantLetTests.swift | 136 + Tests/Rules/RedundantNilInitTests.swift | 489 + Tests/Rules/RedundantObjcTests.swift | 161 + .../Rules/RedundantOptionalBindingTests.swift | 183 + .../RedundantParensTests.swift} | 390 +- Tests/Rules/RedundantPatternTests.swift | 55 + Tests/Rules/RedundantPropertyTests.swift | 182 + Tests/Rules/RedundantRawValuesTests.swift | 37 + Tests/Rules/RedundantReturnTests.swift | 1311 ++ Tests/Rules/RedundantSelfTests.swift | 3393 +++++ Tests/Rules/RedundantStaticSelfTests.swift | 226 + Tests/Rules/RedundantTypeTests.swift | 713 + Tests/Rules/RedundantTypedThrowsTests.swift | 61 + .../Rules/RedundantVoidReturnTypeTests.swift | 115 + Tests/Rules/SemicolonsTests.swift | 77 + Tests/Rules/SortDeclarationsTests.swift | 289 + Tests/Rules/SortImportsTests.swift | 214 + Tests/Rules/SortSwitchCasesTests.swift | 340 + Tests/Rules/SortTypealiasesTests.swift | 178 + Tests/Rules/SpaceAroundBracesTests.swift | 64 + Tests/Rules/SpaceAroundBracketsTests.swift | 106 + Tests/Rules/SpaceAroundCommentsTests.swift | 36 + Tests/Rules/SpaceAroundGenericsTests.swift | 28 + Tests/Rules/SpaceAroundOperatorsTests.swift | 754 ++ Tests/Rules/SpaceAroundParensTests.swift | 347 + Tests/Rules/SpaceInsideBracesTests.swift | 33 + Tests/Rules/SpaceInsideBracketsTests.swift | 31 + Tests/Rules/SpaceInsideCommentsTests.swift | 119 + Tests/Rules/SpaceInsideGenericsTests.swift | 18 + Tests/Rules/SpaceInsideParensTests.swift | 24 + Tests/Rules/StrongOutletsTests.swift | 63 + Tests/Rules/StrongifiedSelfTests.swift | 71 + Tests/Rules/TodosTests.swift | 150 + Tests/Rules/TrailingClosuresTests.swift | 229 + Tests/Rules/TrailingCommasTests.swift | 275 + Tests/Rules/TrailingSpaceTests.swift | 58 + Tests/Rules/TypeSugarTests.swift | 238 + Tests/Rules/UnusedArgumentsTests.swift | 1209 ++ .../Rules/UnusedPrivateDeclarationTests.swift | 380 + Tests/Rules/VoidTests.swift | 261 + Tests/Rules/WrapArgumentsTests.swift | 2167 +++ Tests/Rules/WrapAttributesTests.swift | 645 + Tests/Rules/WrapConditionalBodiesTests.swift | 284 + Tests/Rules/WrapEnumCasesTests.swift | 229 + Tests/Rules/WrapLoopBodiesTests.swift | 42 + ...pMultilineConditionalAssignmentTests.swift | 148 + .../WrapMultilineStatementBracesTests.swift | 727 + Tests/Rules/WrapSingleLineCommentsTests.swift | 160 + Tests/Rules/WrapSwitchCasesTests.swift | 53 + Tests/Rules/WrapTests.swift | 725 + Tests/Rules/YodaConditionsTests.swift | 293 + Tests/RulesTests+General.swift | 1366 -- Tests/RulesTests+Hoisting.swift | 1079 -- Tests/RulesTests+Linebreaks.swift | 861 -- Tests/RulesTests+Organization.swift | 5332 -------- Tests/RulesTests+Redundancy.swift | 10939 ---------------- Tests/RulesTests+Spacing.swift | 2177 --- Tests/RulesTests+Syntax.swift | 5718 -------- Tests/RulesTests+Wrapping.swift | 5481 -------- Tests/SwiftFormatTests.swift | 26 +- ....swift => XCTestCase+testFormatting.swift} | 21 +- 241 files changed, 46176 insertions(+), 42255 deletions(-) create mode 100644 Sources/FormatRule.swift create mode 100644 Sources/RuleRegistry.generated.swift delete mode 100644 Sources/Rules.swift create mode 100644 Sources/Rules/Acronyms.swift create mode 100644 Sources/Rules/AndOperator.swift create mode 100644 Sources/Rules/AnyObjectProtocol.swift create mode 100644 Sources/Rules/ApplicationMain.swift create mode 100644 Sources/Rules/AssertionFailures.swift create mode 100644 Sources/Rules/BlankLineAfterImports.swift create mode 100644 Sources/Rules/BlankLineAfterSwitchCase.swift create mode 100644 Sources/Rules/BlankLinesAroundMark.swift create mode 100644 Sources/Rules/BlankLinesAtEndOfScope.swift create mode 100644 Sources/Rules/BlankLinesAtStartOfScope.swift create mode 100644 Sources/Rules/BlankLinesBetweenChainedFunctions.swift create mode 100644 Sources/Rules/BlankLinesBetweenImports.swift create mode 100644 Sources/Rules/BlankLinesBetweenScopes.swift create mode 100644 Sources/Rules/BlockComments.swift create mode 100644 Sources/Rules/Braces.swift create mode 100644 Sources/Rules/ConditionalAssignment.swift create mode 100644 Sources/Rules/ConsecutiveBlankLines.swift create mode 100644 Sources/Rules/ConsecutiveSpaces.swift create mode 100644 Sources/Rules/ConsistentSwitchCaseSpacing.swift create mode 100644 Sources/Rules/DocComments.swift create mode 100644 Sources/Rules/DocCommentsBeforeAttributes.swift create mode 100644 Sources/Rules/DuplicateImports.swift create mode 100644 Sources/Rules/ElseOnSameLine.swift create mode 100644 Sources/Rules/EmptyBraces.swift create mode 100644 Sources/Rules/EnumNamespaces.swift create mode 100644 Sources/Rules/ExtensionAccessControl.swift create mode 100644 Sources/Rules/FileHeader.swift create mode 100644 Sources/Rules/GenericExtensions.swift create mode 100644 Sources/Rules/HeaderFileName.swift create mode 100644 Sources/Rules/HoistAwait.swift create mode 100644 Sources/Rules/HoistPatternLet.swift create mode 100644 Sources/Rules/HoistTry.swift create mode 100644 Sources/Rules/Indent.swift create mode 100644 Sources/Rules/InitCoderUnavailable.swift create mode 100644 Sources/Rules/IsEmpty.swift create mode 100644 Sources/Rules/LeadingDelimiters.swift create mode 100644 Sources/Rules/LinebreakAtEndOfFile.swift create mode 100644 Sources/Rules/Linebreaks.swift create mode 100644 Sources/Rules/MarkTypes.swift create mode 100644 Sources/Rules/ModifierOrder.swift create mode 100644 Sources/Rules/NoExplicitOwnership.swift create mode 100644 Sources/Rules/NumberFormatting.swift create mode 100644 Sources/Rules/OpaqueGenericParameters.swift create mode 100644 Sources/Rules/OrganizeDeclarations.swift create mode 100644 Sources/Rules/PreferForLoop.swift create mode 100644 Sources/Rules/PreferKeyPath.swift create mode 100644 Sources/Rules/PropertyType.swift create mode 100644 Sources/Rules/RedundantBackticks.swift create mode 100644 Sources/Rules/RedundantBreak.swift create mode 100644 Sources/Rules/RedundantClosure.swift create mode 100644 Sources/Rules/RedundantExtensionACL.swift create mode 100644 Sources/Rules/RedundantFileprivate.swift create mode 100644 Sources/Rules/RedundantGet.swift create mode 100644 Sources/Rules/RedundantInit.swift create mode 100644 Sources/Rules/RedundantInternal.swift create mode 100644 Sources/Rules/RedundantLet.swift create mode 100644 Sources/Rules/RedundantLetError.swift create mode 100644 Sources/Rules/RedundantNilInit.swift create mode 100644 Sources/Rules/RedundantObjc.swift create mode 100644 Sources/Rules/RedundantOptionalBinding.swift create mode 100644 Sources/Rules/RedundantParens.swift create mode 100644 Sources/Rules/RedundantPattern.swift create mode 100644 Sources/Rules/RedundantProperty.swift create mode 100644 Sources/Rules/RedundantRawValues.swift create mode 100644 Sources/Rules/RedundantReturn.swift create mode 100644 Sources/Rules/RedundantSelf.swift create mode 100644 Sources/Rules/RedundantStaticSelf.swift create mode 100644 Sources/Rules/RedundantType.swift create mode 100644 Sources/Rules/RedundantTypedThrows.swift create mode 100644 Sources/Rules/RedundantVoidReturnType.swift create mode 100644 Sources/Rules/Semicolons.swift create mode 100644 Sources/Rules/SortDeclarations.swift create mode 100644 Sources/Rules/SortImports.swift create mode 100644 Sources/Rules/SortSwitchCases.swift create mode 100644 Sources/Rules/SortTypealiases.swift create mode 100644 Sources/Rules/SortedImports.swift create mode 100644 Sources/Rules/SortedSwitchCases.swift create mode 100644 Sources/Rules/SpaceAroundBraces.swift create mode 100644 Sources/Rules/SpaceAroundBrackets.swift create mode 100644 Sources/Rules/SpaceAroundComments.swift create mode 100644 Sources/Rules/SpaceAroundGenerics.swift create mode 100644 Sources/Rules/SpaceAroundOperators.swift create mode 100644 Sources/Rules/SpaceAroundParens.swift create mode 100644 Sources/Rules/SpaceInsideBraces.swift create mode 100644 Sources/Rules/SpaceInsideBrackets.swift create mode 100644 Sources/Rules/SpaceInsideComments.swift create mode 100644 Sources/Rules/SpaceInsideGenerics.swift create mode 100644 Sources/Rules/SpaceInsideParens.swift create mode 100644 Sources/Rules/Specifiers.swift create mode 100644 Sources/Rules/StrongOutlets.swift create mode 100644 Sources/Rules/StrongifiedSelf.swift create mode 100644 Sources/Rules/Todos.swift create mode 100644 Sources/Rules/TrailingClosures.swift create mode 100644 Sources/Rules/TrailingCommas.swift create mode 100644 Sources/Rules/TrailingSpace.swift create mode 100644 Sources/Rules/TypeSugar.swift create mode 100644 Sources/Rules/UnusedArguments.swift create mode 100644 Sources/Rules/UnusedPrivateDeclaration.swift create mode 100644 Sources/Rules/Void.swift create mode 100644 Sources/Rules/Wrap.swift create mode 100644 Sources/Rules/WrapArguments.swift create mode 100644 Sources/Rules/WrapAttributes.swift create mode 100644 Sources/Rules/WrapConditionalBodies.swift create mode 100644 Sources/Rules/WrapEnumCases.swift create mode 100644 Sources/Rules/WrapLoopBodies.swift create mode 100644 Sources/Rules/WrapMultilineConditionalAssignment.swift create mode 100644 Sources/Rules/WrapMultilineStatementBraces.swift create mode 100644 Sources/Rules/WrapSingleLineComments.swift create mode 100644 Sources/Rules/WrapSwitchCases.swift create mode 100644 Sources/Rules/YodaConditions.swift create mode 100644 Tests/Rules/AcronymsTests.swift create mode 100644 Tests/Rules/AndOperatorTests.swift create mode 100644 Tests/Rules/AnyObjectProtocolTests.swift create mode 100644 Tests/Rules/ApplicationMainTests.swift create mode 100644 Tests/Rules/AssertionFailuresTests.swift create mode 100644 Tests/Rules/BlankLineAfterImportsTests.swift create mode 100644 Tests/Rules/BlankLineAfterSwitchCaseTests.swift create mode 100644 Tests/Rules/BlankLinesAroundMarkTests.swift create mode 100644 Tests/Rules/BlankLinesAtEndOfScopeTests.swift create mode 100644 Tests/Rules/BlankLinesAtStartOfScopeTests.swift create mode 100644 Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift create mode 100644 Tests/Rules/BlankLinesBetweenImportsTests.swift create mode 100644 Tests/Rules/BlankLinesBetweenScopesTests.swift create mode 100644 Tests/Rules/BlockCommentsTests.swift rename Tests/{RulesTests+Braces.swift => Rules/BracesTests.swift} (73%) create mode 100644 Tests/Rules/ConditionalAssignmentTests.swift create mode 100644 Tests/Rules/ConsecutiveBlankLinesTests.swift create mode 100644 Tests/Rules/ConsecutiveSpacesTests.swift create mode 100644 Tests/Rules/ConsistentSwitchCaseSpacingTests.swift create mode 100644 Tests/Rules/DocCommentsBeforeAttributesTests.swift create mode 100644 Tests/Rules/DocCommentsTests.swift create mode 100644 Tests/Rules/DuplicateImportsTests.swift create mode 100644 Tests/Rules/ElseOnSameLineTests.swift create mode 100644 Tests/Rules/EmptyBracesTests.swift create mode 100644 Tests/Rules/EnumNamespacesTests.swift create mode 100644 Tests/Rules/ExtensionAccessControlTests.swift create mode 100644 Tests/Rules/FileHeaderTests.swift create mode 100644 Tests/Rules/GenericExtensionsTests.swift create mode 100644 Tests/Rules/HeaderFileNameTests.swift create mode 100644 Tests/Rules/HoistAwaitTests.swift create mode 100644 Tests/Rules/HoistPatternLetTests.swift create mode 100644 Tests/Rules/HoistTryTests.swift rename Tests/{RulesTests+Indentation.swift => Rules/IndentTests.swift} (77%) create mode 100644 Tests/Rules/InitCoderUnavailableTests.swift create mode 100644 Tests/Rules/IsEmptyTests.swift create mode 100644 Tests/Rules/LeadingDelimitersTests.swift create mode 100644 Tests/Rules/LinebreakAtEndOfFileTests.swift create mode 100644 Tests/Rules/LinebreaksTests.swift create mode 100644 Tests/Rules/MarkTypesTests.swift create mode 100644 Tests/Rules/ModifierOrderTests.swift create mode 100644 Tests/Rules/NoExplicitOwnershipTests.swift create mode 100644 Tests/Rules/NumberFormattingTests.swift create mode 100644 Tests/Rules/OpaqueGenericParametersTests.swift create mode 100644 Tests/Rules/OrganizeDeclarationsTests.swift create mode 100644 Tests/Rules/PreferForLoopTests.swift create mode 100644 Tests/Rules/PreferKeyPathTests.swift create mode 100644 Tests/Rules/PropertyTypeTests.swift create mode 100644 Tests/Rules/RedundantBackticksTests.swift create mode 100644 Tests/Rules/RedundantBreakTests.swift create mode 100644 Tests/Rules/RedundantClosureTests.swift create mode 100644 Tests/Rules/RedundantExtensionACLTests.swift create mode 100644 Tests/Rules/RedundantFileprivateTests.swift create mode 100644 Tests/Rules/RedundantGetTests.swift create mode 100644 Tests/Rules/RedundantInitTests.swift create mode 100644 Tests/Rules/RedundantInternalTests.swift create mode 100644 Tests/Rules/RedundantLetErrorTests.swift create mode 100644 Tests/Rules/RedundantLetTests.swift create mode 100644 Tests/Rules/RedundantNilInitTests.swift create mode 100644 Tests/Rules/RedundantObjcTests.swift create mode 100644 Tests/Rules/RedundantOptionalBindingTests.swift rename Tests/{RulesTests+Parens.swift => Rules/RedundantParensTests.swift} (62%) create mode 100644 Tests/Rules/RedundantPatternTests.swift create mode 100644 Tests/Rules/RedundantPropertyTests.swift create mode 100644 Tests/Rules/RedundantRawValuesTests.swift create mode 100644 Tests/Rules/RedundantReturnTests.swift create mode 100644 Tests/Rules/RedundantSelfTests.swift create mode 100644 Tests/Rules/RedundantStaticSelfTests.swift create mode 100644 Tests/Rules/RedundantTypeTests.swift create mode 100644 Tests/Rules/RedundantTypedThrowsTests.swift create mode 100644 Tests/Rules/RedundantVoidReturnTypeTests.swift create mode 100644 Tests/Rules/SemicolonsTests.swift create mode 100644 Tests/Rules/SortDeclarationsTests.swift create mode 100644 Tests/Rules/SortImportsTests.swift create mode 100644 Tests/Rules/SortSwitchCasesTests.swift create mode 100644 Tests/Rules/SortTypealiasesTests.swift create mode 100644 Tests/Rules/SpaceAroundBracesTests.swift create mode 100644 Tests/Rules/SpaceAroundBracketsTests.swift create mode 100644 Tests/Rules/SpaceAroundCommentsTests.swift create mode 100644 Tests/Rules/SpaceAroundGenericsTests.swift create mode 100644 Tests/Rules/SpaceAroundOperatorsTests.swift create mode 100644 Tests/Rules/SpaceAroundParensTests.swift create mode 100644 Tests/Rules/SpaceInsideBracesTests.swift create mode 100644 Tests/Rules/SpaceInsideBracketsTests.swift create mode 100644 Tests/Rules/SpaceInsideCommentsTests.swift create mode 100644 Tests/Rules/SpaceInsideGenericsTests.swift create mode 100644 Tests/Rules/SpaceInsideParensTests.swift create mode 100644 Tests/Rules/StrongOutletsTests.swift create mode 100644 Tests/Rules/StrongifiedSelfTests.swift create mode 100644 Tests/Rules/TodosTests.swift create mode 100644 Tests/Rules/TrailingClosuresTests.swift create mode 100644 Tests/Rules/TrailingCommasTests.swift create mode 100644 Tests/Rules/TrailingSpaceTests.swift create mode 100644 Tests/Rules/TypeSugarTests.swift create mode 100644 Tests/Rules/UnusedArgumentsTests.swift create mode 100644 Tests/Rules/UnusedPrivateDeclarationTests.swift create mode 100644 Tests/Rules/VoidTests.swift create mode 100644 Tests/Rules/WrapArgumentsTests.swift create mode 100644 Tests/Rules/WrapAttributesTests.swift create mode 100644 Tests/Rules/WrapConditionalBodiesTests.swift create mode 100644 Tests/Rules/WrapEnumCasesTests.swift create mode 100644 Tests/Rules/WrapLoopBodiesTests.swift create mode 100644 Tests/Rules/WrapMultilineConditionalAssignmentTests.swift create mode 100644 Tests/Rules/WrapMultilineStatementBracesTests.swift create mode 100644 Tests/Rules/WrapSingleLineCommentsTests.swift create mode 100644 Tests/Rules/WrapSwitchCasesTests.swift create mode 100644 Tests/Rules/WrapTests.swift create mode 100644 Tests/Rules/YodaConditionsTests.swift delete mode 100644 Tests/RulesTests+General.swift delete mode 100644 Tests/RulesTests+Hoisting.swift delete mode 100644 Tests/RulesTests+Linebreaks.swift delete mode 100644 Tests/RulesTests+Organization.swift delete mode 100644 Tests/RulesTests+Redundancy.swift delete mode 100644 Tests/RulesTests+Spacing.swift delete mode 100644 Tests/RulesTests+Syntax.swift delete mode 100644 Tests/RulesTests+Wrapping.swift rename Tests/{RulesTests.swift => XCTestCase+testFormatting.swift} (89%) diff --git a/PerformanceTests/PerformanceTests.swift b/PerformanceTests/PerformanceTests.swift index 0eac1746..a5595a97 100644 --- a/PerformanceTests/PerformanceTests.swift +++ b/PerformanceTests/PerformanceTests.swift @@ -32,25 +32,19 @@ import SwiftFormat import XCTest -private let sourceDirectory = URL(fileURLWithPath: #file) +private let rulesDirectory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent() - .appendingPathComponent("Sources") - -private let rulesFile = "Rules.swift" + .appendingPathComponent("Sources/Rules") class PerformanceTests: XCTestCase { static let files: [String] = { var files = [String]() -// _ = enumerateFiles(withInputURL: projectDirectory) { url, _, _ in -// { -// if let source = try? String(contentsOf: url) { -// files.append(source) -// } -// } -// } - let url = sourceDirectory.appendingPathComponent(rulesFile) - if let source = try? String(contentsOf: url) { - files.append(source) + _ = enumerateFiles(withInputURL: rulesDirectory) { url, _, _ in + { + if let source = try? String(contentsOf: url) { + files.append(source) + } + } } return files }() @@ -74,23 +68,6 @@ class PerformanceTests: XCTestCase { } } -// func testUncachedFormatting() { -// CLI.print = { _, _ in } -// measure { -// XCTAssertEqual(CLI.run(in: sourceDirectory.path, with: "\(rulesFile) --cache ignore --dryrun"), .ok) -// } -// } -// -// // Not possible to run in dry mode because it won't write to cache -// // TODO: find a better way to test this -// func testCachedFormatting() { -// CLI.print = { _, _ in } -// _ = CLI.run(in: sourceDirectory.path, with: rulesFile) // warm the cache -// measure { -// XCTAssertEqual(CLI.run(in: sourceDirectory.path, with: "\(rulesFile) --dryrun"), .ok) -// } -// } - func testWorstCaseFormatting() { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } @@ -137,7 +114,7 @@ class PerformanceTests: XCTestCase { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } measure { - _ = tokens.map { try! format($0, rules: [FormatRules.indent]) } + _ = tokens.map { try! format($0, rules: [.indent]) } } } @@ -146,7 +123,7 @@ class PerformanceTests: XCTestCase { let tokens = files.map { tokenize($0) } let options = FormatOptions(indent: "\t", allmanBraces: true) measure { - _ = tokens.map { try! format($0, rules: [FormatRules.indent], options: options) } + _ = tokens.map { try! format($0, rules: [.indent], options: options) } } } @@ -154,7 +131,7 @@ class PerformanceTests: XCTestCase { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } measure { - _ = tokens.map { try! format($0, rules: [FormatRules.redundantSelf]) } + _ = tokens.map { try! format($0, rules: [.redundantSelf]) } } } @@ -163,7 +140,7 @@ class PerformanceTests: XCTestCase { let tokens = files.map { tokenize($0) } let options = FormatOptions(explicitSelf: .insert) measure { - _ = tokens.map { try! format($0, rules: [FormatRules.redundantSelf], options: options) } + _ = tokens.map { try! format($0, rules: [.redundantSelf], options: options) } } } @@ -171,7 +148,7 @@ class PerformanceTests: XCTestCase { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } measure { - _ = tokens.map { try! format($0, rules: [FormatRules.numberFormatting]) } + _ = tokens.map { try! format($0, rules: [.numberFormatting]) } } } @@ -187,7 +164,7 @@ class PerformanceTests: XCTestCase { hexGrouping: .group(1, 1) ) measure { - _ = tokens.map { try! format($0, rules: [FormatRules.numberFormatting], options: options) } + _ = tokens.map { try! format($0, rules: [.numberFormatting], options: options) } } } } diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 69f3eab1..6769c25d 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -15,7 +15,7 @@ import Foundation /// /// Forms a tree of declaratons, since `type` declarations have a body /// that contains child declarations. -enum Declaration: Equatable { +enum Declaration: Hashable { /// A type-like declaration with body of additional declarations (`class`, `struct`, etc) indirect case type( kind: String, diff --git a/Sources/FormatRule.swift b/Sources/FormatRule.swift new file mode 100644 index 00000000..5965a09e --- /dev/null +++ b/Sources/FormatRule.swift @@ -0,0 +1,185 @@ +// +// FormatRule.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/08/2016. +// Copyright 2016 Nick Lockwood +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/nicklockwood/SwiftFormat +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +public final class FormatRule: Equatable, Comparable, CustomStringConvertible { + private let fn: (Formatter) -> Void + fileprivate(set) var name = "[unnamed rule]" + fileprivate(set) var index = 0 + let help: String + let runOnceOnly: Bool + let disabledByDefault: Bool + let orderAfter: [FormatRule] + let options: [String] + let sharedOptions: [String] + let deprecationMessage: String? + + /// Null rule, used for testing + static let none: FormatRule = .init(help: "") { _ in } + + var isDeprecated: Bool { + deprecationMessage != nil + } + + public var description: String { + name + } + + init(help: String, + deprecationMessage: String? = nil, + runOnceOnly: Bool = false, + disabledByDefault: Bool = false, + orderAfter: [FormatRule] = [], + options: [String] = [], + sharedOptions: [String] = [], + _ fn: @escaping (Formatter) -> Void) + { + self.fn = fn + self.help = help + self.runOnceOnly = runOnceOnly + self.disabledByDefault = disabledByDefault || deprecationMessage != nil + self.orderAfter = orderAfter + self.options = options + self.sharedOptions = sharedOptions + self.deprecationMessage = deprecationMessage + } + + public func apply(with formatter: Formatter) { + formatter.currentRule = self + fn(formatter) + formatter.currentRule = nil + } + + public static func == (lhs: FormatRule, rhs: FormatRule) -> Bool { + lhs === rhs + } + + public static func < (lhs: FormatRule, rhs: FormatRule) -> Bool { + lhs.index < rhs.index + } +} + +public let FormatRules = _FormatRules() + +private let rulesByName: [String: FormatRule] = { + var rules = [String: FormatRule]() + for (name, rule) in ruleRegistry { + rule.name = name + rules[name] = rule + } + for rule in rules.values { + assert(rule.name != "[unnamed rule]") + } + let values = rules.values.sorted(by: { $0.name < $1.name }) + for (index, value) in values.enumerated() { + value.index = index * 10 + } + var changedOrder = true + while changedOrder { + changedOrder = false + for value in values { + for rule in value.orderAfter { + if rule.index >= value.index { + value.index = rule.index + 1 + changedOrder = true + } + } + } + } + return rules +}() + +private func allRules(except rules: [String]) -> [FormatRule] { + precondition(!rules.contains(where: { rulesByName[$0] == nil })) + return Array(rulesByName.keys.sorted().compactMap { + rules.contains($0) ? nil : rulesByName[$0] + }) +} + +private let _allRules = allRules(except: []) +private let _deprecatedRules = _allRules.filter { $0.isDeprecated }.map { $0.name } +private let _disabledByDefault = _allRules.filter { $0.disabledByDefault }.map { $0.name } +private let _defaultRules = allRules(except: _disabledByDefault) + +public extension _FormatRules { + /// A Dictionary of rules by name + var byName: [String: FormatRule] { rulesByName } + + /// All rules + var all: [FormatRule] { _allRules } + + /// Default active rules + var `default`: [FormatRule] { _defaultRules } + + /// Rules that are disabled by default + var disabledByDefault: [String] { _disabledByDefault } + + /// Rules that are deprecated + var deprecated: [String] { _deprecatedRules } + + /// Just the specified rules + func named(_ names: [String]) -> [FormatRule] { + Array(names.sorted().compactMap { rulesByName[$0] }) + } + + /// All rules except those specified + func all(except rules: [String]) -> [FormatRule] { + allRules(except: rules) + } +} + +extension _FormatRules { + /// Get all format options used by a given set of rules + func optionsForRules(_ rules: [FormatRule]) -> [String] { + var options = Set() + for rule in rules { + options.formUnion(rule.options + rule.sharedOptions) + } + return options.sorted() + } + + /// Get shared-only options for a given set of rules + func sharedOptionsForRules(_ rules: [FormatRule]) -> [String] { + var options = Set() + var sharedOptions = Set() + for rule in rules { + options.formUnion(rule.options) + sharedOptions.formUnion(rule.sharedOptions) + } + sharedOptions.subtract(options) + return sharedOptions.sorted() + } +} + +public struct _FormatRules { + fileprivate init() {} +} diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 4b095650..91080445 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -675,7 +675,7 @@ extension Formatter { } } - if currentRule == FormatRules.wrap { + if currentRule == .wrap { let nextWrapIndex = indexOfNextWrap() ?? endOfLine(at: i) if nextWrapIndex > lastIndex, maxWidth < lineLength(upTo: nextWrapIndex), diff --git a/Sources/RuleRegistry.generated.swift b/Sources/RuleRegistry.generated.swift new file mode 100644 index 00000000..da74a110 --- /dev/null +++ b/Sources/RuleRegistry.generated.swift @@ -0,0 +1,122 @@ +// +// RuleRegistry.generated.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/27/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +/// All of the rules defined in the Rules directory. +/// **Generated automatically when running tests. Do not modify.** +let ruleRegistry: [String: FormatRule] = [ + "acronyms": .acronyms, + "andOperator": .andOperator, + "anyObjectProtocol": .anyObjectProtocol, + "applicationMain": .applicationMain, + "assertionFailures": .assertionFailures, + "blankLineAfterImports": .blankLineAfterImports, + "blankLineAfterSwitchCase": .blankLineAfterSwitchCase, + "blankLinesAroundMark": .blankLinesAroundMark, + "blankLinesAtEndOfScope": .blankLinesAtEndOfScope, + "blankLinesAtStartOfScope": .blankLinesAtStartOfScope, + "blankLinesBetweenChainedFunctions": .blankLinesBetweenChainedFunctions, + "blankLinesBetweenImports": .blankLinesBetweenImports, + "blankLinesBetweenScopes": .blankLinesBetweenScopes, + "blockComments": .blockComments, + "braces": .braces, + "conditionalAssignment": .conditionalAssignment, + "consecutiveBlankLines": .consecutiveBlankLines, + "consecutiveSpaces": .consecutiveSpaces, + "consistentSwitchCaseSpacing": .consistentSwitchCaseSpacing, + "docComments": .docComments, + "docCommentsBeforeAttributes": .docCommentsBeforeAttributes, + "duplicateImports": .duplicateImports, + "elseOnSameLine": .elseOnSameLine, + "emptyBraces": .emptyBraces, + "enumNamespaces": .enumNamespaces, + "extensionAccessControl": .extensionAccessControl, + "fileHeader": .fileHeader, + "genericExtensions": .genericExtensions, + "headerFileName": .headerFileName, + "hoistAwait": .hoistAwait, + "hoistPatternLet": .hoistPatternLet, + "hoistTry": .hoistTry, + "indent": .indent, + "initCoderUnavailable": .initCoderUnavailable, + "isEmpty": .isEmpty, + "leadingDelimiters": .leadingDelimiters, + "linebreakAtEndOfFile": .linebreakAtEndOfFile, + "linebreaks": .linebreaks, + "markTypes": .markTypes, + "modifierOrder": .modifierOrder, + "noExplicitOwnership": .noExplicitOwnership, + "numberFormatting": .numberFormatting, + "opaqueGenericParameters": .opaqueGenericParameters, + "organizeDeclarations": .organizeDeclarations, + "preferForLoop": .preferForLoop, + "preferKeyPath": .preferKeyPath, + "propertyType": .propertyType, + "redundantBackticks": .redundantBackticks, + "redundantBreak": .redundantBreak, + "redundantClosure": .redundantClosure, + "redundantExtensionACL": .redundantExtensionACL, + "redundantFileprivate": .redundantFileprivate, + "redundantGet": .redundantGet, + "redundantInit": .redundantInit, + "redundantInternal": .redundantInternal, + "redundantLet": .redundantLet, + "redundantLetError": .redundantLetError, + "redundantNilInit": .redundantNilInit, + "redundantObjc": .redundantObjc, + "redundantOptionalBinding": .redundantOptionalBinding, + "redundantParens": .redundantParens, + "redundantPattern": .redundantPattern, + "redundantProperty": .redundantProperty, + "redundantRawValues": .redundantRawValues, + "redundantReturn": .redundantReturn, + "redundantSelf": .redundantSelf, + "redundantStaticSelf": .redundantStaticSelf, + "redundantType": .redundantType, + "redundantTypedThrows": .redundantTypedThrows, + "redundantVoidReturnType": .redundantVoidReturnType, + "semicolons": .semicolons, + "sortDeclarations": .sortDeclarations, + "sortImports": .sortImports, + "sortSwitchCases": .sortSwitchCases, + "sortTypealiases": .sortTypealiases, + "sortedImports": .sortedImports, + "sortedSwitchCases": .sortedSwitchCases, + "spaceAroundBraces": .spaceAroundBraces, + "spaceAroundBrackets": .spaceAroundBrackets, + "spaceAroundComments": .spaceAroundComments, + "spaceAroundGenerics": .spaceAroundGenerics, + "spaceAroundOperators": .spaceAroundOperators, + "spaceAroundParens": .spaceAroundParens, + "spaceInsideBraces": .spaceInsideBraces, + "spaceInsideBrackets": .spaceInsideBrackets, + "spaceInsideComments": .spaceInsideComments, + "spaceInsideGenerics": .spaceInsideGenerics, + "spaceInsideParens": .spaceInsideParens, + "specifiers": .specifiers, + "strongOutlets": .strongOutlets, + "strongifiedSelf": .strongifiedSelf, + "todos": .todos, + "trailingClosures": .trailingClosures, + "trailingCommas": .trailingCommas, + "trailingSpace": .trailingSpace, + "typeSugar": .typeSugar, + "unusedArguments": .unusedArguments, + "unusedPrivateDeclaration": .unusedPrivateDeclaration, + "void": .void, + "wrap": .wrap, + "wrapArguments": .wrapArguments, + "wrapAttributes": .wrapAttributes, + "wrapConditionalBodies": .wrapConditionalBodies, + "wrapEnumCases": .wrapEnumCases, + "wrapLoopBodies": .wrapLoopBodies, + "wrapMultilineConditionalAssignment": .wrapMultilineConditionalAssignment, + "wrapMultilineStatementBraces": .wrapMultilineStatementBraces, + "wrapSingleLineComments": .wrapSingleLineComments, + "wrapSwitchCases": .wrapSwitchCases, + "yodaConditions": .yodaConditions, +] diff --git a/Sources/Rules.swift b/Sources/Rules.swift deleted file mode 100644 index 743f04e8..00000000 --- a/Sources/Rules.swift +++ /dev/null @@ -1,8427 +0,0 @@ -// -// Rules.swift -// SwiftFormat -// -// Created by Nick Lockwood on 12/08/2016. -// Copyright 2016 Nick Lockwood -// -// Distributed under the permissive MIT license -// Get the latest version from here: -// -// https://github.com/nicklockwood/SwiftFormat -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -public final class FormatRule: Equatable, Comparable, CustomStringConvertible { - private let fn: (Formatter) -> Void - fileprivate(set) var name = "[unnamed rule]" - fileprivate(set) var index = 0 - let help: String - let runOnceOnly: Bool - let disabledByDefault: Bool - let orderAfter: [String] - let options: [String] - let sharedOptions: [String] - let deprecationMessage: String? - - /// Null rule, used for testing - static let none: FormatRule = .init(help: "") { _ in } - - var isDeprecated: Bool { - deprecationMessage != nil - } - - public var description: String { - name - } - - fileprivate init(help: String, - deprecationMessage: String? = nil, - runOnceOnly: Bool = false, - disabledByDefault: Bool = false, - orderAfter: [String] = [], - options: [String] = [], - sharedOptions: [String] = [], - _ fn: @escaping (Formatter) -> Void) - { - self.fn = fn - self.help = help - self.runOnceOnly = runOnceOnly - self.disabledByDefault = disabledByDefault || deprecationMessage != nil - self.orderAfter = orderAfter - self.options = options - self.sharedOptions = sharedOptions - self.deprecationMessage = deprecationMessage - } - - public func apply(with formatter: Formatter) { - formatter.currentRule = self - fn(formatter) - formatter.currentRule = nil - } - - public static func == (lhs: FormatRule, rhs: FormatRule) -> Bool { - lhs === rhs - } - - public static func < (lhs: FormatRule, rhs: FormatRule) -> Bool { - lhs.index < rhs.index - } -} - -public let FormatRules = _FormatRules() - -private let rulesByName: [String: FormatRule] = { - var rules = [String: FormatRule]() - for (label, value) in Mirror(reflecting: FormatRules).children { - guard let name = label, let rule = value as? FormatRule else { - continue - } - rule.name = name - rules[name] = rule - } - let values = rules.values.sorted(by: { $0.name < $1.name }) - for (index, value) in values.enumerated() { - value.index = index * 10 - } - var changedOrder = true - while changedOrder { - changedOrder = false - for value in values { - for name in value.orderAfter { - guard let rule = rules[name] else { - preconditionFailure(name) - } - if rule.index >= value.index { - value.index = rule.index + 1 - changedOrder = true - } - } - } - } - return rules -}() - -private func allRules(except rules: [String]) -> [FormatRule] { - precondition(!rules.contains(where: { rulesByName[$0] == nil })) - return Array(rulesByName.keys.sorted().compactMap { - rules.contains($0) ? nil : rulesByName[$0] - }) -} - -private let _allRules = allRules(except: []) -private let _deprecatedRules = _allRules.filter { $0.isDeprecated }.map { $0.name } -private let _disabledByDefault = _allRules.filter { $0.disabledByDefault }.map { $0.name } -private let _defaultRules = allRules(except: _disabledByDefault) - -public extension _FormatRules { - /// A Dictionary of rules by name - var byName: [String: FormatRule] { rulesByName } - - /// All rules - var all: [FormatRule] { _allRules } - - /// Default active rules - var `default`: [FormatRule] { _defaultRules } - - /// Rules that are disabled by default - var disabledByDefault: [String] { _disabledByDefault } - - /// Rules that are deprecated - var deprecated: [String] { _deprecatedRules } - - /// Just the specified rules - func named(_ names: [String]) -> [FormatRule] { - Array(names.sorted().compactMap { rulesByName[$0] }) - } - - /// All rules except those specified - func all(except rules: [String]) -> [FormatRule] { - allRules(except: rules) - } -} - -extension _FormatRules { - /// Get all format options used by a given set of rules - func optionsForRules(_ rules: [FormatRule]) -> [String] { - var options = Set() - for rule in rules { - options.formUnion(rule.options + rule.sharedOptions) - } - return options.sorted() - } - - /// Get shared-only options for a given set of rules - func sharedOptionsForRules(_ rules: [FormatRule]) -> [String] { - var options = Set() - var sharedOptions = Set() - for rule in rules { - options.formUnion(rule.options) - sharedOptions.formUnion(rule.sharedOptions) - } - sharedOptions.subtract(options) - return sharedOptions.sorted() - } -} - -public struct _FormatRules { - fileprivate init() {} - - /// Replace the obsolete `@UIApplicationMain` and `@NSApplicationMain` - /// attributes with `@main` in Swift 5.3 and above, per SE-0383 - public let applicationMain = FormatRule( - help: """ - Replace obsolete @UIApplicationMain and @NSApplicationMain attributes - with @main for Swift 5.3 and above. - """ - ) { formatter in - guard formatter.options.swiftVersion >= "5.3" else { - return - } - formatter.forEachToken(where: { - [ - .keyword("@UIApplicationMain"), - .keyword("@NSApplicationMain"), - ].contains($0) - }) { i, _ in - formatter.replaceToken(at: i, with: .keyword("@main")) - } - } - - /// Implement the following rules with respect to the spacing around parens: - /// * There is no space between an opening paren and the preceding identifier, - /// unless the identifier is one of the specified keywords - /// * There is no space between an opening paren and the preceding closing brace - /// * There is no space between an opening paren and the preceding closing square bracket - /// * There is space between a closing paren and following identifier - /// * There is space between a closing paren and following opening brace - /// * There is no space between a closing paren and following opening square bracket - public let spaceAroundParens = FormatRule( - help: "Add or remove space around parentheses." - ) { formatter in - func spaceAfter(_ keywordOrAttribute: String, index: Int) -> Bool { - switch keywordOrAttribute { - case "@autoclosure": - if formatter.options.swiftVersion < "3", - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: index), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("escaping") - { - assert(formatter.tokens[nextIndex] == .startOfScope("(")) - return false - } - return true - case "@escaping", "@noescape", "@Sendable": - return true - case _ where keywordOrAttribute.hasPrefix("@"): - if let i = formatter.index(of: .startOfScope("("), after: index) { - return formatter.isParameterList(at: i) - } - return false - case "private", "fileprivate", "internal", - "init", "subscript", "throws": - return false - case "await": - return formatter.options.swiftVersion >= "5.5" || - formatter.options.swiftVersion == .undefined - default: - return keywordOrAttribute.first.map { !"@#".contains($0) } ?? true - } - } - - formatter.forEach(.startOfScope("(")) { i, _ in - let index = i - 1 - guard let prevToken = formatter.token(at: index) else { - return - } - switch prevToken { - case let .keyword(string) where spaceAfter(string, index: index): - fallthrough - case .endOfScope("]") where formatter.isInClosureArguments(at: index), - .endOfScope(")") where formatter.isAttribute(at: index), - .identifier("some") where formatter.isTypePosition(at: index), - .identifier("any") where formatter.isTypePosition(at: index), - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("isolated") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - formatter.insert(.space(" "), at: i) - case .space: - let index = i - 2 - guard let token = formatter.token(at: index) else { - return - } - switch token { - case .identifier("some") where formatter.isTypePosition(at: index), - .identifier("any") where formatter.isTypePosition(at: index), - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("isolated") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - break - case let .keyword(string) where !spaceAfter(string, index: index): - fallthrough - case .number, .identifier: - fallthrough - case .endOfScope("}"), .endOfScope(">"), - .endOfScope("]") where !formatter.isInClosureArguments(at: index), - .endOfScope(")") where !formatter.isAttribute(at: index): - formatter.removeToken(at: i - 1) - default: - break - } - default: - break - } - } - formatter.forEach(.endOfScope(")")) { i, _ in - guard let nextToken = formatter.token(at: i + 1) else { - return - } - switch nextToken { - case .identifier, .keyword, .startOfScope("{"): - formatter.insert(.space(" "), at: i + 1) - case .space where formatter.token(at: i + 2) == .startOfScope("["): - formatter.removeToken(at: i + 1) - default: - break - } - } - } - - /// Remove space immediately inside parens - public let spaceInsideParens = FormatRule( - help: "Remove space inside parentheses." - ) { formatter in - formatter.forEach(.startOfScope("(")) { i, _ in - if formatter.token(at: i + 1)?.isSpace == true, - formatter.token(at: i + 2)?.isComment == false - { - formatter.removeToken(at: i + 1) - } - } - formatter.forEach(.endOfScope(")")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isCommentOrLinebreak == false - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Implement the following rules with respect to the spacing around square brackets: - /// * There is no space between an opening bracket and the preceding identifier, - /// unless the identifier is one of the specified keywords - /// * There is no space between an opening bracket and the preceding closing brace - /// * There is no space between an opening bracket and the preceding closing square bracket - /// * There is space between a closing bracket and following identifier - /// * There is space between a closing bracket and following opening brace - public let spaceAroundBrackets = FormatRule( - help: "Add or remove space around square brackets." - ) { formatter in - formatter.forEach(.startOfScope("[")) { i, _ in - let index = i - 1 - guard let prevToken = formatter.token(at: index) else { - return - } - switch prevToken { - case .keyword, - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - formatter.insert(.space(" "), at: i) - case .space: - let index = i - 2 - if let token = formatter.token(at: index) { - switch token { - case .identifier("as"), .identifier("is"), // not treated as keywords inside macro - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - break - case .identifier, .number, .endOfScope("]"), .endOfScope("}"), .endOfScope(")"): - formatter.removeToken(at: i - 1) - default: - break - } - } - default: - break - } - } - formatter.forEach(.endOfScope("]")) { i, _ in - guard let nextToken = formatter.token(at: i + 1) else { - return - } - switch nextToken { - case .identifier, .keyword, .startOfScope("{"), - .startOfScope("(") where formatter.isInClosureArguments(at: i): - formatter.insert(.space(" "), at: i + 1) - case .space: - switch formatter.token(at: i + 2) { - case .startOfScope("(")? where !formatter.isInClosureArguments(at: i + 2), .startOfScope("[")?: - formatter.removeToken(at: i + 1) - default: - break - } - default: - break - } - } - } - - /// Remove space immediately inside square brackets - public let spaceInsideBrackets = FormatRule( - help: "Remove space inside square brackets." - ) { formatter in - formatter.forEach(.startOfScope("[")) { i, _ in - if formatter.token(at: i + 1)?.isSpace == true, - formatter.token(at: i + 2)?.isComment == false - { - formatter.removeToken(at: i + 1) - } - } - formatter.forEach(.endOfScope("]")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isCommentOrLinebreak == false - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Ensure that there is space between an opening brace and the preceding - /// identifier, and between a closing brace and the following identifier. - public let spaceAroundBraces = FormatRule( - help: "Add or remove space around curly braces." - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - if let prevToken = formatter.token(at: i - 1) { - switch prevToken { - case .space, .linebreak, .operator(_, .prefix), .operator(_, .infix), - .startOfScope where !prevToken.isStringDelimiter: - break - default: - formatter.insert(.space(" "), at: i) - } - } - } - formatter.forEach(.endOfScope("}")) { i, _ in - if let nextToken = formatter.token(at: i + 1) { - switch nextToken { - case .identifier, .keyword: - formatter.insert(.space(" "), at: i + 1) - default: - break - } - } - } - } - - /// Ensure that there is space immediately inside braces - public let spaceInsideBraces = FormatRule( - help: "Add space inside curly braces." - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - if let nextToken = formatter.token(at: i + 1) { - if !nextToken.isSpaceOrLinebreak, - ![.endOfScope("}"), .startOfScope("{")].contains(nextToken) - { - formatter.insert(.space(" "), at: i + 1) - } - } - } - formatter.forEach(.endOfScope("}")) { i, _ in - if let prevToken = formatter.token(at: i - 1) { - if !prevToken.isSpaceOrLinebreak, - ![.endOfScope("}"), .startOfScope("{")].contains(prevToken) - { - formatter.insert(.space(" "), at: i) - } - } - } - } - - /// Ensure there is no space between an opening chevron and the preceding identifier - public let spaceAroundGenerics = FormatRule( - help: "Remove space around angle brackets." - ) { formatter in - formatter.forEach(.startOfScope("<")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isIdentifierOrKeyword == true - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Remove space immediately inside chevrons - public let spaceInsideGenerics = FormatRule( - help: "Remove space inside angle brackets." - ) { formatter in - formatter.forEach(.startOfScope("<")) { i, _ in - if formatter.token(at: i + 1)?.isSpace == true { - formatter.removeToken(at: i + 1) - } - } - formatter.forEach(.endOfScope(">")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isLinebreak == false - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Implement the following rules with respect to the spacing around operators: - /// * Infix operators are separated from their operands by a space on either - /// side. Does not affect prefix/postfix operators, as required by syntax. - /// * Delimiters, such as commas and colons, are consistently followed by a - /// single space, unless it appears at the end of a line, and is not - /// preceded by a space, unless it appears at the beginning of a line. - public let spaceAroundOperators = FormatRule( - help: "Add or remove space around operators or delimiters.", - options: ["operatorfunc", "nospaceoperators", "ranges", "typedelimiter"] - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .operator(_, .none): - switch formatter.token(at: i + 1) { - case nil, .linebreak?, .endOfScope?, .operator?, .delimiter?, - .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: - break - case .space?: - switch formatter.next(.nonSpaceOrLinebreak, after: i) { - case nil, .linebreak?, .endOfScope?, .delimiter?, - .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: - formatter.removeToken(at: i + 1) - default: - break - } - default: - formatter.insert(.space(" "), at: i + 1) - } - case .operator("?", .postfix), .operator("!", .postfix): - if let prevToken = formatter.token(at: i - 1), - formatter.token(at: i + 1)?.isSpaceOrLinebreak == false, - [.keyword("as"), .keyword("try")].contains(prevToken) - { - formatter.insert(.space(" "), at: i + 1) - } - case .operator(".", _): - if formatter.token(at: i + 1)?.isSpace == true { - formatter.removeToken(at: i + 1) - } - guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { - formatter.removeTokens(in: 0 ..< i) - break - } - let spaceRequired: Bool - switch formatter.tokens[prevIndex] { - case .operator(_, .infix), .startOfScope: - return - case let token where token.isUnwrapOperator: - if let prevToken = formatter.last(.nonSpace, before: prevIndex), - [.keyword("as"), .keyword("try")].contains(prevToken) - { - spaceRequired = true - } else { - spaceRequired = false - } - case .operator(_, .prefix): - spaceRequired = false - case let token: - spaceRequired = !token.isAttribute && !token.isLvalue - } - if formatter.token(at: i - 1)?.isSpaceOrLinebreak == true { - if !spaceRequired { - formatter.removeToken(at: i - 1) - } - } else if spaceRequired { - formatter.insertSpace(" ", at: i) - } - case .operator("?", .infix): - break // Spacing around ternary ? is not optional - case let .operator(name, .infix) where formatter.options.noSpaceOperators.contains(name) || - (!formatter.options.spaceAroundRangeOperators && token.isRangeOperator): - if formatter.token(at: i + 1)?.isSpace == true, - formatter.token(at: i - 1)?.isSpace == true, - let nextToken = formatter.next(.nonSpace, after: i), - !nextToken.isCommentOrLinebreak, !nextToken.isOperator, - let prevToken = formatter.last(.nonSpace, before: i), - !prevToken.isCommentOrLinebreak, !prevToken.isOperator || prevToken.isUnwrapOperator - { - formatter.removeToken(at: i + 1) - formatter.removeToken(at: i - 1) - } - case .operator(_, .infix): - if formatter.token(at: i + 1)?.isSpaceOrLinebreak == false { - formatter.insert(.space(" "), at: i + 1) - } - if formatter.token(at: i - 1)?.isSpaceOrLinebreak == false { - formatter.insert(.space(" "), at: i) - } - case .operator(_, .prefix): - if let prevIndex = formatter.index(of: .nonSpace, before: i, if: { - [.startOfScope("["), .startOfScope("("), .startOfScope("<")].contains($0) - }) { - formatter.removeTokens(in: prevIndex + 1 ..< i) - } else if let prevToken = formatter.token(at: i - 1), - !prevToken.isSpaceOrLinebreak, !prevToken.isOperator - { - formatter.insert(.space(" "), at: i) - } - case .delimiter(":"): - // TODO: make this check more robust, and remove redundant space - if formatter.token(at: i + 1)?.isIdentifier == true, - formatter.token(at: i + 2) == .delimiter(":") - { - // It's a selector - break - } - fallthrough - case .operator(_, .postfix), .delimiter(","), .delimiter(";"), .startOfScope(":"): - switch formatter.token(at: i + 1) { - case nil, .space?, .linebreak?, .endOfScope?, .operator?, .delimiter?: - break - default: - // Ensure there is a space after the token - formatter.insert(.space(" "), at: i + 1) - } - - let spaceBeforeToken = formatter.token(at: i - 1)?.isSpace == true - && formatter.token(at: i - 2)?.isLinebreak == false - - if spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaceAfter { - // Remove space before the token - formatter.removeToken(at: i - 1) - } else if !spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaced { - formatter.insertSpace(" ", at: i) - } - default: - break - } - } - } - - /// Add space around comments, except at the start or end of a line - public let spaceAroundComments = FormatRule( - help: "Add space before and/or after comments." - ) { formatter in - formatter.forEach(.startOfScope("//")) { i, _ in - if let prevToken = formatter.token(at: i - 1), !prevToken.isSpaceOrLinebreak { - formatter.insert(.space(" "), at: i) - } - } - formatter.forEach(.endOfScope("*/")) { i, _ in - guard let startIndex = formatter.index(of: .startOfScope("/*"), before: i), - case let .commentBody(commentStart)? = formatter.next(.nonSpaceOrLinebreak, after: startIndex), - case let .commentBody(commentEnd)? = formatter.last(.nonSpaceOrLinebreak, before: i), - !commentStart.hasPrefix("@"), !commentEnd.hasSuffix("@") - else { - return - } - if let nextToken = formatter.token(at: i + 1) { - if !nextToken.isSpaceOrLinebreak { - if nextToken != .delimiter(",") { - formatter.insert(.space(" "), at: i + 1) - } - } else if formatter.next(.nonSpace, after: i + 1) == .delimiter(",") { - formatter.removeToken(at: i + 1) - } - } - if let prevToken = formatter.token(at: startIndex - 1), !prevToken.isSpaceOrLinebreak { - if case let .commentBody(text) = prevToken, text.last?.unicodeScalars.last?.isSpace == true { - return - } - formatter.insert(.space(" "), at: startIndex) - } - } - } - - /// Add space inside comments, taking care not to mangle headerdoc or - /// carefully preformatted comments, such as star boxes, etc. - public let spaceInsideComments = FormatRule( - help: "Add leading and/or trailing space inside comments." - ) { formatter in - formatter.forEach(.startOfScope("//")) { i, _ in - guard case let .commentBody(string)? = formatter.token(at: i + 1), - let first = string.first else { return } - if "/!:".contains(first) { - let nextIndex = string.index(after: string.startIndex) - if nextIndex < string.endIndex, case let next = string[nextIndex], !" \t/".contains(next) { - let string = String(string.first!) + " " + String(string.dropFirst()) - formatter.replaceToken(at: i + 1, with: .commentBody(string)) - } - } else if !" \t".contains(first), !string.hasPrefix("===") { // Special-case check for swift stdlib codebase - formatter.insert(.space(" "), at: i + 1) - } - } - formatter.forEach(.startOfScope("/*")) { i, _ in - guard case let .commentBody(string)? = formatter.token(at: i + 1), - !string.hasPrefix("---"), !string.hasPrefix("@"), !string.hasSuffix("---"), !string.hasSuffix("@") - else { - return - } - if let first = string.first, "*!:".contains(first) { - let nextIndex = string.index(after: string.startIndex) - if nextIndex < string.endIndex, case let next = string[nextIndex], - !" /t".contains(next), !string.hasPrefix("**"), !string.hasPrefix("*/") - { - let string = String(string.first!) + " " + String(string.dropFirst()) - formatter.replaceToken(at: i + 1, with: .commentBody(string)) - } - } else { - formatter.insert(.space(" "), at: i + 1) - } - if let i = formatter.index(of: .endOfScope("*/"), after: i), let prevToken = formatter.token(at: i - 1) { - if !prevToken.isSpaceOrLinebreak, !prevToken.string.hasSuffix("*"), - !prevToken.string.trimmingCharacters(in: .whitespaces).isEmpty - { - formatter.insert(.space(" "), at: i) - } - } - } - } - - /// Removes explicit type declarations from initialization declarations - public let redundantType = FormatRule( - help: "Remove redundant type from variable declarations.", - options: ["redundanttype"] - ) { formatter in - formatter.forEach(.operator("=", .infix)) { i, _ in - guard let keyword = formatter.lastSignificantKeyword(at: i), - ["var", "let"].contains(keyword) - else { - return - } - - let equalsIndex = i - guard let colonIndex = formatter.index(before: i, where: { - [.delimiter(":"), .operator("=", .infix)].contains($0) - }), formatter.tokens[colonIndex] == .delimiter(":"), - let typeEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) - else { return } - - // Compares whether or not two types are equivalent - func compare(typeStartingAfter j: Int, withTypeStartingAfter i: Int) - -> (matches: Bool, i: Int, j: Int, wasValue: Bool) - { - var i = i, j = j, wasValue = false - - while let typeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - typeIndex <= typeEndIndex, - let valueIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: j) - { - let typeToken = formatter.tokens[typeIndex] - let valueToken = formatter.tokens[valueIndex] - if !wasValue { - switch valueToken { - case _ where valueToken.isStringDelimiter, .number, - .identifier("true"), .identifier("false"): - if formatter.options.redundantType == .explicit { - // We never remove the value in this case, so exit early - return (false, i, j, wasValue) - } - wasValue = true - default: - break - } - } - guard typeToken == formatter.typeToken(forValueToken: valueToken) else { - return (false, i, j, wasValue) - } - // Avoid introducing "inferred to have type 'Void'" warning - if formatter.options.redundantType == .inferred, typeToken == .identifier("Void") || - typeToken == .endOfScope(")") && formatter.tokens[i] == .startOfScope("(") - { - return (false, i, j, wasValue) - } - i = typeIndex - j = valueIndex - if formatter.tokens[j].isStringDelimiter, let next = formatter.endOfScope(at: j) { - j = next - } - } - guard i == typeEndIndex else { - return (false, i, j, wasValue) - } - - // Check for ternary - if let endOfExpression = formatter.endOfExpression(at: j, upTo: [.operator("?", .infix)]), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfExpression) == .operator("?", .infix) - { - return (false, i, j, wasValue) - } - - return (true, i, j, wasValue) - } - - // The implementation of RedundantType uses inferred or explicit, - // potentially depending on the context. - let isInferred: Bool - let declarationKeywordIndex: Int? - switch formatter.options.redundantType { - case .inferred: - isInferred = true - declarationKeywordIndex = nil - case .explicit: - isInferred = false - declarationKeywordIndex = formatter.declarationIndexAndScope(at: equalsIndex).index - case .inferLocalsOnly: - let (index, scope) = formatter.declarationIndexAndScope(at: equalsIndex) - switch scope { - case .global, .type: - isInferred = false - declarationKeywordIndex = index - case .local: - isInferred = true - declarationKeywordIndex = nil - } - } - - // Explicit type can't be safely removed from @Model classes - // https://github.com/nicklockwood/SwiftFormat/issues/1649 - if !isInferred, - let declarationKeywordIndex = declarationKeywordIndex, - formatter.modifiersForDeclaration(at: declarationKeywordIndex, contains: "@Model") - { - return - } - - // Removes a type already processed by `compare(typeStartingAfter:withTypeStartingAfter:)` - func removeType(after indexBeforeStartOfType: Int, i: Int, j: Int, wasValue: Bool) { - if isInferred { - formatter.removeTokens(in: colonIndex ... typeEndIndex) - if formatter.tokens[colonIndex - 1].isSpace { - formatter.removeToken(at: colonIndex - 1) - } - } else if !wasValue, let valueStartIndex = formatter - .index(of: .nonSpaceOrCommentOrLinebreak, after: indexBeforeStartOfType), - !formatter.isConditionalStatement(at: i), - let endIndex = formatter.endOfExpression(at: j, upTo: []), - endIndex > j - { - let allowChains = formatter.options.swiftVersion >= "5.4" - if formatter.next(.nonSpaceOrComment, after: j) == .startOfScope("(") { - if allowChains || formatter.index( - of: .operator(".", .infix), - in: j ..< endIndex - ) == nil { - formatter.replaceTokens(in: valueStartIndex ... j, with: [ - .operator(".", .infix), .identifier("init"), - ]) - } - } else if let nextIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - after: j, - if: { $0 == .operator(".", .infix) } - ), allowChains || formatter.index( - of: .operator(".", .infix), - in: (nextIndex + 1) ..< endIndex - ) == nil { - formatter.removeTokens(in: valueStartIndex ... j) - } - } - } - - // In Swift 5.9+ (SE-0380) we need to handle if / switch expressions by checking each branch - if formatter.options.swiftVersion >= "5.9", - let tokenAfterEquals = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex), - let conditionalBranches = formatter.conditionalBranches(at: tokenAfterEquals), - formatter.allRecursiveConditionalBranches( - in: conditionalBranches, - satisfy: { branch in - compare(typeStartingAfter: branch.startOfBranch, withTypeStartingAfter: colonIndex).matches - } - ) - { - if isInferred { - formatter.removeTokens(in: colonIndex ... typeEndIndex) - if formatter.tokens[colonIndex - 1].isSpace { - formatter.removeToken(at: colonIndex - 1) - } - } else { - formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in - let (_, i, j, wasValue) = compare( - typeStartingAfter: branch.startOfBranch, - withTypeStartingAfter: colonIndex - ) - - removeType(after: branch.startOfBranch, i: i, j: j, wasValue: wasValue) - } - } - } - - // Otherwise this is just a simple assignment expression where the RHS is a single value - else { - let (matches, i, j, wasValue) = compare(typeStartingAfter: equalsIndex, withTypeStartingAfter: colonIndex) - if matches { - removeType(after: equalsIndex, i: i, j: j, wasValue: wasValue) - } - } - } - } - - /// Collapse all consecutive space characters to a single space, except at - /// the start of a line or inside a comment or string, as these have no semantic - /// meaning and lead to noise in commits. - public let consecutiveSpaces = FormatRule( - help: "Replace consecutive spaces with a single space." - ) { formatter in - formatter.forEach(.space) { i, token in - switch token { - case .space(""): - formatter.removeToken(at: i) - case .space(" "): - break - default: - guard let prevToken = formatter.token(at: i - 1), - let nextToken = formatter.token(at: i + 1) - else { - return - } - switch prevToken { - case .linebreak, .startOfScope("/*"), .startOfScope("//"), .commentBody: - return - case .endOfScope("*/") where nextToken == .startOfScope("/*") && - formatter.currentScope(at: i) == .startOfScope("/*"): - return - default: - break - } - switch nextToken { - case .linebreak, .endOfScope("*/"), .commentBody: - return - default: - formatter.replaceToken(at: i, with: .space(" ")) - } - } - } - } - - /// Converts types used for hosting only static members into enums to avoid instantiation. - public let enumNamespaces = FormatRule( - help: """ - Convert types used for hosting only static members into enums (an empty enum is - the canonical way to create a namespace in Swift as it can't be instantiated). - """, - options: ["enumnamespaces"] - ) { formatter in - func rangeHostsOnlyStaticMembersAtTopLevel(_ range: Range) -> Bool { - // exit for empty declarations - guard formatter.next(.nonSpaceOrCommentOrLinebreak, in: range) != nil else { - return false - } - - var j = range.startIndex - while j < range.endIndex, let token = formatter.token(at: j) { - if token == .startOfScope("{"), - let skip = formatter.index(of: .endOfScope("}"), after: j) - { - j = skip - continue - } - // exit if there's a explicit init - if token == .keyword("init") { - return false - } else if [.keyword("let"), - .keyword("var"), - .keyword("func")].contains(token), - !formatter.modifiersForDeclaration(at: j, contains: "static") - { - return false - } - j += 1 - } - return true - } - - func rangeContainsTypeInit(_ type: String, in range: Range) -> Bool { - for i in range { - guard case let .identifier(name) = formatter.tokens[i], - [type, "Self", "self"].contains(name) - else { - continue - } - if let nextIndex = formatter.index(of: .nonSpaceOrComment, after: i), - let nextToken = formatter.token(at: nextIndex), nextToken == .startOfScope("(") || - (nextToken == .operator(".", .infix) && [.identifier("init"), .identifier("self")] - .contains(formatter.next(.nonSpaceOrComment, after: nextIndex) ?? .space(""))) - { - return true - } - } - return false - } - - func rangeContainsSelfAssignment(_ range: Range) -> Bool { - for i in range { - guard case .identifier("self") = formatter.tokens[i] else { - continue - } - if let token = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - [.operator("=", .infix), .delimiter(":"), .startOfScope("(")].contains(token) - { - return true - } - } - return false - } - - formatter.forEachToken(where: { [.keyword("class"), .keyword("struct")].contains($0) }) { i, token in - if token == .keyword("class") { - guard let next = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i), - // exit if structs only - formatter.options.enumNamespaces != .structsOnly, - // exit if class is a type modifier - !(next.isKeywordOrAttribute || next.isModifierKeyword), - // exit for class as protocol conformance - formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .delimiter(":"), - // exit if not closed for extension - formatter.modifiersForDeclaration(at: i, contains: "final") - else { - return - } - } - guard let braceIndex = formatter.index(of: .startOfScope("{"), after: i), - // exit if import statement - formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import"), - // exit if has attribute(s) - !formatter.modifiersForDeclaration(at: i, contains: { $1.hasPrefix("@") }), - // exit if type is conforming any other types - !formatter.tokens[i ... braceIndex].contains(.delimiter(":")), - let endIndex = formatter.index(of: .endOfScope("}"), after: braceIndex), - case let .identifier(name)? = formatter.next(.identifier, after: i + 1) - else { - return - } - let range = braceIndex + 1 ..< endIndex - if rangeHostsOnlyStaticMembersAtTopLevel(range), - !rangeContainsTypeInit(name, in: range), !rangeContainsSelfAssignment(range) - { - formatter.replaceToken(at: i, with: .keyword("enum")) - - if let finalIndex = formatter.indexOfModifier("final", forDeclarationAt: i), - let nextIndex = formatter.index(of: .nonSpace, after: finalIndex) - { - formatter.removeTokens(in: finalIndex ..< nextIndex) - } - } - } - } - - /// Remove trailing space from the end of lines, as it has no semantic - /// meaning and leads to noise in commits. - public let trailingSpace = FormatRule( - help: "Remove trailing space at end of a line.", - orderAfter: ["wrap", "wrapArguments"], - options: ["trimwhitespace"] - ) { formatter in - formatter.forEach(.space) { i, _ in - if formatter.token(at: i + 1)?.isLinebreak ?? true, - formatter.options.truncateBlankLines || formatter.token(at: i - 1)?.isLinebreak == false - { - formatter.removeToken(at: i) - } - } - } - - /// Collapse all consecutive blank lines into a single blank line - public let consecutiveBlankLines = FormatRule( - help: "Replace consecutive blank lines with a single blank line." - ) { formatter in - formatter.forEach(.linebreak) { i, _ in - guard let prevIndex = formatter.index(of: .nonSpace, before: i, if: { $0.isLinebreak }) else { - return - } - if let scope = formatter.currentScope(at: i), scope.isMultilineStringDelimiter { - return - } - if let nextIndex = formatter.index(of: .nonSpace, after: i) { - if formatter.tokens[nextIndex].isLinebreak { - formatter.removeTokens(in: i + 1 ... nextIndex) - } - } else if !formatter.options.fragment { - formatter.removeTokens(in: i ..< formatter.tokens.count) - } - } - } - - /// Remove blank lines immediately after an opening brace, bracket, paren or chevron - public let blankLinesAtStartOfScope = FormatRule( - help: "Remove leading blank line at the start of a scope.", - orderAfter: ["organizeDeclarations"], - options: ["typeblanklines"] - ) { formatter in - formatter.forEach(.startOfScope) { i, token in - guard ["{", "(", "[", "<"].contains(token.string), - let indexOfFirstLineBreak = formatter.index(of: .nonSpaceOrComment, after: i), - // If there is extra code on the same line, ignore it - formatter.tokens[indexOfFirstLineBreak].isLinebreak - else { return } - - // Consumers can choose whether or not this rule should apply to type bodies - if !formatter.options.removeStartOrEndBlankLinesFromTypes, - ["class", "actor", "struct", "enum", "protocol", "extension"].contains( - formatter.lastSignificantKeyword(at: i, excluding: ["where"])) - { - return - } - - // Find next non-space token - var index = indexOfFirstLineBreak + 1 - var indexOfLastLineBreak = indexOfFirstLineBreak - loop: while let token = formatter.token(at: index) { - switch token { - case .linebreak: - indexOfLastLineBreak = index - case .space: - break - default: - break loop - } - index += 1 - } - if formatter.options.removeBlankLines, indexOfFirstLineBreak != indexOfLastLineBreak { - formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak) - return - } - } - } - - /// Remove blank lines immediately before a closing brace, bracket, paren or chevron - /// unless it's followed by more code on the same line (e.g. } else { ) - public let blankLinesAtEndOfScope = FormatRule( - help: "Remove trailing blank line at the end of a scope.", - orderAfter: ["organizeDeclarations"], - sharedOptions: ["typeblanklines"] - ) { formatter in - formatter.forEach(.startOfScope) { startOfScopeIndex, _ in - guard let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex) else { return } - let endOfScope = formatter.tokens[endOfScopeIndex] - - guard ["}", ")", "]", ">"].contains(endOfScope.string), - // If there is extra code after the closing scope on the same line, ignore it - (formatter.next(.nonSpaceOrComment, after: endOfScopeIndex).map { $0.isLinebreak }) ?? true - else { return } - - // Consumers can choose whether or not this rule should apply to type bodies - if !formatter.options.removeStartOrEndBlankLinesFromTypes, - ["class", "actor", "struct", "enum", "protocol", "extension"].contains( - formatter.lastSignificantKeyword(at: startOfScopeIndex, excluding: ["where"])) - { - return - } - - // Find previous non-space token - var index = endOfScopeIndex - 1 - var indexOfFirstLineBreak: Int? - var indexOfLastLineBreak: Int? - loop: while let token = formatter.token(at: index) { - switch token { - case .linebreak: - indexOfFirstLineBreak = index - if indexOfLastLineBreak == nil { - indexOfLastLineBreak = index - } - case .space: - break - default: - break loop - } - index -= 1 - } - if formatter.options.removeBlankLines, - let indexOfFirstLineBreak = indexOfFirstLineBreak, - indexOfFirstLineBreak != indexOfLastLineBreak - { - formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak!) - return - } - } - } - - /// Remove blank lines between import statements - public let blankLinesBetweenImports = FormatRule( - help: """ - Remove blank lines between import statements. - """, - disabledByDefault: true, - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.keyword("import")) { currentImportIndex, _ in - guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), - let nextImportIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine, if: { - $0 == .keyword("@testable") || $0 == .keyword("import") - }) - else { - return - } - - formatter.replaceTokens(in: endOfLine ..< nextImportIndex, with: formatter.linebreakToken(for: currentImportIndex + 1)) - } - } - - /// Remove blank lines between chained functions but keep the linebreaks - public let blankLinesBetweenChainedFunctions = FormatRule( - help: """ - Remove blank lines between chained functions but keep the linebreaks. - """ - ) { formatter in - formatter.forEach(.operator(".", .infix)) { i, _ in - let endOfLine = formatter.endOfLine(at: i) - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfLine), - formatter.tokens[nextIndex] == .operator(".", .infix), - // Make sure to preserve any code comment between the two lines - let nextTokenOrComment = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine) - { - if formatter.tokens[nextTokenOrComment].isComment { - if formatter.options.enabledRules.contains(FormatRules.blankLinesAroundMark.name), - case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenOrComment), - body.hasPrefix("MARK:") - { - return - } - if let endOfComment = formatter.index(of: .comment, before: nextIndex) { - let endOfLine = formatter.endOfLine(at: endOfComment) - let startOfLine = formatter.startOfLine(at: nextIndex) - formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) - } - } - let startOfLine = formatter.startOfLine(at: nextTokenOrComment) - formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) - } - } - } - - /// Insert blank line after import statements - public let blankLineAfterImports = FormatRule( - help: """ - Insert blank line after import statements. - """, - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.keyword("import")) { currentImportIndex, _ in - guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), - var nextIndex = formatter.index(of: .nonSpace, after: endOfLine) - else { - return - } - var keyword: Token = formatter.tokens[nextIndex] - while keyword == .startOfScope("#if") || keyword.isModifierKeyword || keyword.isAttribute, - let index = formatter.index(of: .keyword, after: nextIndex) - { - nextIndex = index - keyword = formatter.tokens[nextIndex] - } - switch formatter.tokens[nextIndex] { - case .linebreak, .keyword("import"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"): - break - default: - formatter.insertLinebreak(at: endOfLine) - } - } - } - - /// Adds a blank line immediately after a closing brace, unless followed by another closing brace - public let blankLinesBetweenScopes = FormatRule( - help: """ - Insert blank line before class, struct, enum, extension, protocol or function - declarations. - """, - sharedOptions: ["linebreaks"] - ) { formatter in - var spaceableScopeStack = [true] - var isSpaceableScopeType = false - formatter.forEachToken(onlyWhereEnabled: false) { i, token in - outer: switch token { - case .keyword("class"), - .keyword("actor"), - .keyword("struct"), - .keyword("extension"), - .keyword("enum"): - isSpaceableScopeType = - (formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import")) - case .keyword("func"), .keyword("var"): - isSpaceableScopeType = false - case .startOfScope("{"): - spaceableScopeStack.append(isSpaceableScopeType) - isSpaceableScopeType = false - case .endOfScope("}"): - spaceableScopeStack.removeLast() - guard spaceableScopeStack.last == true, - let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: i), - formatter.lastIndex(of: .linebreak, in: openingBraceIndex + 1 ..< i) != nil - else { - // Inline braces - break - } - var i = i - if let nextTokenIndex = formatter.index(of: .nonSpace, after: i, if: { - $0 == .startOfScope("(") - }), let closingParenIndex = formatter.index(of: - .endOfScope(")"), after: nextTokenIndex) - { - i = closingParenIndex - } - guard let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i), - formatter.isEnabled, formatter.options.insertBlankLines, - let firstLinebreakIndex = formatter.index(of: .linebreak, in: i + 1 ..< nextTokenIndex), - formatter.index(of: .linebreak, in: firstLinebreakIndex + 1 ..< nextTokenIndex) == nil - else { - break - } - if var nextNonCommentIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) - { - while formatter.tokens[nextNonCommentIndex] == .startOfScope("#if"), - let nextIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - after: formatter.endOfLine(at: nextNonCommentIndex) - ) - { - nextNonCommentIndex = nextIndex - } - switch formatter.tokens[nextNonCommentIndex] { - case .error, .endOfScope, - .operator(".", _), .delimiter(","), .delimiter(":"), - .keyword("else"), .keyword("catch"), .keyword("#else"): - break outer - case .keyword("while"): - if let previousBraceIndex = formatter.index(of: .startOfScope("{"), before: i), - formatter.last(.nonSpaceOrCommentOrLinebreak, before: previousBraceIndex) - == .keyword("repeat") - { - break outer - } - default: - if formatter.isLabel(at: nextNonCommentIndex), let colonIndex - = formatter.index(of: .delimiter(":"), after: nextNonCommentIndex), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) - == .startOfScope("{") - { - break outer - } - } - } - switch formatter.tokens[nextTokenIndex] { - case .startOfScope("//"): - if case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenIndex), - body.trimmingCharacters(in: .whitespaces).lowercased().hasPrefix("sourcery:") - { - break - } - formatter.insertLinebreak(at: firstLinebreakIndex) - default: - formatter.insertLinebreak(at: firstLinebreakIndex) - } - default: - break - } - } - } - - /// Adds a blank line around MARK: comments - public let blankLinesAroundMark = FormatRule( - help: "Insert blank line before and after `MARK:` comments.", - options: ["lineaftermarks"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEachToken { i, token in - guard case let .commentBody(comment) = token, comment.hasPrefix("MARK:"), - let startIndex = formatter.index(of: .nonSpace, before: i), - formatter.tokens[startIndex] == .startOfScope("//") else { return } - if let nextIndex = formatter.index(of: .linebreak, after: i), - let nextToken = formatter.next(.nonSpace, after: nextIndex), - !nextToken.isLinebreak, nextToken != .endOfScope("}"), - formatter.options.lineAfterMarks - { - formatter.insertLinebreak(at: nextIndex) - } - if formatter.options.insertBlankLines, - let lastIndex = formatter.index(of: .linebreak, before: startIndex), - let lastToken = formatter.last(.nonSpace, before: lastIndex), - !lastToken.isLinebreak, lastToken != .startOfScope("{") - { - formatter.insertLinebreak(at: lastIndex) - } - } - } - - /// Always end file with a linebreak, to avoid incompatibility with certain unix tools: - /// http://stackoverflow.com/questions/2287967/why-is-it-recommended-to-have-empty-line-in-the-end-of-file - public let linebreakAtEndOfFile = FormatRule( - help: "Add empty blank line at end of file.", - sharedOptions: ["linebreaks"] - ) { formatter in - guard !formatter.options.fragment else { return } - var wasLinebreak = true - formatter.forEachToken(onlyWhereEnabled: false) { _, token in - switch token { - case .linebreak: - wasLinebreak = true - case .space: - break - default: - wasLinebreak = false - } - } - if formatter.isEnabled, !wasLinebreak { - formatter.insertLinebreak(at: formatter.tokens.count) - } - } - - /// Indent code according to standard scope indenting rules. - /// The type (tab or space) and level (2 spaces, 4 spaces, etc.) of the - /// indenting can be configured with the `options` parameter of the formatter. - public let indent = FormatRule( - help: "Indent code in accordance with the scope level.", - orderAfter: ["trailingSpace", "wrap", "wrapArguments"], - options: ["indent", "tabwidth", "smarttabs", "indentcase", "ifdef", "xcodeindentation", "indentstrings"], - sharedOptions: ["trimwhitespace", "allman", "wrapconditions", "wrapternary"] - ) { formatter in - var scopeStack: [Token] = [] - var scopeStartLineIndexes: [Int] = [] - var lastNonSpaceOrLinebreakIndex = -1 - var lastNonSpaceIndex = -1 - var indentStack = [""] - var stringBodyIndentStack = [""] - var indentCounts = [1] - var linewrapStack = [false] - var lineIndex = 0 - - func inFunctionDeclarationWhereReturnTypeIsWrappedToStartOfLine(at i: Int) -> Bool { - guard let returnOperatorIndex = formatter.startOfReturnType(at: i) else { - return false - } - return formatter.last(.nonSpaceOrComment, before: returnOperatorIndex)?.isLinebreak == true - } - - func isFirstStackedClosureArgument(at i: Int) -> Bool { - assert(formatter.tokens[i] == .startOfScope("{")) - if let prevIndex = formatter.index(of: .nonSpace, before: i), - let prevToken = formatter.token(at: prevIndex), prevToken == .startOfScope("(") || - (prevToken == .delimiter(":") && formatter.token(at: prevIndex - 1)?.isIdentifier == true - && formatter.last(.nonSpace, before: prevIndex - 1) == .startOfScope("(")), - let endIndex = formatter.endOfScope(at: i), - let commaIndex = formatter.index(of: .nonSpace, after: endIndex, if: { - $0 == .delimiter(",") - }), - formatter.next(.nonSpaceOrComment, after: commaIndex)?.isLinebreak == true - { - return true - } - return false - } - - if formatter.options.fragment, - let firstIndex = formatter.index(of: .nonSpaceOrLinebreak, after: -1), - let indentToken = formatter.token(at: firstIndex - 1), case let .space(string) = indentToken - { - indentStack[0] = string - } - formatter.forEachToken(onlyWhereEnabled: false) { i, token in - func popScope() { - if linewrapStack.last == true { - indentStack.removeLast() - stringBodyIndentStack.removeLast() - } - indentStack.removeLast() - stringBodyIndentStack.removeLast() - indentCounts.removeLast() - linewrapStack.removeLast() - scopeStartLineIndexes.removeLast() - scopeStack.removeLast() - } - - func stringBodyIndent(at i: Int) -> String { - var space = "" - let start = formatter.startOfLine(at: i) - if let index = formatter.index(of: .nonSpace, in: start ..< i), - case let .stringBody(string) = formatter.tokens[index], - string.unicodeScalars.first?.isSpace == true - { - var index = string.startIndex - while index < string.endIndex, string[index].unicodeScalars.first!.isSpace { - space.append(string[index]) - index = string.index(after: index) - } - } - return space - } - - var i = i - switch token { - case let .startOfScope(string): - switch string { - case ":" where scopeStack.last == .endOfScope("case"): - popScope() - case "{" where !formatter.isStartOfClosure(at: i, in: scopeStack.last) && - linewrapStack.last == true: - indentStack.removeLast() - linewrapStack[linewrapStack.count - 1] = false - default: - break - } - // Handle start of scope - scopeStack.append(token) - var indentCount: Int - if lineIndex > scopeStartLineIndexes.last ?? -1 { - indentCount = 1 - } else if token.isMultilineStringDelimiter, let endIndex = formatter.endOfScope(at: i), - let closingIndex = formatter.index(of: .endOfScope(")"), after: endIndex), - formatter.next(.linebreak, in: endIndex + 1 ..< closingIndex) != nil - { - indentCount = 1 - } else if scopeStack.count > 1, scopeStack[scopeStack.count - 2] == .startOfScope(":") { - indentCount = 1 - } else { - indentCount = indentCounts.last! + 1 - } - var indent = indentStack[indentStack.count - indentCount] - - switch string { - case "/*": - if scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("/*") { - // Comments only indent one space - indent += " " - } - case ":": - indent += formatter.options.indent - if formatter.options.indentCase, - scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("#if") - { - indent += formatter.options.indent - } - case "#if": - if let lineIndex = formatter.index(of: .linebreak, after: i), - let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ - .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), - ].contains(nextKeyword) - { - indent = indentStack[indentStack.count - indentCount - 1] - if formatter.options.indentCase { - indent += formatter.options.indent - } - } - switch formatter.options.ifdefIndent { - case .indent: - i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) - indent += formatter.options.indent - case .noIndent: - i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) - case .outdent: - i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) - } - case "{" where isFirstStackedClosureArgument(at: i): - guard var prevIndex = formatter.index(of: .nonSpace, before: i) else { - assertionFailure() - break - } - if formatter.tokens[prevIndex] == .delimiter(":") { - guard formatter.token(at: prevIndex - 1)?.isIdentifier == true, - let parenIndex = formatter.index(of: .nonSpace, before: prevIndex - 1, if: { - $0 == .startOfScope("(") - }) - else { - let stringIndent = stringBodyIndent(at: i) - stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent - indent += stringIndent + formatter.options.indent - break - } - prevIndex = parenIndex - } - let startIndex = formatter.startOfLine(at: i) - indent = formatter.spaceEquivalentToTokens(from: startIndex, upTo: prevIndex + 1) - indentStack[indentStack.count - 1] = indent - indent += formatter.options.indent - indentCount -= 1 - case "{" where formatter.isStartOfClosure(at: i): - // When a trailing closure starts on the same line as the end of a multi-line - // method call the trailing closure body should be double-indented - if let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i), - formatter.tokens[prevIndex] == .endOfScope(")"), - case let prevIndent = formatter.currentIndentForLine(at: prevIndex), - prevIndent == indent + formatter.options.indent - { - if linewrapStack.last == false { - linewrapStack[linewrapStack.count - 1] = true - indentStack.append(prevIndent) - stringBodyIndentStack.append("") - } - indent = prevIndent - } - let stringIndent = stringBodyIndent(at: i) - stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent - indent += stringIndent + formatter.options.indent - case _ where token.isStringDelimiter, "//": - break - case "[", "(": - guard let linebreakIndex = formatter.index(of: .linebreak, after: i), - let nextIndex = formatter.index(of: .nonSpace, after: i), - nextIndex != linebreakIndex - else { - fallthrough - } - if formatter.last(.nonSpaceOrComment, before: linebreakIndex) != .delimiter(","), - formatter.next(.nonSpaceOrComment, after: linebreakIndex) != .delimiter(",") - { - fallthrough - } - let start = formatter.startOfLine(at: i) - // Align indent with previous value - let lastIndentCount = indentCounts.last ?? 0 - if indentCount > lastIndentCount { - indentCount = lastIndentCount - indentCounts[indentCounts.count - 1] = 1 - } - indent = formatter.spaceEquivalentToTokens(from: start, upTo: nextIndex) - default: - let stringIndent = stringBodyIndent(at: i) - stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent - indent += stringIndent + formatter.options.indent - } - indentStack.append(indent) - stringBodyIndentStack.append("") - indentCounts.append(indentCount) - scopeStartLineIndexes.append(lineIndex) - linewrapStack.append(false) - case .space: - if i == 0, !formatter.options.fragment, - formatter.token(at: i + 1)?.isLinebreak != true - { - formatter.removeToken(at: i) - } - case .error("}"), .error("]"), .error(")"), .error(">"): - // Handled over-terminated fragment - if let prevToken = formatter.token(at: i - 1) { - if case let .space(string) = prevToken { - let prevButOneToken = formatter.token(at: i - 2) - if prevButOneToken == nil || prevButOneToken!.isLinebreak { - indentStack[0] = string - } - } else if prevToken.isLinebreak { - indentStack[0] = "" - } - } - return - case .keyword("#else"), .keyword("#elseif"): - var indent = indentStack[indentStack.count - 2] - if scopeStack.last == .startOfScope(":") { - indent = indentStack[indentStack.count - 4] - if formatter.options.indentCase { - indent += formatter.options.indent - } - } - let start = formatter.startOfLine(at: i) - switch formatter.options.ifdefIndent { - case .indent, .noIndent: - i += formatter.insertSpaceIfEnabled(indent, at: start) - case .outdent: - i += formatter.insertSpaceIfEnabled("", at: start) - } - case .keyword("@unknown") where scopeStack.last != .startOfScope("#if"): - var indent = indentStack[indentStack.count - 2] - if formatter.options.indentCase { - indent += formatter.options.indent - } - let start = formatter.startOfLine(at: i) - let stringIndent = stringBodyIndentStack.last! - i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) - case .keyword("in") where scopeStack.last == .startOfScope("{"): - if let startIndex = formatter.index(of: .startOfScope("{"), before: i), - formatter.index(of: .keyword("for"), in: startIndex + 1 ..< i) == nil, - let paramsIndex = formatter.index(of: .startOfScope, in: startIndex + 1 ..< i), - !formatter.tokens[startIndex + 1 ..< paramsIndex].contains(where: { - $0.isLinebreak - }), formatter.tokens[paramsIndex + 1 ..< i].contains(where: { - $0.isLinebreak - }) - { - indentStack[indentStack.count - 1] += formatter.options.indent - } - case .operator("=", .infix): - // If/switch expressions on their own line following an `=` assignment should always be indented - guard let nextKeyword = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - ["if", "switch"].contains(formatter.tokens[nextKeyword].string), - !formatter.onSameLine(i, nextKeyword) - else { fallthrough } - - let indent = (indentStack.last ?? "") + formatter.options.indent - indentStack.append(indent) - stringBodyIndentStack.append("") - indentCounts.append(1) - scopeStartLineIndexes.append(lineIndex) - linewrapStack.append(false) - scopeStack.append(.operator("=", .infix)) - scopeStartLineIndexes.append(lineIndex) - default: - // If this is the final `endOfScope` in a conditional assignment, - // we have to end the scope introduced by that assignment operator. - defer { - if token == .endOfScope("}"), let startOfScope = formatter.startOfScope(at: i) { - // Find the `=` before this start of scope, which isn't itself part of the conditional statement - var previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: startOfScope) - while let currentPreviousAssignmentIndex = previousAssignmentIndex, - formatter.isConditionalStatement(at: currentPreviousAssignmentIndex) - { - previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: currentPreviousAssignmentIndex) - } - - // Make sure the `=` actually created a new scope - if scopeStack.last == .operator("=", .infix), - // Parse the conditional branches following the `=` assignment operator - let previousAssignmentIndex = previousAssignmentIndex, - let nextTokenAfterAssignment = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: previousAssignmentIndex), - let conditionalBranches = formatter.conditionalBranches(at: nextTokenAfterAssignment), - // If this is the very end of the conditional assignment following the `=`, - // then we can end the scope. - conditionalBranches.last?.endOfBranch == i - { - popScope() - } - } - } - - // Handle end of scope - if let scope = scopeStack.last, token.isEndOfScope(scope) { - let indentCount = indentCounts.last! - 1 - popScope() - guard !token.isLinebreak, lineIndex > scopeStartLineIndexes.last ?? -1 else { - break - } - // If indentCount > 0, drop back to previous indent level - if indentCount > 0 { - indentStack.removeLast(indentCount) - stringBodyIndentStack.removeLast(indentCount) - for _ in 0 ..< indentCount { - indentStack.append(indentStack.last ?? "") - stringBodyIndentStack.append(stringBodyIndentStack.last ?? "") - } - } - - // Don't reduce indent if line doesn't start with end of scope - let start = formatter.startOfLine(at: i) - guard let firstIndex = formatter.index(of: .nonSpaceOrComment, after: start - 1) else { - break - } - if firstIndex != i { - break - } - func isInIfdef() -> Bool { - guard scopeStack.last == .startOfScope("#if") else { - return false - } - var index = i - 1 - while index > 0 { - switch formatter.tokens[index] { - case .keyword("switch"): - return false - case .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): - return true - default: - index -= 1 - } - } - return false - } - if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .outdent { - i += formatter.insertSpaceIfEnabled("", at: start) - } else { - var indent = indentStack.last ?? "" - if token.isSwitchCaseOrDefault, - formatter.options.indentCase, !isInIfdef() - { - indent += formatter.options.indent - } - let stringIndent = stringBodyIndentStack.last! - i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) - } - } else if token == .endOfScope("#endif"), indentStack.count > 1 { - var indent = indentStack[indentStack.count - 2] - if scopeStack.last == .startOfScope(":"), indentStack.count > 1 { - indent = indentStack[indentStack.count - 4] - if formatter.options.indentCase { - indent += formatter.options.indent - } - popScope() - } - switch formatter.options.ifdefIndent { - case .indent, .noIndent: - i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) - case .outdent: - i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) - } - if scopeStack.last == .startOfScope("#if") { - popScope() - } - } - } - switch token { - case .endOfScope("case"): - scopeStack.append(token) - var indent = (indentStack.last ?? "") - if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { - indent += formatter.options.indent - } else { - if formatter.options.indentCase { - indent += formatter.options.indent - } - // Align indent with previous case value - indent += formatter.spaceEquivalentToWidth(5) - } - indentStack.append(indent) - stringBodyIndentStack.append("") - indentCounts.append(1) - scopeStartLineIndexes.append(lineIndex) - linewrapStack.append(false) - fallthrough - case .endOfScope("default"), .keyword("@unknown"), - .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): - var index = formatter.startOfLine(at: i) - if index == i || index == i - 1 { - let indent: String - if case let .space(space) = formatter.tokens[index] { - indent = space - } else { - indent = "" - } - index -= 1 - while let prevToken = formatter.token(at: index - 1), prevToken.isComment, - let startIndex = formatter.index(of: .nonSpaceOrComment, before: index), - formatter.tokens[startIndex].isLinebreak - { - // Set indent for comment immediately before this line to match this line - if !formatter.isCommentedCode(at: startIndex + 1) { - formatter.insertSpaceIfEnabled(indent, at: startIndex + 1) - } - if case .endOfScope("*/") = prevToken, - var index = formatter.index(of: .startOfScope("/*"), after: startIndex) - { - while let linebreakIndex = formatter.index(of: .linebreak, after: index) { - formatter.insertSpaceIfEnabled(indent + " ", at: linebreakIndex + 1) - index = linebreakIndex - } - } - index = startIndex - } - } - case .linebreak: - // Detect linewrap - let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) - let _nextToken = nextTokenIndex.map { formatter.tokens[$0] } ?? .space("") - let linewrapped = lastNonSpaceOrLinebreakIndex > -1 && ( - !formatter.isEndOfStatement(at: lastNonSpaceOrLinebreakIndex, in: scopeStack.last) || - (nextTokenIndex.map { formatter.isTrailingClosureLabel(at: $0) } == true) || - !(nextTokenIndex == nil || [ - .endOfScope("}"), .endOfScope("]"), .endOfScope(")"), - ].contains(_nextToken) || _nextToken.isStringBody || - formatter.isStartOfStatement(at: nextTokenIndex!, in: scopeStack.last) || ( - ((_nextToken.isIdentifier && !(_nextToken == .identifier("async") && formatter.currentScope(at: nextTokenIndex!) != .startOfScope("("))) || [ - .keyword("try"), .keyword("await"), - ].contains(_nextToken)) && - formatter.last(.nonSpaceOrCommentOrLinebreak, before: nextTokenIndex!).map { - $0 != .keyword("return") && !$0.isOperator(ofType: .infix) - } ?? false) || ( - _nextToken == .delimiter(",") && [ - "<", "[", "(", "case", - ].contains(formatter.currentScope(at: nextTokenIndex!)?.string ?? "") - ) - ) - ) - - // Determine current indent - var indent = indentStack.last ?? "" - if linewrapped, lineIndex == scopeStartLineIndexes.last { - indent = indentStack.count > 1 ? indentStack[indentStack.count - 2] : "" - } - lineIndex += 1 - - func shouldIndentNextLine(at i: Int) -> Bool { - // If there is a linebreak after certain symbols, we should add - // an additional indentation to the lines at the same indention scope - // after this line. - let endOfLine = formatter.endOfLine(at: i) - switch formatter.token(at: endOfLine - 1) { - case .keyword("return")?, .operator("=", .infix)?: - let endOfNextLine = formatter.endOfLine(at: endOfLine + 1) - switch formatter.last(.nonSpaceOrCommentOrLinebreak, before: endOfNextLine) { - case .operator(_, .infix)?, .delimiter(",")?: - return false - case .endOfScope(")")?: - return !formatter.options.xcodeIndentation - default: - return formatter.lastIndex(of: .startOfScope, in: i ..< endOfNextLine) == nil - } - default: - return false - } - } - - guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: i), - let nextToken = formatter.token(at: nextNonSpaceIndex) - else { - break - } - - // Begin wrap scope - if linewrapStack.last == true { - if !linewrapped { - indentStack.removeLast() - linewrapStack[linewrapStack.count - 1] = false - indent = indentStack.last! - } else { - let shouldIndentLeadingDotStatement: Bool - if formatter.options.xcodeIndentation { - if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), - formatter.token(at: formatter.startOfLine( - at: prevIndex, excludingIndent: true - )) == .endOfScope("}"), - formatter.index(of: .linebreak, in: prevIndex + 1 ..< i) != nil - { - shouldIndentLeadingDotStatement = false - } else { - shouldIndentLeadingDotStatement = true - } - } else { - shouldIndentLeadingDotStatement = ( - formatter.startOfConditionalStatement(at: i) != nil - && formatter.options.wrapConditions == .beforeFirst - ) - } - if shouldIndentLeadingDotStatement, - formatter.next(.nonSpace, after: i) == .operator(".", .infix), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), - case let lineStart = formatter.index(of: .linebreak, before: prevIndex + 1) ?? - formatter.startOfLine(at: prevIndex), - let startIndex = formatter.index(of: .nonSpace, after: lineStart), - formatter.isStartOfStatement(at: startIndex) || ( - (formatter.tokens[startIndex].isIdentifier || [ - .keyword("try"), .keyword("await"), - ].contains(formatter.tokens[startIndex]) || - formatter.isTrailingClosureLabel(at: startIndex)) && - formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex).map { - $0 != .keyword("return") && !$0.isOperator(ofType: .infix) - } ?? false) - { - indent += formatter.options.indent - indentStack[indentStack.count - 1] = indent - } - - // When inside conditionals, unindent after any commas (which separate conditions) - // that were indented by the block above - if !formatter.options.xcodeIndentation, - formatter.options.wrapConditions == .beforeFirst, - formatter.isConditionalStatement(at: i), - formatter.lastToken(before: i, where: { - $0.is(.nonSpaceOrCommentOrLinebreak) - }) == .delimiter(","), - let conditionBeginIndex = formatter.index(before: i, where: { - ["if", "guard", "while", "for"].contains($0.string) - }), - formatter.currentIndentForLine(at: conditionBeginIndex) - .count < indent.count + formatter.options.indent.count - { - indent = formatter.currentIndentForLine(at: conditionBeginIndex) + formatter.options.indent - indentStack[indentStack.count - 1] = indent - } - - let startOfLineIndex = formatter.startOfLine(at: i, excludingIndent: true) - let startOfLine = formatter.tokens[startOfLineIndex] - - if formatter.options.wrapTernaryOperators == .beforeOperators, - startOfLine == .operator(":", .infix) || startOfLine == .operator("?", .infix) - { - // Push a ? scope onto the stack so we can easily know - // that the next : is the closing operator of this ternary - if startOfLine.string == "?" { - // We smuggle the index of this operator in the scope stack - // so we can recover it trivially when handling the - // corresponding : operator. - scopeStack.append(.operator("?-\(startOfLineIndex)", .infix)) - } - - // Indent any operator-leading lines following a compomnent operator - // of a wrapped ternary operator expression, except for the : - // following a ? - if let nextToken = formatter.next(.nonSpace, after: i), - nextToken.isOperator(ofType: .infix), - nextToken != .operator(":", .infix) - { - indent += formatter.options.indent - indentStack[indentStack.count - 1] = indent - } - } - - // Make sure the indentation for this : operator matches - // the indentation of the previous ? operator - if formatter.options.wrapTernaryOperators == .beforeOperators, - formatter.next(.nonSpace, after: i) == .operator(":", .infix), - let scope = scopeStack.last, - scope.string.hasPrefix("?"), - scope.isOperator(ofType: .infix), - let previousOperatorIndex = scope.string.components(separatedBy: "-").last.flatMap({ Int($0) }) - { - scopeStack.removeLast() - indent = formatter.currentIndentForLine(at: previousOperatorIndex) - indentStack[indentStack.count - 1] = indent - } - } - } else if linewrapped { - func isWrappedDeclaration() -> Bool { - guard let keywordIndex = formatter - .indexOfLastSignificantKeyword(at: i, excluding: [ - "where", "throws", "rethrows", - ]), !formatter.tokens[keywordIndex ..< i].contains(.endOfScope("}")), - case let .keyword(keyword) = formatter.tokens[keywordIndex], - ["class", "actor", "struct", "enum", "protocol", "extension", - "func"].contains(keyword) - else { - return false - } - - let end = formatter.endOfLine(at: i + 1) - guard let lastToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: end + 1), - [.startOfScope("{"), .endOfScope("}")].contains(lastToken) else { return false } - - return true - } - - // Don't indent line starting with dot if previous line was just a closing brace - var lastToken = formatter.tokens[lastNonSpaceOrLinebreakIndex] - if formatter.options.allmanBraces, nextToken == .startOfScope("{"), - formatter.isStartOfClosure(at: nextNonSpaceIndex) - { - // Don't indent further - } else if formatter.token(at: nextTokenIndex ?? -1) == .operator(".", .infix) || - formatter.isLabel(at: nextTokenIndex ?? -1) - { - var lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) - let startToken = formatter.token(at: lineStart) - if let startToken = startToken, [ - .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif") - ].contains(startToken) { - if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lineStart) { - lastNonSpaceOrLinebreakIndex = index - lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) - } - } - if formatter.token(at: lineStart) == .operator(".", .infix), - [.keyword("#else"), .keyword("#elseif"), .endOfScope("#endif")].contains(startToken) - { - indent = formatter.currentIndentForLine(at: lineStart) - } else if formatter.tokens[lineStart ..< lastNonSpaceOrLinebreakIndex].allSatisfy({ - $0.isEndOfScope || $0.isSpaceOrComment - }) { - if lastToken.isEndOfScope { - indent = formatter.currentIndentForLine(at: lastNonSpaceOrLinebreakIndex) - } - if !lastToken.isEndOfScope || lastToken == .endOfScope("case") || - formatter.options.xcodeIndentation, ![ - .endOfScope("}"), .endOfScope(")") - ].contains(lastToken) - { - indent += formatter.options.indent - } - } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { - indent += formatter.linewrapIndent(at: i) - } - } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { - indent += formatter.linewrapIndent(at: i) - } - - linewrapStack[linewrapStack.count - 1] = true - indentStack.append(indent) - stringBodyIndentStack.append("") - } - // Avoid indenting commented code - guard !formatter.isCommentedCode(at: nextNonSpaceIndex) else { - break - } - // Apply indent - switch nextToken { - case .linebreak: - if formatter.options.truncateBlankLines { - formatter.insertSpaceIfEnabled("", at: i + 1) - } else if scopeStack.last?.isStringDelimiter == true, - formatter.token(at: i + 1)?.isSpace == true - { - formatter.insertSpaceIfEnabled(indent, at: i + 1) - } - case .error, .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"), - .startOfScope("#if") where formatter.options.ifdefIndent != .indent: - break - case .startOfScope("/*"), .commentBody, .endOfScope("*/"): - nextNonSpaceIndex = formatter.endOfScope(at: nextNonSpaceIndex) ?? nextNonSpaceIndex - fallthrough - case .startOfScope("//"): - nextNonSpaceIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, - after: nextNonSpaceIndex) ?? nextNonSpaceIndex - nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, - before: nextNonSpaceIndex) ?? nextNonSpaceIndex - if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), - let nextToken = formatter.next(.nonSpace, after: lineIndex), - [.startOfScope("#if"), .keyword("#else"), .keyword("#elseif")].contains(nextToken) - { - break - } - fallthrough - case .startOfScope("#if"): - if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), - let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ - .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), - ].contains(nextKeyword) - { - break - } - formatter.insertSpaceIfEnabled(indent, at: i + 1) - case .endOfScope, .keyword("@unknown"): - if let scope = scopeStack.last { - switch scope { - case .startOfScope("/*"), .startOfScope("#if"), - .keyword("#else"), .keyword("#elseif"), - .startOfScope where scope.isStringDelimiter: - formatter.insertSpaceIfEnabled(indent, at: i + 1) - default: - break - } - } - default: - var lastIndex = lastNonSpaceOrLinebreakIndex > -1 ? lastNonSpaceOrLinebreakIndex : i - while formatter.token(at: lastIndex) == .endOfScope("#endif"), - let index = formatter.index(of: .startOfScope, before: lastIndex, if: { - $0 == .startOfScope("#if") - }) - { - lastIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - before: index - ) ?? index - } - let lastToken = formatter.tokens[lastIndex] - if [.endOfScope("}"), .endOfScope(")")].contains(lastToken), - lastIndex == formatter.startOfLine(at: lastIndex, excludingIndent: true), - formatter.token(at: nextNonSpaceIndex) == .operator(".", .infix) || - (lastToken == .endOfScope("}") && formatter.isLabel(at: nextNonSpaceIndex)) - { - indent = formatter.currentIndentForLine(at: lastIndex) - } - if formatter.options.fragment, lastToken == .delimiter(",") { - break // Can't reliably indent - } - formatter.insertSpaceIfEnabled(indent, at: i + 1) - } - - if linewrapped, shouldIndentNextLine(at: i) { - indentStack[indentStack.count - 1] += formatter.options.indent - } - default: - break - } - // Track token for line wraps - if !token.isSpaceOrComment { - lastNonSpaceIndex = i - if !token.isLinebreak { - lastNonSpaceOrLinebreakIndex = i - } - } - } - - if formatter.options.indentStrings { - formatter.forEach(.startOfScope("\"\"\"")) { stringStartIndex, _ in - let baseIndent = formatter.currentIndentForLine(at: stringStartIndex) - let expectedIndent = baseIndent + formatter.options.indent - - guard let stringEndIndex = formatter.endOfScope(at: stringStartIndex), - // Preserve the default indentation if the opening """ is on a line by itself - formatter.startOfLine(at: stringStartIndex, excludingIndent: true) != stringStartIndex - else { return } - - for linebreakIndex in (stringStartIndex ..< stringEndIndex).reversed() - where formatter.tokens[linebreakIndex].isLinebreak - { - // If this line is completely blank, do nothing - // - This prevents conflicts with the trailingSpace rule - if formatter.nextToken(after: linebreakIndex)?.isLinebreak == true { - continue - } - - let indentIndex = linebreakIndex + 1 - if formatter.tokens[indentIndex].is(.space) { - formatter.replaceToken(at: indentIndex, with: .space(expectedIndent)) - } else { - formatter.insert(.space(expectedIndent), at: indentIndex) - } - } - } - } - } - - /// Add @available(*, unavailable) to init?(coder aDecoder: NSCoder) - public let initCoderUnavailable = FormatRule( - help: """ - Add `@available(*, unavailable)` attribute to required `init(coder:)` when - it hasn't been implemented. - """, - options: ["initcodernil"], - sharedOptions: ["linebreaks"] - ) { formatter in - let unavailableTokens = tokenize("@available(*, unavailable)") - formatter.forEach(.identifier("required")) { i, _ in - // look for required init?(coder - guard var initIndex = formatter.index(of: .keyword("init"), after: i) else { return } - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { - $0 == .operator("?", .postfix) - }) { - initIndex = nextIndex - } - - guard let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { - $0 == .startOfScope("(") - }), let coderIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { - $0 == .identifier("coder") - }), let endParenIndex = formatter.index(of: .endOfScope(")"), after: coderIndex), - let braceIndex = formatter.index(of: .startOfScope("{"), after: endParenIndex) - else { return } - - // make sure the implementation is empty or fatalError - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: braceIndex, if: { - [.endOfScope("}"), .identifier("fatalError")].contains($0) - }) else { return } - - if formatter.options.initCoderNil, - formatter.token(at: firstTokenIndex) == .identifier("fatalError"), - let fatalParenEndOfScope = formatter.index(of: .endOfScope, after: firstTokenIndex + 1) - { - formatter.replaceTokens(in: firstTokenIndex ... fatalParenEndOfScope, with: [.identifier("nil")]) - } - - // avoid adding attribute if it's already there - if formatter.modifiersForDeclaration(at: i, contains: "@available") { return } - - let startIndex = formatter.startOfModifiers(at: i, includingAttributes: true) - formatter.insert(.space(formatter.currentIndentForLine(at: startIndex)), at: startIndex) - formatter.insertLinebreak(at: startIndex) - formatter.insert(unavailableTokens, at: startIndex) - } - } - - /// Implement brace-wrapping rules - public let braces = FormatRule( - help: "Wrap braces in accordance with selected style (K&R or Allman).", - options: ["allman"], - sharedOptions: ["linebreaks", "maxwidth", "indent", "tabwidth", "assetliterals"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard let closingBraceIndex = formatter.endOfScope(at: i), - // Check this isn't an inline block - formatter.index(of: .linebreak, in: i + 1 ..< closingBraceIndex) != nil, - let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - ![.delimiter(","), .keyword("in")].contains(prevToken), - !prevToken.is(.startOfScope) - else { - return - } - if let penultimateToken = formatter.last(.nonSpaceOrComment, before: closingBraceIndex), - !penultimateToken.isLinebreak - { - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: closingBraceIndex) - formatter.insertLinebreak(at: closingBraceIndex) - if formatter.token(at: closingBraceIndex - 1)?.isSpace == true { - formatter.removeToken(at: closingBraceIndex - 1) - } - } - if formatter.options.allmanBraces { - // Implement Allman-style braces, where opening brace appears on the next line - switch formatter.last(.nonSpace, before: i) ?? .space("") { - case .identifier, .keyword, .endOfScope, .number, - .operator("?", .postfix), .operator("!", .postfix): - formatter.insertLinebreak(at: i) - if let breakIndex = formatter.index(of: .linebreak, after: i + 1), - let nextIndex = formatter.index(of: .nonSpace, after: breakIndex, if: { $0.isLinebreak }) - { - formatter.removeTokens(in: breakIndex ..< nextIndex) - } - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) - if formatter.tokens[i - 1].isSpace { - formatter.removeToken(at: i - 1) - } - default: - break - } - } else { - // Implement K&R-style braces, where opening brace appears on the same line - guard let prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), - formatter.tokens[prevIndex ..< i].contains(where: { $0.isLinebreak }), - !formatter.tokens[prevIndex].isComment - else { - return - } - - var maxWidth = formatter.options.maxWidth - if maxWidth == 0 { - maxWidth = .max - } - - // Check that unwrapping wouldn't exceed line length - let endOfLine = formatter.endOfLine(at: i) - let length = formatter.lineLength(from: i, upTo: endOfLine) - let prevLineLength = formatter.lineLength(at: prevIndex) - guard prevLineLength + length + 1 <= maxWidth else { - return - } - - // Avoid conflicts with wrapMultilineStatementBraces - let ruleName = FormatRules.wrapMultilineStatementBraces.name - if formatter.options.enabledRules.contains(ruleName), - formatter.shouldWrapMultilineStatementBrace(at: i) - { - return - } - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) - } - } - } - - /// Ensure that an `else` statement following `if { ... }` appears on the same line - /// as the closing brace. This has no effect on the `else` part of a `guard` statement. - /// Also applies to `catch` after `try` and `while` after `repeat`. - public let elseOnSameLine = FormatRule( - help: """ - Place `else`, `catch` or `while` keyword in accordance with current style (same or - next line). - """, - orderAfter: ["wrapMultilineStatementBraces"], - options: ["elseposition", "guardelse"], - sharedOptions: ["allman", "linebreaks"] - ) { formatter in - func bracesContainLinebreak(_ endIndex: Int) -> Bool { - guard let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { - return false - } - return (startIndex ..< endIndex).contains(where: { formatter.tokens[$0].isLinebreak }) - } - formatter.forEachToken { i, token in - switch token { - case .keyword("while"): - if let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .endOfScope("}") - }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex), - formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex) == .keyword("repeat") { - fallthrough - } - case .keyword("else"): - guard var prevIndex = formatter.index(of: .nonSpace, before: i), - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - !$0.isComment - }) - else { - return - } - let isOnNewLine = formatter.tokens[prevIndex].isLinebreak - if isOnNewLine { - prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) ?? prevIndex - } - if formatter.tokens[prevIndex] == .endOfScope("}") { - fallthrough - } - guard let guardIndex = formatter.indexOfLastSignificantKeyword(at: prevIndex + 1, excluding: [ - "var", "let", "case", - ]), formatter.tokens[guardIndex] == .keyword("guard") else { - return - } - let shouldWrap: Bool - switch formatter.options.guardElsePosition { - case .auto: - // Only wrap if else or following brace is on next line - shouldWrap = isOnNewLine || - formatter.tokens[i + 1 ..< nextIndex].contains { $0.isLinebreak } - case .nextLine: - // Only wrap if guard statement spans multiple lines - shouldWrap = isOnNewLine || - formatter.tokens[guardIndex + 1 ..< nextIndex].contains { $0.isLinebreak } - case .sameLine: - shouldWrap = false - } - if shouldWrap { - if !formatter.options.allmanBraces { - formatter.replaceTokens(in: i + 1 ..< nextIndex, with: .space(" ")) - } - if !isOnNewLine { - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: - formatter.linebreakToken(for: prevIndex + 1)) - formatter.insertSpace(formatter.currentIndentForLine(at: guardIndex), at: prevIndex + 2) - } - } else if isOnNewLine { - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) - } - case .keyword("catch"): - guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { - return - } - - let precededByBlankLine = formatter.tokens[prevIndex].isLinebreak - && formatter.lastToken(before: prevIndex, where: { !$0.isSpaceOrComment })?.isLinebreak == true - - if precededByBlankLine { - return - } - - let shouldWrap = formatter.options.allmanBraces || formatter.options.elseOnNextLine - if !shouldWrap, formatter.tokens[prevIndex].isLinebreak { - if let prevBraceIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex, if: { - $0 == .endOfScope("}") - }), bracesContainLinebreak(prevBraceIndex) { - formatter.replaceTokens(in: prevBraceIndex + 1 ..< i, with: .space(" ")) - } - } else if shouldWrap, let token = formatter.token(at: prevIndex), !token.isLinebreak, - let prevBraceIndex = (token == .endOfScope("}")) ? prevIndex : - formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex, if: { - $0 == .endOfScope("}") - }), bracesContainLinebreak(prevBraceIndex) - { - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: - formatter.linebreakToken(for: prevIndex + 1)) - formatter.insertSpace(formatter.currentIndentForLine(at: prevIndex + 1), at: prevIndex + 2) - } - default: - break - } - } - } - - public let wrapConditionalBodies = FormatRule( - help: "Wrap the bodies of inline conditional statements onto a new line.", - disabledByDefault: true, - sharedOptions: ["linebreaks", "indent"] - ) { formatter in - formatter.forEachToken(where: { [.keyword("if"), .keyword("else")].contains($0) }) { i, _ in - guard let startIndex = formatter.index(of: .startOfScope("{"), after: i) else { - return formatter.fatalError("Expected {", at: i) - } - formatter.wrapStatementBody(at: startIndex) - } - } - - public let wrapLoopBodies = FormatRule( - help: "Wrap the bodies of inline loop statements onto a new line.", - orderAfter: ["preferForLoop"], - sharedOptions: ["linebreaks", "indent"] - ) { formatter in - formatter.forEachToken(where: { [ - .keyword("for"), - .keyword("while"), - .keyword("repeat"), - ].contains($0) }) { i, token in - if let startIndex = formatter.index(of: .startOfScope("{"), after: i) { - formatter.wrapStatementBody(at: startIndex) - } else if token == .keyword("for") { - return formatter.fatalError("Expected {", at: i) - } - } - } - - /// Ensure that the last item in a multi-line array literal is followed by a comma. - /// This is useful for preventing noise in commits when items are added to end of array. - public let trailingCommas = FormatRule( - help: "Add or remove trailing comma from the last item in a collection literal.", - options: ["commas"] - ) { formatter in - formatter.forEach(.endOfScope("]")) { i, _ in - guard let prevTokenIndex = formatter.index(of: .nonSpaceOrComment, before: i), - let scopeType = formatter.scopeType(at: i) - else { - return - } - switch scopeType { - case .array, .dictionary: - switch formatter.tokens[prevTokenIndex] { - case .linebreak: - guard let prevTokenIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex + 1 - ) else { - break - } - switch formatter.tokens[prevTokenIndex] { - case .startOfScope("["), .delimiter(":"): - break // do nothing - case .delimiter(","): - if !formatter.options.trailingCommas { - formatter.removeToken(at: prevTokenIndex) - } - default: - if formatter.options.trailingCommas { - formatter.insert(.delimiter(","), at: prevTokenIndex + 1) - } - } - case .delimiter(","): - formatter.removeToken(at: prevTokenIndex) - default: - break - } - default: - return - } - } - } - - /// Ensure that TODO, MARK and FIXME comments are followed by a : as required - public let todos = FormatRule( - help: "Use correct formatting for `TODO:`, `MARK:` or `FIXME:` comments." - ) { formatter in - formatter.forEachToken { i, token in - guard case var .commentBody(string) = token else { - return - } - var removedSpace = false - if string.hasPrefix("/"), let scopeStart = formatter.index(of: .startOfScope, before: i, if: { - $0 == .startOfScope("//") - }) { - if let prevLinebreak = formatter.index(of: .linebreak, before: scopeStart), - case .commentBody? = formatter.last(.nonSpace, before: prevLinebreak) - { - return - } - if let nextLinebreak = formatter.index(of: .linebreak, after: i), - case .startOfScope("//")? = formatter.next(.nonSpace, after: nextLinebreak) - { - return - } - removedSpace = true - string = string.replacingOccurrences(of: "^/(\\s+)", with: "", options: .regularExpression) - } - for pair in [ - "todo:": "TODO:", - "todo :": "TODO:", - "fixme:": "FIXME:", - "fixme :": "FIXME:", - "mark:": "MARK:", - "mark :": "MARK:", - "mark-": "MARK: -", - "mark -": "MARK: -", - ] where string.lowercased().hasPrefix(pair.0) { - string = pair.1 + string.dropFirst(pair.0.count) - } - guard let tag = ["TODO", "MARK", "FIXME"].first(where: { string.hasPrefix($0) }) else { - return - } - var suffix = String(string[tag.endIndex ..< string.endIndex]) - if let first = suffix.unicodeScalars.first, !" :".unicodeScalars.contains(first) { - // If not followed by a space or :, don't mess with it as it may be a custom format - return - } - while let first = suffix.unicodeScalars.first, " :".unicodeScalars.contains(first) { - suffix = String(suffix.dropFirst()) - } - if tag == "MARK", suffix.hasPrefix("-"), suffix != "-", !suffix.hasPrefix("- ") { - suffix = "- " + suffix.dropFirst() - } - formatter.replaceToken(at: i, with: .commentBody(tag + ":" + (suffix.isEmpty ? "" : " \(suffix)"))) - if removedSpace { - formatter.insertSpace(" ", at: i) - } - } - } - - /// Remove semicolons, except where doing so would change the meaning of the code - public let semicolons = FormatRule( - help: "Remove semicolons.", - options: ["semicolons"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.delimiter(";")) { i, _ in - if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { - let prevTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) - let prevToken = prevTokenIndex.map { formatter.tokens[$0] } - if prevToken == nil || nextToken == .endOfScope("}") { - // Safe to remove - formatter.removeToken(at: i) - } else if prevToken == .keyword("return") || ( - formatter.options.swiftVersion < "3" && - // Might be a traditional for loop (not supported in Swift 3 and above) - formatter.currentScope(at: i) == .startOfScope("(") - ) { - // Not safe to remove or replace - } else if case .identifier? = prevToken, formatter.last( - .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex! - ) == .keyword("var") { - // Not safe to remove or replace - } else if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { - // Safe to remove - formatter.removeToken(at: i) - } else if !formatter.options.allowInlineSemicolons { - // Replace with a linebreak - if formatter.token(at: i + 1)?.isSpace == true { - formatter.removeToken(at: i + 1) - } - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) - formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) - } - } else { - // Safe to remove - formatter.removeToken(at: i) - } - } - } - - /// Standardise linebreak characters as whatever is specified in the options (\n by default) - public let linebreaks = FormatRule( - help: "Use specified linebreak character for all linebreaks (CR, LF or CRLF).", - options: ["linebreaks"] - ) { formatter in - formatter.forEach(.linebreak) { i, _ in - formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) - } - } - - /// Deprecated - public let specifiers = FormatRule( - help: "Use consistent ordering for member modifiers.", - deprecationMessage: "Use modifierOrder instead.", - options: ["modifierorder"] - ) { formatter in - _ = formatter.options.modifierOrder - FormatRules.modifierOrder.apply(with: formatter) - } - - /// Standardise the order of property modifiers - public let modifierOrder = FormatRule( - help: "Use consistent ordering for member modifiers.", - options: ["modifierorder"] - ) { formatter in - formatter.forEach(.keyword) { i, token in - switch token.string { - case "let", "func", "var", "class", "actor", "extension", "init", "enum", - "struct", "typealias", "subscript", "associatedtype", "protocol": - break - default: - return - } - var modifiers = [String: [Token]]() - var lastModifier: (name: String, tokens: [Token])? - func pushModifier() { - lastModifier.map { modifiers[$0.name] = $0.tokens } - } - var lastIndex = i - var previousIndex = lastIndex - loop: while let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lastIndex) { - switch formatter.tokens[index] { - case .operator(_, .prefix), .operator(_, .infix), .keyword("case"): - // Last modifier was invalid - lastModifier = nil - lastIndex = previousIndex - break loop - case let token where token.isModifierKeyword: - pushModifier() - lastModifier = (token.string, [Token](formatter.tokens[index ..< lastIndex])) - previousIndex = lastIndex - lastIndex = index - case .endOfScope(")"): - if case let .identifier(param)? = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), - let openParenIndex = formatter.index(of: .startOfScope("("), before: index), - let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: openParenIndex), - let token = formatter.token(at: index), token.isModifierKeyword - { - pushModifier() - let modifier = token.string + (param == "set" ? "(set)" : "") - lastModifier = (modifier, [Token](formatter.tokens[index ..< lastIndex])) - previousIndex = lastIndex - lastIndex = index - } else { - break loop - } - default: - // Not a modifier - break loop - } - } - pushModifier() - guard !modifiers.isEmpty else { return } - var sortedModifiers = [Token]() - for modifier in formatter.modifierOrder { - if let tokens = modifiers[modifier] { - sortedModifiers += tokens - } - } - formatter.replaceTokens(in: lastIndex ..< i, with: sortedModifiers) - } - } - - /// Convert closure arguments to trailing closure syntax where possible - public let trailingClosures = FormatRule( - help: "Use trailing closure syntax where applicable.", - options: ["trailingclosures", "nevertrailing"] - ) { formatter in - let useTrailing = Set([ - "async", "asyncAfter", "sync", "autoreleasepool", - ] + formatter.options.trailingClosures) - - let nonTrailing = Set([ - "performBatchUpdates", - "expect", // Special case to support autoclosure arguments in the Nimble framework - ] + formatter.options.neverTrailing) - - formatter.forEach(.startOfScope("(")) { i, _ in - guard let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - case let .identifier(name) = prevToken, // TODO: are trailing closures allowed in other cases? - !nonTrailing.contains(name), !formatter.isConditionalStatement(at: i) - else { - return - } - guard let closingIndex = formatter.index(of: .endOfScope(")"), after: i), let closingBraceIndex = - formatter.index(of: .nonSpaceOrComment, before: closingIndex, if: { $0 == .endOfScope("}") }), - let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: closingBraceIndex), - formatter.index(of: .endOfScope("}"), before: openingBraceIndex) == nil - else { - return - } - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) != .startOfScope("{"), - var startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: openingBraceIndex) - else { - return - } - switch formatter.tokens[startIndex] { - case .delimiter(","), .startOfScope("("): - break - case .delimiter(":"): - guard useTrailing.contains(name) else { - return - } - if let commaIndex = formatter.index(of: .delimiter(","), before: openingBraceIndex) { - startIndex = commaIndex - } else if formatter.index(of: .startOfScope("("), before: openingBraceIndex) == i { - startIndex = i - } else { - return - } - default: - return - } - let wasParen = (startIndex == i) - formatter.removeParen(at: closingIndex) - formatter.replaceTokens(in: startIndex ..< openingBraceIndex, with: - wasParen ? [.space(" ")] : [.endOfScope(")"), .space(" ")]) - } - } - - /// Remove redundant parens around the arguments for loops, if statements, closures, etc. - public let redundantParens = FormatRule( - help: "Remove redundant parentheses." - ) { formatter in - func nestedParens(in range: ClosedRange) -> ClosedRange? { - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: range.lowerBound, if: { - $0 == .startOfScope("(") - }), let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: range.upperBound, if: { - $0 == .endOfScope(")") - }), formatter.index(of: .endOfScope(")"), after: startIndex) == endIndex else { - return nil - } - return startIndex ... endIndex - } - - // TODO: unify with conditionals logic in trailingClosures - let conditionals = Set(["in", "while", "if", "case", "switch", "where", "for", "guard"]) - - formatter.forEach(.startOfScope("(")) { i, _ in - guard var closingIndex = formatter.index(of: .endOfScope(")"), after: i), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .keyword("repeat") - else { - return - } - var innerParens = nestedParens(in: i ... closingIndex) - while let range = innerParens, nestedParens(in: range) != nil { - // TODO: this could be a lot more efficient if we kept track of the - // removed token indices instead of recalculating paren positions every time - formatter.removeParen(at: range.upperBound) - formatter.removeParen(at: range.lowerBound) - closingIndex = formatter.index(of: .endOfScope(")"), after: i)! - innerParens = nestedParens(in: i ... closingIndex) - } - var isClosure = false - let previousIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) ?? -1 - let prevToken = formatter.token(at: previousIndex) ?? .space("") - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") - switch nextToken { - case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), - .identifier("async"), .keyword("in"): - if prevToken != .keyword("throws"), - formatter.index(before: i, where: { - [.endOfScope(")"), .operator("->", .infix), .keyword("for")].contains($0) - }) == nil, - let scopeIndex = formatter.startOfScope(at: i) - { - isClosure = formatter.isStartOfClosure(at: scopeIndex) && formatter.isInClosureArguments(at: i) - } - if !isClosure, nextToken != .keyword("in") { - return // It's a closure type, function declaration or for loop - } - case .operator: - if case let .operator(inner, _)? = formatter.last(.nonSpace, before: closingIndex), - !["?", "!"].contains(inner) - { - return - } - default: - break - } - switch prevToken { - case .stringBody, .operator("?", .postfix), .operator("!", .postfix), .operator("->", .infix): - return - case .identifier: // TODO: are trailing closures allowed in other cases? - // Parens before closure - guard closingIndex == formatter.index(of: .nonSpace, after: i), - let openingIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closingIndex, if: { - $0 == .startOfScope("{") - }), - formatter.isStartOfClosure(at: openingIndex) - else { - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - case _ where isClosure: - if formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == closingIndex || - formatter.index(of: .delimiter(":"), in: i + 1 ..< closingIndex) != nil || - formatter.tokens[i + 1 ..< closingIndex].contains(.identifier("self")) - { - return - } - if let index = formatter.tokens[i + 1 ..< closingIndex].firstIndex(of: .identifier("_")), - formatter.next(.nonSpaceOrComment, after: index)?.isIdentifier == true - { - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - case let .keyword(name) where !conditionals.contains(name) && !["let", "var", "return"].contains(name): - return - case .endOfScope("}"), .endOfScope(")"), .endOfScope("]"), .endOfScope(">"): - if formatter.tokens[previousIndex + 1 ..< i].contains(where: { $0.isLinebreak }) { - fallthrough - } - return // Probably a method invocation - case .delimiter(","), .endOfScope, .keyword: - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") - guard formatter.index(of: .endOfScope("}"), before: closingIndex) == nil, - ![.endOfScope("}"), .endOfScope(">")].contains(prevToken) || - ![.startOfScope("{"), .delimiter(",")].contains(nextToken) - else { - return - } - let string = prevToken.string - if ![.startOfScope("{"), .delimiter(","), .startOfScope(":")].contains(nextToken), - !(string == "for" && nextToken == .keyword("in")), - !(string == "guard" && nextToken == .keyword("else")) - { - // TODO: this is confusing - refactor to move fallthrough to end of case - fallthrough - } - if formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< closingIndex) == nil || - formatter.index(of: .delimiter(","), in: i + 1 ..< closingIndex) != nil - { - // Might be a tuple, so we won't remove the parens - // TODO: improve the logic here so we don't misidentify function calls as tuples - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - case .operator(_, .infix): - guard let nextIndex = formatter.index(of: .nonSpaceOrComment, after: i, if: { - $0 == .startOfScope("{") - }), let lastIndex = formatter.index(of: .endOfScope("}"), after: nextIndex), - formatter.index(of: .nonSpaceOrComment, before: closingIndex) == lastIndex else { - fallthrough - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - default: - if let range = innerParens { - formatter.removeParen(at: range.upperBound) - formatter.removeParen(at: range.lowerBound) - closingIndex = formatter.index(of: .endOfScope(")"), after: i)! - innerParens = nil - } - if prevToken == .startOfScope("("), - formatter.last(.nonSpaceOrComment, before: previousIndex) == .identifier("Selector") - { - return - } - if case .operator = formatter.tokens[closingIndex - 1], - case .operator(_, .infix)? = formatter.token(at: closingIndex + 1) - { - return - } - let nextNonLinebreak = formatter.next(.nonSpaceOrComment, after: closingIndex) - if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - case .operator = formatter.tokens[index] - { - if nextToken.isOperator(".") || (index == i + 1 && - formatter.token(at: i - 1)?.isSpaceOrCommentOrLinebreak == false) - { - return - } - switch nextNonLinebreak { - case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: - return - default: - break - } - } - guard formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) != closingIndex, - formatter.index(in: i + 1 ..< closingIndex, where: { - switch $0 { - case .operator(_, .infix), .identifier("any"), .identifier("some"), .identifier("each"), - .keyword("as"), .keyword("is"), .keyword("try"), .keyword("await"): - switch prevToken { - // TODO: add option to always strip parens in this case (or only for boolean operators?) - case .operator("=", .infix) where $0 == .operator("->", .infix): - break - case .operator(_, .prefix), .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: - break - } - switch nextToken { - case .operator(_, .postfix), .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: - break - } - switch nextNonLinebreak { - case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: - return true - default: - return false - } - case .operator(_, .postfix): - switch prevToken { - case .operator(_, .prefix), .keyword("as"), .keyword("is"): - return true - default: - return false - } - case .delimiter(","), .delimiter(":"), .delimiter(";"), - .operator(_, .none), .startOfScope("{"): - return true - default: - return false - } - }) == nil, - formatter.index(in: i + 1 ..< closingIndex, where: { $0.isUnwrapOperator }) ?? closingIndex >= - formatter.index(of: .nonSpace, before: closingIndex) ?? closingIndex - 1 - else { - return - } - if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == .keyword("#file") { - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - } - } - } - - /// Remove redundant `get {}` clause inside read-only computed property - public let redundantGet = FormatRule( - help: "Remove unneeded `get` clause inside computed properties." - ) { formatter in - formatter.forEach(.identifier("get")) { i, _ in - if formatter.isAccessorKeyword(at: i, checkKeyword: false), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .startOfScope("{") - }), let openIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .startOfScope("{") - }), - let closeIndex = formatter.index(of: .endOfScope("}"), after: openIndex), - let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closeIndex, if: { - $0 == .endOfScope("}") - }) - { - formatter.removeTokens(in: closeIndex ..< nextIndex) - formatter.removeTokens(in: prevIndex + 1 ... openIndex) - // TODO: fix-up indenting of lines in between removed braces - } - } - } - - /// Remove or insert redundant `= nil` initialization for Optional properties - public let redundantNilInit = FormatRule( - help: "Remove/insert redundant `nil` default value (Optional vars are nil by default).", - options: ["nilinit"] - ) { formatter in - func search(from index: Int, isStoredProperty: Bool) { - if let optionalIndex = formatter.index(of: .unwrapOperator, after: index) { - if formatter.index(of: .endOfStatement, in: index + 1 ..< optionalIndex) != nil { - return - } - let previousToken = formatter.tokens[optionalIndex - 1] - if !previousToken.isSpaceOrCommentOrLinebreak && previousToken != .keyword("as") { - let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: optionalIndex, if: { - $0 == .operator("=", .infix) - }) - switch formatter.options.nilInit { - case .remove: - if let equalsIndex = equalsIndex, let nilIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { - $0 == .identifier("nil") - }) { - formatter.removeTokens(in: optionalIndex + 1 ... nilIndex) - } - case .insert: - if isStoredProperty && equalsIndex == nil { - let tokens: [Token] = [.space(" "), .operator("=", .infix), .space(" "), .identifier("nil")] - formatter.insert(tokens, at: optionalIndex + 1) - } - } - } - search(from: optionalIndex, isStoredProperty: isStoredProperty) - } - } - - // Check modifiers don't include `lazy` - formatter.forEach(.keyword("var")) { i, _ in - if formatter.modifiersForDeclaration(at: i, contains: { - $1 == "lazy" || ($1 != "@objc" && $1.hasPrefix("@")) - }) || formatter.isInResultBuilder(at: i) { - return // Can't remove the init - } - // Check this isn't a Codable - if let scopeIndex = formatter.index(of: .startOfScope("{"), before: i) { - var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: scopeIndex) - loop: while let index = prevIndex { - switch formatter.tokens[index] { - case .identifier("Codable"), .identifier("Decodable"): - return // Can't safely remove the default value - case .keyword("struct") where formatter.options.swiftVersion < "5.2": - if formatter.index(of: .keyword("init"), after: scopeIndex) == nil { - return // Can't safely remove the default value - } - break loop - case .keyword: - break loop - default: - break - } - prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) - } - } - // Find the nil - search(from: i, isStoredProperty: formatter.isStoredProperty(atIntroducerIndex: i)) - } - } - - /// Remove redundant let/var for unnamed variables - public let redundantLet = FormatRule( - help: "Remove redundant `let`/`var` from ignored variables." - ) { formatter in - formatter.forEach(.identifier("_")) { i, _ in - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":"), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - [.keyword("let"), .keyword("var")].contains($0) - }), - let nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, after: prevIndex) - else { - return - } - if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: prevIndex) { - switch prevToken { - case .keyword("if"), .keyword("guard"), .keyword("while"), .identifier("async"), - .keyword where prevToken.isAttribute, - .delimiter(",") where formatter.currentScope(at: i) != .startOfScope("("): - return - default: - break - } - } - // Crude check for Result Builder - if formatter.isInResultBuilder(at: i) { - return - } - formatter.removeTokens(in: prevIndex ..< nextNonSpaceIndex) - } - } - - /// Remove redundant pattern in case statements - public let redundantPattern = FormatRule( - help: "Remove redundant pattern matching parameter syntax." - ) { formatter in - func redundantBindings(in range: Range) -> Bool { - var isEmpty = true - for token in formatter.tokens[range.lowerBound ..< range.upperBound] { - switch token { - case .identifier("_"): - isEmpty = false - case .space, .linebreak, .delimiter(","), .keyword("let"), .keyword("var"): - break - default: - return false - } - } - return !isEmpty - } - - formatter.forEach(.startOfScope("(")) { i, _ in - let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) - if let prevIndex = prevIndex, let prevToken = formatter.token(at: prevIndex), - [.keyword("case"), .endOfScope("case")].contains(prevToken) - { - // Not safe to remove - return - } - guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex), - [.startOfScope(":"), .operator("=", .infix)].contains(nextToken), - redundantBindings(in: i + 1 ..< endIndex) - else { - return - } - formatter.removeTokens(in: i ... endIndex) - if let prevIndex = prevIndex, formatter.tokens[prevIndex].isIdentifier, - formatter.last(.nonSpaceOrComment, before: prevIndex)?.string == "." - { - if let endOfScopeIndex = formatter.index( - before: prevIndex, - where: { tkn in tkn == .endOfScope("case") || tkn == .keyword("case") } - ), - let varOrLetIndex = formatter.index(after: endOfScopeIndex, where: { tkn in - tkn == .keyword("let") || tkn == .keyword("var") - }), - let operatorIndex = formatter.index(of: .operator, before: prevIndex), - varOrLetIndex < operatorIndex - { - formatter.removeTokens(in: varOrLetIndex ..< operatorIndex) - } - return - } - - // Was an assignment - formatter.insert(.identifier("_"), at: i) - if formatter.token(at: i - 1).map({ $0.isSpaceOrLinebreak }) != true { - formatter.insert(.space(" "), at: i) - } - } - } - - /// Remove redundant raw string values for case statements - public let redundantRawValues = FormatRule( - help: "Remove redundant raw string values for enum cases." - ) { formatter in - formatter.forEach(.keyword("enum")) { i, _ in - guard let nameIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, after: i, if: { $0.isIdentifier } - ), let colonIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { $0 == .delimiter(":") } - ), formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) == .identifier("String"), - let braceIndex = formatter.index(of: .startOfScope("{"), after: colonIndex) else { - return - } - var lastIndex = formatter.index(of: .keyword("case"), after: braceIndex) - while var index = lastIndex { - guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - $0.isIdentifier - }) else { break } - if let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: nameIndex, if: { - $0 == .operator("=", .infix) - }), let quoteIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { - $0 == .startOfScope("\"") - }), formatter.token(at: quoteIndex + 2) == .endOfScope("\"") { - if formatter.tokens[nameIndex].unescaped() == formatter.token(at: quoteIndex + 1)?.string { - formatter.removeTokens(in: nameIndex + 1 ... quoteIndex + 2) - index = nameIndex - } else { - index = quoteIndex + 2 - } - } else { - index = nameIndex - } - lastIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - $0 == .delimiter(",") - }) ?? formatter.index(of: .keyword("case"), after: index) - } - } - } - - /// Remove redundant void return values for function and closure declarations - public let redundantVoidReturnType = FormatRule( - help: "Remove explicit `Void` return type.", - options: ["closurevoid"] - ) { formatter in - formatter.forEach(.operator("->", .infix)) { i, _ in - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - let endIndex = formatter.endOfVoidType(at: startIndex) - else { - return - } - - // If this is the explicit return type of a closure, it should - // always be safe to remove - if formatter.options.closureVoidReturn == .remove, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .keyword("in") - { - formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) - return - } - - guard let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) else { return } - - let isInProtocol = nextToken == .endOfScope("}") || (nextToken.isKeywordOrAttribute && nextToken != .keyword("in")) - - // After a `Void` we could see the start of a function's body, or if the function is inside a protocol declaration - // we can find a keyword related to other declarations or the end scope of the protocol definition. - guard nextToken == .startOfScope("{") || isInProtocol else { return } - - guard let prevIndex = formatter.index(of: .endOfScope(")"), before: i), - let parenIndex = formatter.index(of: .startOfScope("("), before: prevIndex), - let startToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: parenIndex), - startToken.isIdentifier || [.startOfScope("{"), .endOfScope("]")].contains(startToken) - else { - return - } - - let startRemoveIndex: Int - if isInProtocol, formatter.token(at: i - 1)?.isSpace == true { - startRemoveIndex = i - 1 - } else { - startRemoveIndex = i - } - formatter.removeTokens(in: startRemoveIndex ..< formatter.index(of: .nonSpace, after: endIndex)!) - } - } - - /// Remove redundant return keyword - public let redundantReturn = FormatRule( - help: "Remove unneeded `return` keyword." - ) { formatter in - // indices of returns that are safe to remove - var returnIndices = [Int]() - - // Also handle redundant void returns in void functions, which can always be removed. - // - The following code is the original implementation of the `redundantReturn` rule - // and is partially redundant with the below code so could be simplified in the future. - formatter.forEach(.keyword("return")) { i, _ in - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) else { - return - } - defer { - // Check return wasn't removed already - if formatter.token(at: i) == .keyword("return") { - returnIndices.append(i) - } - } - switch formatter.tokens[startIndex] { - case .keyword("in"): - break - case .startOfScope("{"): - guard var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex) else { - break - } - if formatter.options.swiftVersion < "5.1", formatter.isAccessorKeyword(at: prevIndex) { - return - } - if formatter.tokens[prevIndex] == .endOfScope(")"), - let j = formatter.index(of: .startOfScope("("), before: prevIndex) - { - prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: j) ?? j - if formatter.tokens[prevIndex] == .operator("?", .postfix) { - prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) ?? prevIndex - } - let prevToken = formatter.tokens[prevIndex] - guard prevToken.isIdentifier || prevToken == .keyword("init") else { - return - } - } - let prevToken = formatter.tokens[prevIndex] - guard ![.delimiter(":"), .startOfScope("(")].contains(prevToken), - var prevKeywordIndex = formatter.indexOfLastSignificantKeyword( - at: startIndex, excluding: ["where"] - ) - else { - break - } - switch formatter.tokens[prevKeywordIndex].string { - case "let", "var": - guard formatter.options.swiftVersion >= "5.1" || prevToken == .operator("=", .infix) || - formatter.lastIndex(of: .operator("=", .infix), in: prevKeywordIndex + 1 ..< prevIndex) != nil, - !formatter.isConditionalStatement(at: prevKeywordIndex) - else { - return - } - case "func", "throws", "rethrows", "init", "subscript": - if formatter.options.swiftVersion < "5.1", - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .endOfScope("}") - { - return - } - default: - return - } - default: - guard let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .endOfScope("}") - }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { - return - } - if !formatter.isStartOfClosure(at: startIndex), !["func", "throws", "rethrows"] - .contains(formatter.lastSignificantKeyword(at: startIndex, excluding: ["where"]) ?? "") - { - return - } - } - // Don't remove return if it's followed by more code - guard let endIndex = formatter.endOfScope(at: i), - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == endIndex - else { - return - } - if formatter.index(of: .nonSpaceOrLinebreak, after: i) == endIndex, - let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) - { - formatter.removeTokens(in: startIndex + 1 ... i) - return - } - formatter.removeToken(at: i) - if var nextIndex = formatter.index(of: .nonSpace, after: i - 1, if: { $0.isLinebreak }) { - if let i = formatter.index(of: .nonSpaceOrLinebreak, after: nextIndex) { - nextIndex = i - 1 - } - formatter.removeTokens(in: i ... nextIndex) - } else if formatter.token(at: i)?.isSpace == true { - formatter.removeToken(at: i) - } - } - - // Explicit returns are redundant in closures, functions, etc with a single statement body - formatter.forEach(.startOfScope("{")) { startOfScopeIndex, _ in - // Closures always supported implicit returns, but other types of scopes - // only support implicit return in Swift 5.1+ (SE-0255) - let isClosure = formatter.isStartOfClosure(at: startOfScopeIndex) - if formatter.options.swiftVersion < "5.1", !isClosure { - return - } - - // Make sure this is a type of scope that supports implicit returns - let lastKeyword = isClosure ? "" : formatter.lastSignificantKeyword( - at: startOfScopeIndex, - excluding: ["throws", "where"] - ) - if !isClosure, formatter.isConditionalStatement(at: startOfScopeIndex, excluding: ["where"]) || - ["do", "else", "catch"].contains(lastKeyword) - { - return - } - - // Only strip return from conditional block if conditionalAssignment rule is enabled - var stripConditionalReturn = formatter.options.enabledRules.contains("conditionalAssignment") - - // Don't strip return if type is opaque - // (https://github.com/nicklockwood/SwiftFormat/issues/1819) - if stripConditionalReturn, - lastKeyword == "func", - let arrowIndex = formatter.index(of: .operator("->", .infix), before: startOfScopeIndex), - formatter.tokens[arrowIndex ..< startOfScopeIndex].contains(.identifier("some")) - { - stripConditionalReturn = false - } - - // Make sure the body only has a single statement - guard formatter.blockBodyHasSingleStatement( - atStartOfScope: startOfScopeIndex, - includingConditionalStatements: true, - includingReturnStatements: true, - includingReturnInConditionalStatements: stripConditionalReturn - ) else { - return - } - - // Make sure we aren't in a failable `init?`, where explicit return is required unless it's the only statement - if !isClosure, let lastSignificantKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScopeIndex), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex) != .keyword("return"), - formatter.tokens[lastSignificantKeywordIndex] == .keyword("init"), - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lastSignificantKeywordIndex), - nextToken == .operator("?", .postfix) - { - return - } - - // Find all of the return keywords to remove before we remove any of them, - // so we can apply additional validation first. - var returnKeywordRangesToRemove = [Range]() - var hasReturnThatCantBeRemoved = false - - /// Finds the return keywords to remove and stores them in `returnKeywordRangesToRemove` - func removeReturn(atStartOfScope startOfScopeIndex: Int) { - // If this scope is a single-statement if or switch statement then we have to recursively - // remove the return from each branch of the if statement - let startOfBody = formatter.startOfBody(atStartOfScope: startOfScopeIndex) - - if let firstTokenInBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), - let conditionalBranches = formatter.conditionalBranches(at: firstTokenInBody) - { - for branch in conditionalBranches.reversed() { - // In Swift 5.9, there's a bug that prevents you from writing an - // if or switch expression using an `as?` on one of the branches: - // https://github.com/apple/swift/issues/68764 - // - // if condition { - // foo as? String - // } else { - // "bar" - // } - // - if formatter.conditionalBranchHasUnsupportedCastOperator( - startOfScopeIndex: branch.startOfBranch) - { - hasReturnThatCantBeRemoved = true - return - } - - removeReturn(atStartOfScope: branch.startOfBranch) - } - } - - // Otherwise this is a simple case with a single return at the start of the scope - else if let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex), - let returnIndex = formatter.index(of: .keyword("return"), after: startOfScopeIndex), - returnIndices.contains(returnIndex), - returnIndex < endOfScopeIndex, - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: returnIndex), - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: returnIndex)! < endOfScopeIndex - { - let range = returnIndex ..< nextIndex - for (i, index) in returnIndices.enumerated().reversed() { - if range.contains(index) { - returnIndices.remove(at: i) - } else if index > returnIndex { - returnIndices[i] -= range.count - } - } - returnKeywordRangesToRemove.append(range) - } - } - - removeReturn(atStartOfScope: startOfScopeIndex) - - guard !hasReturnThatCantBeRemoved else { return } - - for returnKeywordRangeToRemove in returnKeywordRangesToRemove.sorted(by: { $0.startIndex > $1.startIndex }) { - formatter.removeTokens(in: returnKeywordRangeToRemove) - } - } - } - - /// Remove redundant backticks around non-keywords, or in places where keywords don't need escaping - public let redundantBackticks = FormatRule( - help: "Remove redundant backticks around identifiers." - ) { formatter in - formatter.forEach(.identifier) { i, token in - guard token.string.first == "`", !formatter.backticksRequired(at: i) else { - return - } - formatter.replaceToken(at: i, with: .identifier(token.unescaped())) - } - } - - /// Remove redundant Self keyword - public let redundantStaticSelf = FormatRule( - help: "Remove explicit `Self` where applicable." - ) { formatter in - formatter.addOrRemoveSelf(static: true) - } - - /// Insert or remove redundant self keyword - public let redundantSelf = FormatRule( - help: "Insert/remove explicit `self` where applicable.", - options: ["self", "selfrequired"] - ) { formatter in - _ = formatter.options.selfRequired - _ = formatter.options.explicitSelf - formatter.addOrRemoveSelf(static: false) - } - - /// Replace unused arguments with an underscore - public let unusedArguments = FormatRule( - help: "Mark unused function arguments with `_`.", - options: ["stripunusedargs"] - ) { formatter in - guard !formatter.options.fragment else { return } - - func removeUsed(from argNames: inout [String], with associatedData: inout [T], - locals: Set = [], in range: CountableRange) - { - var isDeclaration = false - var wasDeclaration = false - var isConditional = false - var isGuard = false - var locals = locals - var tempLocals = Set() - func pushLocals() { - if isDeclaration, isConditional { - for name in tempLocals { - if let index = argNames.firstIndex(of: name), - !locals.contains(name) - { - argNames.remove(at: index) - associatedData.remove(at: index) - } - } - } - wasDeclaration = isDeclaration - isDeclaration = false - locals.formUnion(tempLocals) - tempLocals.removeAll() - } - var i = range.lowerBound - while i < range.upperBound { - if formatter.isStartOfStatement(at: i, treatingCollectionKeysAsStart: false), - // Immediately following an `=` operator, if or switch keywords - // are expressions rather than statements. - formatter.lastToken(before: i, where: { !$0.isSpaceOrCommentOrLinebreak })?.isOperator("=") != true - { - pushLocals() - wasDeclaration = false - } - let token = formatter.tokens[i] - outer: switch token { - case .keyword("guard"): - isGuard = true - case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): - isDeclaration = true - var i = i - while let scopeStart = formatter.index(of: .startOfScope("("), before: i) { - i = scopeStart - } - isConditional = formatter.isConditionalStatement(at: i) - case .identifier: - let name = token.unescaped() - guard let index = argNames.firstIndex(of: name), !locals.contains(name) else { - break - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(".") == false, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":") || - [.startOfScope("("), .startOfScope("[")].contains(formatter.currentScope(at: i) ?? .space("")) - { - if isDeclaration { - switch formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { - case .endOfScope(")")?, .operator("=", .infix)?, - .delimiter(",")? where !isConditional: - tempLocals.insert(name) - break outer - default: - break - } - } - argNames.remove(at: index) - associatedData.remove(at: index) - if argNames.isEmpty { - return - } - } - case .keyword("if"), .keyword("switch"): - guard formatter.isConditionalAssignment(at: i), - let conditinalBranches = formatter.conditionalBranches(at: i), - let endIndex = conditinalBranches.last?.endOfBranch - else { fallthrough } - - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - case .startOfScope("{"): - guard let endIndex = formatter.endOfScope(at: i) else { - return formatter.fatalError("Expected }", at: i) - } - if formatter.isStartOfClosure(at: i) { - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - } else if isGuard { - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - pushLocals() - } else { - let prevLocals = locals - pushLocals() - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - locals = prevLocals - } - - isGuard = false - i = endIndex - case .endOfScope("case"), .endOfScope("default"): - pushLocals() - guard let colonIndex = formatter.index(of: .startOfScope(":"), after: i) else { - return formatter.fatalError("Expected :", at: i) - } - guard let endIndex = formatter.endOfScope(at: colonIndex) else { - return formatter.fatalError("Expected end of case statement", - at: colonIndex) - } - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - i = endIndex - 1 - case .operator("=", .infix), .delimiter(":"), .startOfScope(":"), - .keyword("in"), .keyword("where"): - wasDeclaration = isDeclaration - isDeclaration = false - case .delimiter(","): - if let scope = formatter.currentScope(at: i), [ - .startOfScope("("), .startOfScope("["), .startOfScope("<"), - ].contains(scope) { - break - } - if isConditional { - if isGuard, wasDeclaration { - pushLocals() - } - wasDeclaration = false - } else { - let _wasDeclaration = wasDeclaration - pushLocals() - isDeclaration = _wasDeclaration - } - case .delimiter(";"): - pushLocals() - wasDeclaration = false - default: - break - } - i += 1 - } - } - // Closure arguments - formatter.forEach(.keyword("in")) { i, _ in - var argNames = [String]() - var nameIndexPairs = [(Int, Int)]() - guard let start = formatter.index(of: .startOfScope("{"), before: i) else { - return - } - var index = i - 1 - var argCountStack = [0] - while index > start { - let token = formatter.tokens[index] - switch token { - case .endOfScope("}"): - return - case .endOfScope("]"): - // TODO: handle unused capture list arguments - index = formatter.index(of: .startOfScope("["), before: index) ?? index - case .endOfScope(")"): - argCountStack.append(argNames.count) - case .startOfScope("("): - argCountStack.removeLast() - case .delimiter(","): - argCountStack[argCountStack.count - 1] = argNames.count - case .identifier("async") where - formatter.last(.nonSpaceOrLinebreak, before: index)?.isIdentifier == true: - fallthrough - case .operator("->", .infix), .keyword("throws"): - // Everything after this was part of return value - let count = argCountStack.last ?? 0 - argNames.removeSubrange(count ..< argNames.count) - nameIndexPairs.removeSubrange(count ..< nameIndexPairs.count) - case let .keyword(name) where - !token.isAttribute && !name.hasPrefix("#") && name != "inout": - return - case .identifier: - guard argCountStack.count < 3, - let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), [ - .delimiter(","), .startOfScope("("), .startOfScope("{"), .endOfScope("]"), - ].contains(prevToken), let scopeStart = formatter.index(of: .startOfScope, before: index), - ![.startOfScope("["), .startOfScope("<")].contains(formatter.tokens[scopeStart]) - else { - break - } - let name = token.unescaped() - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index), - let nextToken = formatter.token(at: nextIndex), case .identifier = nextToken, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .delimiter(":") - { - let internalName = nextToken.unescaped() - if internalName != "_" { - argNames.append(internalName) - nameIndexPairs.append((index, nextIndex)) - } - } else if name != "_" { - argNames.append(name) - nameIndexPairs.append((index, index)) - } - default: - break - } - index -= 1 - } - guard !argNames.isEmpty, let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: i) else { - return - } - removeUsed(from: &argNames, with: &nameIndexPairs, in: i + 1 ..< bodyEndIndex) - for pair in nameIndexPairs { - if case .identifier("_") = formatter.tokens[pair.0], pair.0 != pair.1 { - formatter.removeToken(at: pair.1) - if formatter.tokens[pair.1 - 1] == .space(" ") { - formatter.removeToken(at: pair.1 - 1) - } - } else { - formatter.replaceToken(at: pair.1, with: .identifier("_")) - } - } - } - // Function arguments - formatter.forEachToken { i, token in - guard formatter.options.stripUnusedArguments != .closureOnly, - case let .keyword(keyword) = token, ["func", "init", "subscript"].contains(keyword), - let startIndex = formatter.index(of: .startOfScope("("), after: i), - let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) else { return } - let isOperator = (keyword == "subscript") || - (keyword == "func" && formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isOperator == true) - var index = startIndex - var argNames = [String]() - var nameIndexPairs = [(Int, Int)]() - while index < endIndex { - guard let externalNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - if case let .identifier(name) = $0 { - return formatter.options.stripUnusedArguments != .unnamedOnly || name == "_" - } - // Probably an empty argument list - return false - }) else { return } - guard let nextIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: externalNameIndex) else { return } - let nextToken = formatter.tokens[nextIndex] - switch nextToken { - case let .identifier(name): - if name != "_" { - argNames.append(nextToken.unescaped()) - nameIndexPairs.append((externalNameIndex, nextIndex)) - } - case .delimiter(":"): - let externalNameToken = formatter.tokens[externalNameIndex] - if case let .identifier(name) = externalNameToken, name != "_" { - argNames.append(externalNameToken.unescaped()) - nameIndexPairs.append((externalNameIndex, externalNameIndex)) - } - default: - return - } - index = formatter.index(of: .delimiter(","), after: index) ?? endIndex - } - guard !argNames.isEmpty, let bodyStartIndex = formatter.index(after: endIndex, where: { - switch $0 { - case .startOfScope("{"): // What we're looking for - return true - case .keyword("throws"), - .keyword("rethrows"), - .identifier("async"), - .keyword("where"), - .keyword("is"): - return false // Keep looking - case .keyword: - return true // Not valid between end of arguments and start of body - default: - return false // Keep looking - } - }), formatter.tokens[bodyStartIndex] == .startOfScope("{"), - let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: bodyStartIndex) else { - return - } - removeUsed(from: &argNames, with: &nameIndexPairs, in: bodyStartIndex + 1 ..< bodyEndIndex) - for pair in nameIndexPairs.reversed() { - if pair.0 == pair.1 { - if isOperator { - formatter.replaceToken(at: pair.0, with: .identifier("_")) - } else { - formatter.insert(.identifier("_"), at: pair.0 + 1) - formatter.insert(.space(" "), at: pair.0 + 1) - } - } else if case .identifier("_") = formatter.tokens[pair.0] { - formatter.removeToken(at: pair.1) - if formatter.tokens[pair.1 - 1] == .space(" ") { - formatter.removeToken(at: pair.1 - 1) - } - } else { - formatter.replaceToken(at: pair.1, with: .identifier("_")) - } - } - } - } - - public let hoistTry = FormatRule( - help: "Move inline `try` keyword(s) to start of expression.", - options: ["throwcapturing"] - ) { formatter in - let names = formatter.options.throwCapturing.union(["expect"]) - formatter.forEachToken(where: { - $0 == .startOfScope("(") || $0 == .startOfScope("[") - }) { i, _ in - formatter.hoistEffectKeyword("try", inScopeAt: i) { prevIndex in - guard case let .identifier(name) = formatter.tokens[prevIndex] else { - return false - } - return name.hasPrefix("XCTAssert") || formatter.isSymbol(at: prevIndex, in: names) - } - } - } - - /// Reposition `await` keyword outside of the current scope. - public let hoistAwait = FormatRule( - help: "Move inline `await` keyword(s) to start of expression.", - options: ["asynccapturing"] - ) { formatter in - guard formatter.options.swiftVersion >= "5.5" else { return } - - formatter.forEachToken(where: { - $0 == .startOfScope("(") || $0 == .startOfScope("[") - }) { i, _ in - formatter.hoistEffectKeyword("await", inScopeAt: i) { prevIndex in - formatter.isSymbol(at: prevIndex, in: formatter.options.asyncCapturing) - } - } - } - - /// Move `let` and `var` inside patterns to the beginning - public let hoistPatternLet = FormatRule( - help: "Reposition `let` or `var` bindings within pattern.", - options: ["patternlet"] - ) { formatter in - func indicesOf(_ keyword: String, in range: CountableRange) -> [Int]? { - var indices = [Int]() - var keywordFound = false, identifierFound = false - var count = 0 - for index in range { - switch formatter.tokens[index] { - case .keyword(keyword): - indices.append(index) - keywordFound = true - case .identifier("_"): - break - case .identifier where formatter.last(.nonSpaceOrComment, before: index) != .operator(".", .prefix): - identifierFound = true - if keywordFound { - count += 1 - } - case .delimiter(","): - guard keywordFound || !identifierFound else { - return nil - } - keywordFound = false - identifierFound = false - case .startOfScope("{"): - return nil - case .startOfScope("<"): - // See: https://github.com/nicklockwood/SwiftFormat/issues/768 - return nil - default: - break - } - } - return (keywordFound || !identifierFound) && count > 0 ? indices : nil - } - - formatter.forEach(.startOfScope("(")) { i, _ in - let hoist = formatter.options.hoistPatternLet - // Check if pattern already starts with let/var - guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), - let prevIndex = formatter.index(before: i, where: { - switch $0 { - case .operator(".", _), .keyword("let"), .keyword("var"), - .endOfScope("*/"): - return false - case .endOfScope, .delimiter, .operator, .keyword: - return true - default: - return false - } - }) - else { - return - } - switch formatter.tokens[prevIndex] { - case .endOfScope("case"), .keyword("case"), .keyword("catch"): - break - case .delimiter(","): - loop: for token in formatter.tokens[0 ..< prevIndex].reversed() { - switch token { - case .endOfScope("case"), .keyword("catch"): - break loop - case .keyword("var"), .keyword("let"): - break - case .keyword: - // Tuple assignment - return - default: - break - } - } - default: - return - } - let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: prevIndex) - ?? (prevIndex + 1) - if case let .keyword(keyword) = formatter.tokens[startIndex], - ["let", "var"].contains(keyword) - { - if hoist { - // No changes needed - return - } - // Find variable indices - var indices = [Int]() - var index = i + 1 - var wasParenOrCommaOrLabel = true - while index < endIndex { - let token = formatter.tokens[index] - switch token { - case .delimiter(","), .startOfScope("("), .delimiter(":"): - wasParenOrCommaOrLabel = true - case .identifier("_"), .identifier("true"), .identifier("false"), .identifier("nil"): - wasParenOrCommaOrLabel = false - case let .identifier(name) where wasParenOrCommaOrLabel: - wasParenOrCommaOrLabel = false - let next = formatter.next(.nonSpaceOrComment, after: index) - if next != .operator(".", .infix), next != .delimiter(":") { - indices.append(index) - } - case _ where token.isSpaceOrCommentOrLinebreak: - break - case .startOfScope("["): - guard let next = formatter.endOfScope(at: index) else { - return formatter.fatalError("Expected ]", at: index) - } - index = next - default: - wasParenOrCommaOrLabel = false - } - index += 1 - } - // Insert keyword at indices - for index in indices.reversed() { - formatter.insert([.keyword(keyword), .space(" ")], at: index) - } - // Remove keyword - let range = ((formatter.index(of: .nonSpace, before: startIndex) ?? - (prevIndex - 1)) + 1) ... startIndex - formatter.removeTokens(in: range) - } else if hoist { - // Find let/var keyword indices - var keyword = "let" - guard let indices: [Int] = { - guard let indices = indicesOf(keyword, in: i + 1 ..< endIndex) else { - keyword = "var" - return indicesOf(keyword, in: i + 1 ..< endIndex) - } - return indices - }() else { - return - } - // Remove keywords inside parens - for index in indices.reversed() { - if formatter.tokens[index + 1].isSpace { - formatter.removeToken(at: index + 1) - } - formatter.removeToken(at: index) - } - // Insert keyword before parens - formatter.insert(.keyword(keyword), at: startIndex) - if let nextToken = formatter.token(at: startIndex + 1), !nextToken.isSpaceOrLinebreak { - formatter.insert(.space(" "), at: startIndex + 1) - } - if let prevToken = formatter.token(at: startIndex - 1), - !prevToken.isSpaceOrCommentOrLinebreak, !prevToken.isStartOfScope - { - formatter.insert(.space(" "), at: startIndex) - } - } - } - } - - public let wrap = FormatRule( - help: "Wrap lines that exceed the specified maximum width.", - options: ["maxwidth", "nowrapoperators", "assetliterals", "wrapternary"], - sharedOptions: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", "indent", - "trimwhitespace", "linebreaks", "tabwidth", "maxwidth", "smarttabs", "wrapreturntype", - "wrapconditions", "wraptypealiases", "wrapternary", "wrapeffects"] - ) { formatter in - let maxWidth = formatter.options.maxWidth - guard maxWidth > 0 else { return } - - // Wrap collections first to avoid conflict - formatter.wrapCollectionsAndArguments(completePartialWrapping: false, - wrapSingleArguments: false) - - // Wrap other line types - var currentIndex = 0 - var indent = "" - var alreadyLinewrapped = false - - func isLinewrapToken(_ token: Token?) -> Bool { - switch token { - case .delimiter?, .operator(_, .infix)?: - return true - default: - return false - } - } - - formatter.forEachToken(onlyWhereEnabled: false) { i, token in - if i < currentIndex { - return - } - if token.isLinebreak { - indent = formatter.currentIndentForLine(at: i + 1) - alreadyLinewrapped = isLinewrapToken(formatter.last(.nonSpaceOrComment, before: i)) - currentIndex = i + 1 - } else if let breakPoint = formatter.indexWhereLineShouldWrapInLine(at: i) { - if !alreadyLinewrapped { - indent += formatter.linewrapIndent(at: breakPoint) - } - alreadyLinewrapped = true - if formatter.isEnabled { - let spaceAdded = formatter.insertSpace(indent, at: breakPoint + 1) - formatter.insertLinebreak(at: breakPoint + 1) - currentIndex = breakPoint + spaceAdded + 2 - } else { - currentIndex = breakPoint + 1 - } - } else { - currentIndex = formatter.endOfLine(at: i) - } - } - - formatter.wrapCollectionsAndArguments(completePartialWrapping: true, - wrapSingleArguments: true) - } - - /// Normalize argument wrapping style - public let wrapArguments = FormatRule( - help: "Align wrapped function arguments or collection elements.", - orderAfter: ["wrap"], - options: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", - "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects"], - sharedOptions: ["indent", "trimwhitespace", "linebreaks", - "tabwidth", "maxwidth", "smarttabs", "assetliterals", "wrapternary"] - ) { formatter in - formatter.wrapCollectionsAndArguments(completePartialWrapping: true, - wrapSingleArguments: false) - } - - public let wrapMultilineStatementBraces = FormatRule( - help: "Wrap the opening brace of multiline statements.", - orderAfter: ["braces", "indent", "wrapArguments"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard formatter.last(.nonSpaceOrComment, before: i)?.isLinebreak == false, - formatter.shouldWrapMultilineStatementBrace(at: i), - let endIndex = formatter.endOfScope(at: i) - else { - return - } - let indent = formatter.currentIndentForLine(at: endIndex) - // Insert linebreak - formatter.insertLinebreak(at: i) - // Align the opening brace with closing brace - formatter.insertSpace(indent, at: i + 1) - // Clean up trailing space on the previous line - if case .space? = formatter.token(at: i - 1) { - formatter.removeToken(at: i - 1) - } - } - } - - /// Formats enum cases declaration into one case per line - public let wrapEnumCases = FormatRule( - help: "Rewrite comma-delimited enum cases to one case per line.", - disabledByDefault: true, - options: ["wrapenumcases"], - sharedOptions: ["linebreaks"] - ) { formatter in - - func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { - guard let firstIndex = caseRangeGroup.first?.value.lowerBound, - let scopeStart = formatter.startOfScope(at: firstIndex), - formatter.tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) - else { - // Don't wrap if first case is on same line as opening `{` - return false - } - return formatter.options.wrapEnumCases == .always || caseRangeGroup.contains(where: { - formatter.tokens[$0.value].contains(where: { - [.startOfScope("("), .operator("=", .infix)].contains($0) - }) - }) - } - - formatter.parseEnumCaseRanges() - .filter(shouldWrapCaseRangeGroup) - .flatMap { $0 } - .filter { $0.endOfCaseRangeToken == .delimiter(",") } - .reversed() - .forEach { enumCase in - guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: enumCase.value.upperBound) else { - return - } - let caseIndex = formatter.lastIndex(of: .keyword("case"), in: 0 ..< enumCase.value.lowerBound) - let indent = formatter.currentIndentForLine(at: caseIndex ?? enumCase.value.lowerBound) - - if formatter.tokens[nextNonSpaceIndex] == .startOfScope("//") { - formatter.removeToken(at: enumCase.value.upperBound) - if formatter.token(at: enumCase.value.upperBound)?.isSpace == true, - formatter.token(at: enumCase.value.upperBound - 1)?.isSpace == true - { - formatter.removeToken(at: enumCase.value.upperBound - 1) - } - nextNonSpaceIndex = formatter.index(of: .linebreak, after: enumCase.value.upperBound) ?? nextNonSpaceIndex - } else { - formatter.removeTokens(in: enumCase.value.upperBound ..< nextNonSpaceIndex) - nextNonSpaceIndex = enumCase.value.upperBound - } - - if !formatter.tokens[nextNonSpaceIndex].isLinebreak { - formatter.insertLinebreak(at: nextNonSpaceIndex) - } - - let offset = indent.isEmpty ? 0 : 1 - formatter.insertSpace(indent, at: nextNonSpaceIndex + 1) - formatter.insert([.keyword("case")], at: nextNonSpaceIndex + 1 + offset) - formatter.insertSpace(" ", at: nextNonSpaceIndex + 2 + offset) - } - } - - /// Wrap single-line comments that exceed given `FormatOptions.maxWidth` setting. - public let wrapSingleLineComments = FormatRule( - help: "Wrap single line `//` comments that exceed the specified `--maxwidth`.", - sharedOptions: ["maxwidth", "indent", "tabwidth", "assetliterals", "linebreaks"] - ) { formatter in - let delimiterLength = "//".count - var maxWidth = formatter.options.maxWidth - guard maxWidth > 3 else { - return - } - - formatter.forEach(.startOfScope("//")) { i, _ in - let startOfLine = formatter.startOfLine(at: i) - let endOfLine = formatter.endOfLine(at: i) - guard formatter.lineLength(from: startOfLine, upTo: endOfLine) > maxWidth else { - return - } - - guard let startIndex = formatter.index(of: .nonSpace, after: i), - case var .commentBody(comment) = formatter.tokens[startIndex], - !comment.isCommentDirective - else { - return - } - - var words = comment.components(separatedBy: " ") - comment = words.removeFirst() - let commentPrefix = comment == "/" ? "/ " : comment.hasPrefix("/") ? "/" : "" - let prefixLength = formatter.lineLength(upTo: startIndex) - var length = prefixLength + comment.count - while length <= maxWidth, let next = words.first, - length + next.count < maxWidth || - // Don't wrap if next word won't fit on a line by itself anyway - prefixLength + commentPrefix.count + next.count > maxWidth - { - comment += " \(next)" - length += next.count + 1 - words.removeFirst() - } - if words.isEmpty || comment == commentPrefix { - return - } - var prefix = formatter.tokens[i ..< startIndex] - if let token = formatter.token(at: startOfLine), case .space = token { - prefix.insert(token, at: prefix.startIndex) - } - formatter.replaceTokens(in: startIndex ..< endOfLine, with: [ - .commentBody(comment), formatter.linebreakToken(for: startIndex), - ] + prefix + [ - .commentBody(commentPrefix + words.joined(separator: " ")), - ]) - } - } - - /// Writes one switch case per line - public let wrapSwitchCases = FormatRule( - help: "Wrap comma-delimited switch cases onto multiple lines.", - disabledByDefault: true, - sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"] - ) { formatter in - formatter.forEach(.endOfScope("case")) { i, _ in - guard var endIndex = formatter.index(of: .startOfScope(":"), after: i) else { return } - let lineStart = formatter.startOfLine(at: i) - let indent = formatter.spaceEquivalentToTokens(from: lineStart, upTo: i + 2) - - var startIndex = i - while let commaIndex = formatter.index(of: .delimiter(","), in: startIndex + 1 ..< endIndex), - let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: commaIndex) - { - if formatter.index(of: .linebreak, in: commaIndex ..< nextIndex) == nil { - formatter.insertLinebreak(at: commaIndex + 1) - let delta = formatter.insertSpace(indent, at: commaIndex + 2) - endIndex += 1 + delta - } - startIndex = commaIndex - } - } - } - - /// Normalize the use of void in closure arguments and return values - public let void = FormatRule( - help: "Use `Void` for type declarations and `()` for values.", - options: ["voidtype"] - ) { formatter in - func isArgumentToken(at index: Int) -> Bool { - guard let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: index) else { - return false - } - switch nextToken { - case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), .identifier("async"): - return true - case .startOfScope("{"): - if formatter.tokens[index] == .endOfScope(")"), - let index = formatter.index(of: .startOfScope("("), before: index), - let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index, if: { - $0.isIdentifier - }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: nameIndex) == .keyword("func") - { - return true - } - return false - case .keyword("in"): - if formatter.tokens[index] == .endOfScope(")"), - let index = formatter.index(of: .startOfScope("("), before: index) - { - return formatter.last(.nonSpaceOrCommentOrLinebreak, before: index) == .startOfScope("{") - } - return false - default: - return false - } - } - - let hasLocalVoid: Bool = { - for (i, token) in formatter.tokens.enumerated() where token == .identifier("Void") { - if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) { - switch prevToken { - case .keyword("typealias"), .keyword("struct"), .keyword("class"), .keyword("enum"): - return true - default: - break - } - } - } - return false - }() - - formatter.forEach(.identifier("Void")) { i, _ in - if let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .endOfScope(")") - }), var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), { - let token = formatter.tokens[prevIndex] - if token == .delimiter(":"), - let prevPrevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex), - formatter.tokens[prevPrevIndex] == .identifier("_"), - let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevPrevIndex), - formatter.tokens[startIndex] == .startOfScope("(") - { - prevIndex = startIndex - return true - } - return token == .startOfScope("(") - }() { - if isArgumentToken(at: nextIndex) || formatter.last( - .nonSpaceOrLinebreak, - before: prevIndex - )?.isIdentifier == true { - if !formatter.options.useVoid, !hasLocalVoid { - // Convert to parens - formatter.replaceToken(at: i, with: .endOfScope(")")) - formatter.insert(.startOfScope("("), at: i) - } - } else if formatter.options.useVoid { - // Strip parens - formatter.removeTokens(in: i + 1 ... nextIndex) - formatter.removeTokens(in: prevIndex ..< i) - } else { - // Remove Void - formatter.removeTokens(in: prevIndex + 1 ..< nextIndex) - } - } else if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - [.operator(".", .prefix), .operator(".", .infix), - .keyword("typealias")].contains(prevToken) - { - return - } else if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == - .operator(".", .infix) - { - return - } else if formatter.next(.nonSpace, after: i) == .startOfScope("(") { - if !hasLocalVoid { - formatter.removeToken(at: i) - } - } else if !formatter.options.useVoid || isArgumentToken(at: i), !hasLocalVoid { - // Convert to parens - formatter.replaceToken(at: i, with: [.startOfScope("("), .endOfScope(")")]) - } - } - formatter.forEach(.startOfScope("(")) { i, _ in - guard formatter.options.useVoid else { - return - } - guard let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .endOfScope(")") - }), let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - !isArgumentToken(at: endIndex) else { - return - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator("->", .infix) { - if !hasLocalVoid { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) - } - } else if prevToken == .startOfScope("<") || - (prevToken == .delimiter(",") && formatter.currentScope(at: i) == .startOfScope("<")), - !hasLocalVoid - { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) - } - // TODO: other cases - } - } - - /// Standardize formatting of numeric literals - public let numberFormatting = FormatRule( - help: """ - Use consistent grouping for numeric literals. Groups will be separated by `_` - delimiters to improve readability. For each numeric type you can specify a group - size (the number of digits in each group) and a threshold (the minimum number of - digits in a number before grouping is applied). - """, - options: ["decimalgrouping", "binarygrouping", "octalgrouping", "hexgrouping", - "fractiongrouping", "exponentgrouping", "hexliteralcase", "exponentcase"] - ) { formatter in - func applyGrouping(_ grouping: Grouping, to number: inout String) { - switch grouping { - case .none, .group: - number = number.replacingOccurrences(of: "_", with: "") - case .ignore: - return - } - guard case let .group(group, threshold) = grouping, group > 0, number.count >= threshold else { - return - } - var output = Substring() - var index = number.endIndex - var count = 0 - repeat { - index = number.index(before: index) - if count > 0, count % group == 0 { - output.insert("_", at: output.startIndex) - } - count += 1 - output.insert(number[index], at: output.startIndex) - } while index != number.startIndex - number = String(output) - } - formatter.forEachToken { i, token in - guard case let .number(number, type) = token else { - return - } - let grouping: Grouping - let prefix: String, exponentSeparator: String, parts: [String] - switch type { - case .integer, .decimal: - grouping = formatter.options.decimalGrouping - prefix = "" - exponentSeparator = formatter.options.uppercaseExponent ? "E" : "e" - parts = number.components(separatedBy: CharacterSet(charactersIn: ".eE")) - case .binary: - grouping = formatter.options.binaryGrouping - prefix = "0b" - exponentSeparator = "" - parts = [String(number[prefix.endIndex...])] - case .octal: - grouping = formatter.options.octalGrouping - prefix = "0o" - exponentSeparator = "" - parts = [String(number[prefix.endIndex...])] - case .hex: - grouping = formatter.options.hexGrouping - prefix = "0x" - exponentSeparator = formatter.options.uppercaseExponent ? "P" : "p" - parts = number[prefix.endIndex...].components(separatedBy: CharacterSet(charactersIn: ".pP")).map { - formatter.options.uppercaseHex ? $0.uppercased() : $0.lowercased() - } - } - var main = parts[0], fraction = "", exponent = "" - switch parts.count { - case 2 where number.contains("."): - fraction = parts[1] - case 2: - exponent = parts[1] - case 3: - fraction = parts[1] - exponent = parts[2] - default: - break - } - applyGrouping(grouping, to: &main) - if formatter.options.fractionGrouping { - applyGrouping(grouping, to: &fraction) - } - if formatter.options.exponentGrouping { - applyGrouping(grouping, to: &exponent) - } - var result = prefix + main - if !fraction.isEmpty { - result += "." + fraction - } - if !exponent.isEmpty { - result += exponentSeparator + exponent - } - formatter.replaceToken(at: i, with: .number(result, type)) - } - } - - /// Strip header comments from the file - public let fileHeader = FormatRule( - help: "Use specified source file header template for all files.", - runOnceOnly: true, - options: ["header", "dateformat", "timezone"], - sharedOptions: ["linebreaks"] - ) { formatter in - var headerTokens = [Token]() - var directives = [String]() - switch formatter.options.fileHeader { - case .ignore: - return - case var .replace(string): - let file = formatter.options.fileInfo - let options = ReplacementOptions( - dateFormat: formatter.options.dateFormat, - timeZone: formatter.options.timeZone - ) - - for (key, replacement) in formatter.options.fileInfo.replacements { - if let replacementStr = replacement.resolve(file, options) { - string = string.replacingOccurrences(of: key.placeholder, with: replacementStr) - } - } - headerTokens = tokenize(string) - directives = headerTokens.compactMap { - guard case let .commentBody(body) = $0 else { - return nil - } - return body.commentDirective - } - } - - guard let headerRange = formatter.headerCommentTokenRange(includingDirectives: directives) else { - return - } - - if headerTokens.isEmpty { - formatter.removeTokens(in: headerRange) - return - } - - var lastHeaderTokenIndex = headerRange.endIndex - 1 - let endIndex = lastHeaderTokenIndex + headerTokens.count - if formatter.tokens.endIndex > endIndex, headerTokens == Array(formatter.tokens[ - lastHeaderTokenIndex + 1 ... endIndex - ]) { - lastHeaderTokenIndex += headerTokens.count - } - let headerLinebreaks = headerTokens.reduce(0) { result, token -> Int in - result + (token.isLinebreak ? 1 : 0) - } - if lastHeaderTokenIndex < formatter.tokens.count - 1 { - headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 1)) - if lastHeaderTokenIndex < formatter.tokens.count - 2, - !formatter.tokens[lastHeaderTokenIndex + 1 ... lastHeaderTokenIndex + 2].allSatisfy({ - $0.isLinebreak - }) - { - headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 2)) - } - } - if let index = formatter.index(of: .nonSpace, after: lastHeaderTokenIndex, if: { - $0.isLinebreak - }) { - lastHeaderTokenIndex = index - } - formatter.replaceTokens(in: headerRange.startIndex ..< lastHeaderTokenIndex + 1, with: headerTokens) - } - - /// Ensure file name reference in header matches actual file name - public let headerFileName = FormatRule( - help: "Ensure file name in header comment matches the actual file name.", - runOnceOnly: true, - orderAfter: ["fileHeader"] - ) { formatter in - guard let fileName = formatter.options.fileInfo.fileName, - let headerRange = formatter.headerCommentTokenRange(includingDirectives: ["*"]), - fileName.hasSuffix(".swift") - else { - return - } - - for i in headerRange { - guard case let .commentBody(body) = formatter.tokens[i] else { - continue - } - if body.hasSuffix(".swift"), body != fileName, !body.contains(where: { " /".contains($0) }) { - formatter.replaceToken(at: i, with: .commentBody(fileName)) - } - } - } - - /// Strip redundant `.init` from type instantiations - public let redundantInit = FormatRule( - help: "Remove explicit `init` if not required.", - orderAfter: ["propertyType"] - ) { formatter in - formatter.forEach(.identifier("init")) { initIndex, _ in - guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: initIndex, if: { - $0.isOperator(".") - }), let openParenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: initIndex, if: { - $0 == .startOfScope("(") - }), let closeParenIndex = formatter.index(of: .endOfScope(")"), after: openParenIndex), - formatter.last(.nonSpaceOrCommentOrLinebreak, before: closeParenIndex) != .delimiter(":"), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex), - let prevToken = formatter.token(at: prevIndex), - formatter.isValidEndOfType(at: prevIndex), - // Find and parse the type that comes before the .init call - let startOfTypeIndex = Array(0 ..< dotIndex).reversed().last(where: { typeIndex in - guard let type = formatter.parseType(at: typeIndex) else { return false } - return (formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: type.range.upperBound) == dotIndex - // Since `Foo.init` is potentially a valid type, the `.init` may be parsed as part of the type name - || type.range.upperBound == initIndex) - // If this is actually a method call like `type(of: foo).init()`, the token before the "type" - // (which in this case looks like a tuple) will be an identifier. - && !(formatter.last(.nonSpaceOrComment, before: typeIndex)?.isIdentifier ?? false) - }), - let type = formatter.parseType(at: startOfTypeIndex), - // Filter out values that start with a lowercase letter. - // This covers edge cases like `super.init()`, where the `init` is not redundant. - let firstChar = type.name.components(separatedBy: ".").last?.first, - firstChar != "$", - String(firstChar).uppercased() == String(firstChar) - else { return } - - let lineStart = formatter.startOfLine(at: prevIndex, excludingIndent: true) - if [.startOfScope("#if"), .keyword("#elseif")].contains(formatter.tokens[lineStart]) { - return - } - var j = dotIndex - while let prevIndex = formatter.index( - of: prevToken, before: j - ) ?? formatter.index( - of: .startOfScope, before: j - ) { - j = prevIndex - if prevToken == formatter.tokens[prevIndex], - let prevPrevToken = formatter.last( - .nonSpaceOrCommentOrLinebreak, before: prevIndex - ), [.keyword("let"), .keyword("var")].contains(prevPrevToken) - { - return - } - } - formatter.removeTokens(in: initIndex + 1 ..< openParenIndex) - formatter.removeTokens(in: dotIndex ... initIndex) - } - } - - /// Deprecated - public let sortedSwitchCases = FormatRule( - help: "Sort switch cases alphabetically.", - deprecationMessage: "Use sortSwitchCases instead." - ) { formatter in - FormatRules.sortSwitchCases.apply(with: formatter) - } - - /// Sorts switch cases alphabetically - public let sortSwitchCases = FormatRule( - help: "Sort switch cases alphabetically.", - disabledByDefault: true - ) { formatter in - formatter.parseSwitchCaseRanges() - .reversed() // don't mess with indexes - .forEach { switchCaseRanges in - guard switchCaseRanges.count > 1, // nothing to sort - let firstCaseIndex = switchCaseRanges.first?.beforeDelimiterRange.lowerBound else { return } - - let indentCounts = switchCaseRanges.map { formatter.currentIndentForLine(at: $0.beforeDelimiterRange.lowerBound).count } - let maxIndentCount = indentCounts.max() ?? 0 - - func sortableValue(for token: Token) -> String? { - switch token { - case let .identifier(name): - return name - case let .stringBody(body): - return body - case let .number(value, .hex): - return Int(value.dropFirst(2), radix: 16) - .map(String.init) ?? value - case let .number(value, .octal): - return Int(value.dropFirst(2), radix: 8) - .map(String.init) ?? value - case let .number(value, .binary): - return Int(value.dropFirst(2), radix: 2) - .map(String.init) ?? value - case let .number(value, _): - return value - default: - return nil - } - } - - let sorted = switchCaseRanges.sorted { case1, case2 -> Bool in - let lhs = formatter.tokens[case1.beforeDelimiterRange] - .compactMap(sortableValue) - let rhs = formatter.tokens[case2.beforeDelimiterRange] - .compactMap(sortableValue) - for (lhs, rhs) in zip(lhs, rhs) { - switch lhs.localizedStandardCompare(rhs) { - case .orderedAscending: - return true - case .orderedDescending: - return false - case .orderedSame: - continue - } - } - return lhs.count < rhs.count - } - - let sortedTokens = sorted.map { formatter.tokens[$0.beforeDelimiterRange] } - let sortedComments = sorted.map { formatter.tokens[$0.afterDelimiterRange] } - - // ignore if there's a where keyword and it is not in the last place. - let firstWhereIndex = sortedTokens.firstIndex(where: { slice in slice.contains(.keyword("where")) }) - guard firstWhereIndex == nil || firstWhereIndex == sortedTokens.count - 1 else { return } - - for switchCase in switchCaseRanges.enumerated().reversed() { - let newTokens = Array(sortedTokens[switchCase.offset]) - var newComments = Array(sortedComments[switchCase.offset]) - let oldComments = formatter.tokens[switchCaseRanges[switchCase.offset].afterDelimiterRange] - - if newComments.last?.isLinebreak == oldComments.last?.isLinebreak { - formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) - } else if newComments.count > 1, - newComments.last?.isLinebreak == true, oldComments.last?.isLinebreak == false - { - // indent the new content - newComments.append(.space(String(repeating: " ", count: maxIndentCount))) - formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) - } - - formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].beforeDelimiterRange, with: newTokens) - } - } - } - - /// Deprecated - public let sortedImports = FormatRule( - help: "Sort import statements alphabetically.", - deprecationMessage: "Use sortImports instead.", - options: ["importgrouping"], - sharedOptions: ["linebreaks"] - ) { formatter in - _ = formatter.options.importGrouping - _ = formatter.options.linebreak - FormatRules.sortImports.apply(with: formatter) - } - - /// Sort import statements - public let sortImports = FormatRule( - help: "Sort import statements alphabetically.", - options: ["importgrouping"], - sharedOptions: ["linebreaks"] - ) { formatter in - func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] { - if case .alpha = formatter.options.importGrouping { - return ranges.sorted(by: <) - } else if case .length = formatter.options.importGrouping { - return ranges.sorted { $0.module.count < $1.module.count } - } - // Group @testable imports at the top or bottom - // TODO: need more general solution for handling other import attributes - return ranges.sorted { - // If both have a @testable keyword, or neither has one, just sort alphabetically - guard $0.isTestable != $1.isTestable else { - return $0 < $1 - } - return formatter.options.importGrouping == .testableFirst ? $0.isTestable : $1.isTestable - } - } - - for var importRanges in formatter.parseImports().reversed() { - guard importRanges.count > 1 else { continue } - let range: Range = importRanges.first!.range.lowerBound ..< importRanges.last!.range.upperBound - let sortedRanges = sortRanges(importRanges) - var insertedLinebreak = false - var sortedTokens = sortedRanges.flatMap { inputRange -> [Token] in - var tokens = Array(formatter.tokens[inputRange.range]) - if tokens.first?.isLinebreak == false { - insertedLinebreak = true - tokens.insert(formatter.linebreakToken(for: tokens.startIndex), at: tokens.startIndex) - } - return tokens - } - if insertedLinebreak { - sortedTokens.removeFirst() - } - formatter.replaceTokens(in: range, with: sortedTokens) - } - } - - /// Remove duplicate import statements - public let duplicateImports = FormatRule( - help: "Remove duplicate import statements." - ) { formatter in - for var importRanges in formatter.parseImports().reversed() { - for i in importRanges.indices.reversed() { - let range = importRanges.remove(at: i) - guard let j = importRanges.firstIndex(where: { $0.module == range.module }) else { - continue - } - let range2 = importRanges[j] - if Set(range.attributes).isSubset(of: range2.attributes) { - formatter.removeTokens(in: range.range) - continue - } - if j >= i { - formatter.removeTokens(in: range2.range) - importRanges.remove(at: j) - } - importRanges.append(range) - } - } - } - - /// Strip unnecessary `weak` from @IBOutlet properties (except delegates and datasources) - public let strongOutlets = FormatRule( - help: "Remove `weak` modifier from `@IBOutlet` properties." - ) { formatter in - formatter.forEach(.keyword("@IBOutlet")) { i, _ in - guard let varIndex = formatter.index(of: .keyword("var"), after: i), - let weakIndex = (i ..< varIndex).first(where: { formatter.tokens[$0] == .identifier("weak") }), - case let .identifier(name)? = formatter.next(.identifier, after: varIndex) - else { - return - } - let lowercased = name.lowercased() - if lowercased.hasSuffix("delegate") || lowercased.hasSuffix("datasource") { - return - } - if formatter.tokens[weakIndex + 1].isSpace { - formatter.removeToken(at: weakIndex + 1) - } else if formatter.tokens[weakIndex - 1].isSpace { - formatter.removeToken(at: weakIndex - 1) - } - formatter.removeToken(at: weakIndex) - } - } - - /// Remove white-space between empty braces - public let emptyBraces = FormatRule( - help: "Remove whitespace inside empty braces.", - options: ["emptybraces"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard let closingIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .endOfScope("}") - }) else { - return - } - if let token = formatter.next(.nonSpaceOrComment, after: closingIndex), - [.keyword("else"), .keyword("catch")].contains(token) - { - return - } - let range = i + 1 ..< closingIndex - switch formatter.options.emptyBracesSpacing { - case .noSpace: - formatter.removeTokens(in: range) - case .spaced: - formatter.replaceTokens(in: range, with: .space(" ")) - case .linebreak: - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: range.endIndex) - formatter.replaceTokens(in: range, with: formatter.linebreakToken(for: i + 1)) - } - } - } - - /// Replace the `&&` operator with `,` where applicable - public let andOperator = FormatRule( - help: "Prefer comma over `&&` in `if`, `guard` or `while` conditions." - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .keyword("if"), .keyword("guard"), - .keyword("while") where formatter.last(.keyword, before: i) != .keyword("repeat"): - break - default: - return - } - guard var endIndex = formatter.index(of: .startOfScope("{"), after: i) else { - return - } - if formatter.options.swiftVersion < "5.3", formatter.isInResultBuilder(at: i) { - return - } - var index = i + 1 - var chevronIndex: Int? - outer: while index < endIndex { - switch formatter.tokens[index] { - case .operator("&&", .infix): - let endOfGroup = formatter.index(of: .delimiter(","), after: index) ?? endIndex - var nextOpIndex = index - while let next = formatter.index(of: .operator, after: nextOpIndex) { - if formatter.tokens[next] == .operator("||", .infix) { - index = endOfGroup - continue outer - } - nextOpIndex = next - } - if let chevronIndex = chevronIndex, - formatter.index(of: .operator(">", .infix), in: index ..< endIndex) != nil - { - // Check if this would cause ambiguity for chevrons - var tokens = Array(formatter.tokens[i ... endIndex]) - tokens[index - i] = .delimiter(",") - tokens.append(.endOfScope("}")) - let reparsed = tokenize(sourceCode(for: tokens)) - if reparsed[chevronIndex - i] == .startOfScope("<") { - return - } - } - formatter.replaceToken(at: index, with: .delimiter(",")) - if formatter.tokens[index - 1] == .space(" ") { - formatter.removeToken(at: index - 1) - endIndex -= 1 - index -= 1 - } else if let prevIndex = formatter.index(of: .nonSpace, before: index), - formatter.tokens[prevIndex].isLinebreak, let nonLinbreak = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) - { - formatter.removeToken(at: index) - formatter.insert(.delimiter(","), at: nonLinbreak + 1) - if formatter.tokens[index + 1] == .space(" ") { - formatter.removeToken(at: index + 1) - endIndex -= 1 - } - } - case .operator("<", .infix): - chevronIndex = index - case .operator("||", .infix), .operator("=", .infix), .keyword("try"): - index = formatter.index(of: .delimiter(","), after: index) ?? endIndex - case .startOfScope: - index = formatter.endOfScope(at: index) ?? endIndex - default: - break - } - index += 1 - } - } - } - - /// Replace count == 0 with isEmpty - public let isEmpty = FormatRule( - help: "Prefer `isEmpty` over comparing `count` against zero.", - disabledByDefault: true - ) { formatter in - formatter.forEach(.identifier("count")) { i, _ in - guard let dotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i, if: { - $0.isOperator(".") - }), let opIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0.isOperator - }), let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: opIndex, if: { - $0 == .number("0", .integer) - }) else { - return - } - var isOptional = false - var index = dotIndex - var wasIdentifier = false - loop: while true { - guard let prev = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) else { - break - } - switch formatter.tokens[prev] { - case .operator("!", _), .operator(".", _): - break // Ignored - case .operator("?", _): - if formatter.tokens[prev - 1].isSpace { - break loop - } - isOptional = true - case let .operator(op, .infix): - guard ["||", "&&", ":"].contains(op) else { - return - } - break loop - case .keyword, .delimiter, .startOfScope: - break loop - case .identifier: - if wasIdentifier { - break loop - } - wasIdentifier = true - index = prev - continue - case .endOfScope: - guard !wasIdentifier, let start = formatter.index(of: .startOfScope, before: prev) else { - break loop - } - wasIdentifier = false - index = start - continue - default: - break - } - wasIdentifier = false - index = prev - } - let isEmpty: Bool - switch formatter.tokens[opIndex] { - case .operator("==", .infix): isEmpty = true - case .operator("!=", .infix), .operator(">", .infix): isEmpty = false - default: return - } - if isEmpty { - if isOptional { - formatter.replaceTokens(in: i ... endIndex, with: [ - .identifier("isEmpty"), .space(" "), .operator("==", .infix), .space(" "), .identifier("true"), - ]) - } else { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) - } - } else { - if isOptional { - formatter.replaceTokens(in: i ... endIndex, with: [ - .identifier("isEmpty"), .space(" "), .operator("!=", .infix), .space(" "), .identifier("true"), - ]) - } else { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) - formatter.insert(.operator("!", .prefix), at: index) - } - } - } - } - - /// Remove redundant `let error` from `catch` statements - public let redundantLetError = FormatRule( - help: "Remove redundant `let error` from `catch` clause." - ) { formatter in - formatter.forEach(.keyword("catch")) { i, _ in - if let letIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .keyword("let") - }), let errorIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: letIndex, if: { - $0 == .identifier("error") - }), let scopeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: errorIndex, if: { - $0 == .startOfScope("{") - }) { - formatter.removeTokens(in: letIndex ..< scopeIndex) - } - } - } - - /// Prefer `AnyObject` over `class` for class-based protocols - public let anyObjectProtocol = FormatRule( - help: "Prefer `AnyObject` over `class` in protocol definitions." - ) { formatter in - formatter.forEach(.keyword("protocol")) { i, _ in - guard formatter.options.swiftVersion >= "4.1", - let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0.isIdentifier - }), let colonIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { - $0 == .delimiter(":") - }), let classIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex, if: { - $0 == .keyword("class") - }) - else { - return - } - formatter.replaceToken(at: classIndex, with: .identifier("AnyObject")) - } - } - - /// Remove redundant `break` keyword from switch cases - public let redundantBreak = FormatRule( - help: "Remove redundant `break` in switch case." - ) { formatter in - formatter.forEach(.keyword("break")) { i, _ in - guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .startOfScope(":"), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isEndOfScope == true, - var startIndex = formatter.index(of: .nonSpace, before: i), - let endIndex = formatter.index(of: .nonSpace, after: i), - formatter.currentScope(at: i) == .startOfScope(":") - else { - return - } - if !formatter.tokens[startIndex].isLinebreak || !formatter.tokens[endIndex].isLinebreak { - startIndex += 1 - } - formatter.removeTokens(in: startIndex ..< endIndex) - } - } - - /// Removed backticks from `self` when strongifying - public let strongifiedSelf = FormatRule( - help: "Remove backticks around `self` in Optional unwrap expressions." - ) { formatter in - formatter.forEach(.identifier("`self`")) { i, _ in - guard formatter.options.swiftVersion >= "4.2", - let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .operator("=", .infix) - }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self"), - formatter.isConditionalStatement(at: i) - else { - return - } - formatter.replaceToken(at: i, with: .identifier("self")) - } - } - - /// Remove redundant @objc annotation - public let redundantObjc = FormatRule( - help: "Remove redundant `@objc` annotations." - ) { formatter in - let objcAttributes = [ - "@IBOutlet", "@IBAction", "@IBSegueAction", - "@IBDesignable", "@IBInspectable", "@GKInspectable", - "@NSManaged", - ] - - formatter.forEach(.keyword("@objc")) { i, _ in - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .startOfScope("(") else { - return - } - var index = i - loop: while var nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index) { - switch formatter.tokens[nextIndex] { - case .keyword("class"), .keyword("actor"), .keyword("enum"), - // Not actually allowed currently, but: future-proofing! - .keyword("protocol"), .keyword("struct"): - return - case .keyword("private"), .keyword("fileprivate"): - if formatter.next(.nonSpaceOrComment, after: nextIndex) == .startOfScope("(") { - break - } - // Can't safely remove objc from private members - return - case let token where token.isAttribute: - if let startIndex = formatter.index(of: .startOfScope("("), after: nextIndex), - let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) - { - nextIndex = endIndex - } - case let token: - guard token.isModifierKeyword else { - break loop - } - } - index = nextIndex - } - func removeAttribute() { - formatter.removeToken(at: i) - if formatter.token(at: i)?.isSpace == true { - formatter.removeToken(at: i) - } else if formatter.token(at: i - 1)?.isSpace == true { - formatter.removeToken(at: i - 1) - } - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i, if: { - $0.isAttribute && objcAttributes.contains($0.string) - }) != nil || formatter.next(.nonSpaceOrCommentOrLinebreak, after: i, if: { - $0.isAttribute && objcAttributes.contains($0.string) - }) != nil { - removeAttribute() - return - } - guard let scopeStart = formatter.index(of: .startOfScope("{"), before: i), - let keywordIndex = formatter.index(of: .keyword, before: scopeStart) - else { - return - } - switch formatter.tokens[keywordIndex] { - case .keyword("class"), .keyword("actor"): - if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objcMembers") { - removeAttribute() - } - case .keyword("extension"): - if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objc") { - removeAttribute() - } - default: - break - } - } - } - - /// Replace Array, Dictionary and Optional with [T], [T: U] and T? - public let typeSugar = FormatRule( - help: "Prefer shorthand syntax for Arrays, Dictionaries and Optionals.", - options: ["shortoptionals"] - ) { formatter in - formatter.forEach(.startOfScope("<")) { i, _ in - guard let typeIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), - case let .identifier(identifier) = formatter.tokens[typeIndex], - let endIndex = formatter.index(of: .endOfScope(">"), after: i), - let typeStart = formatter.index(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex), - let typeEnd = formatter.lastIndex(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex) - else { - return - } - let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex, if: { - $0.isOperator(".") - }) - if let dotIndex = dotIndex, formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: dotIndex, if: { - ![.identifier("self"), .identifier("Type")].contains($0) - }) != nil, identifier != "Optional" { - return - } - // Workaround for https://bugs.swift.org/browse/SR-12856 - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) != .delimiter(":") || - formatter.currentScope(at: i) == .startOfScope("[") - { - var startIndex = i - if formatter.tokens[typeIndex] == .identifier("Dictionary") { - startIndex = formatter.index(of: .delimiter(","), in: i + 1 ..< endIndex) ?? startIndex - } - if let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startIndex, if: { - $0 == .startOfScope("(") - }), let underscoreIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { - $0 == .identifier("_") - }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: underscoreIndex)?.isIdentifier == true { - return - } - } - if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) { - switch prevToken { - case .keyword("struct"), .keyword("class"), .keyword("actor"), - .keyword("enum"), .keyword("protocol"), .keyword("typealias"): - return - default: - break - } - } - switch formatter.tokens[typeIndex] { - case .identifier("Array"): - formatter.replaceTokens(in: typeIndex ... endIndex, with: - [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) - case .identifier("Dictionary"): - guard let commaIndex = formatter.index(of: .delimiter(","), in: typeStart ..< typeEnd) else { - return - } - formatter.replaceToken(at: commaIndex, with: .delimiter(":")) - formatter.replaceTokens(in: typeIndex ... endIndex, with: - [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) - case .identifier("Optional"): - if formatter.options.shortOptionals == .exceptProperties, - let lastKeyword = formatter.lastSignificantKeyword(at: i), - ["var", "let"].contains(lastKeyword) - { - return - } - if formatter.lastSignificantKeyword(at: i) == "case" || - formatter.last(.endOfScope, before: i) == .endOfScope("case") - { - // https://bugs.swift.org/browse/SR-13838 - return - } - var typeTokens = formatter.tokens[typeStart ... typeEnd] - if [.operator("&", .infix), .operator("->", .infix), - .identifier("some"), .identifier("any")].contains(where: typeTokens.contains) - { - typeTokens.insert(.startOfScope("("), at: typeTokens.startIndex) - typeTokens.append(.endOfScope(")")) - } - typeTokens.append(.operator("?", .postfix)) - formatter.replaceTokens(in: typeIndex ... endIndex, with: typeTokens) - default: - return - } - // Drop leading Swift. namespace - if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: typeIndex, if: { - $0.isOperator(".") - }), let swiftTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex, if: { - $0 == .identifier("Swift") - }) { - formatter.removeTokens(in: swiftTokenIndex ..< typeIndex) - } - } - } - - /// Remove redundant access control level modifiers in extensions - public let redundantExtensionACL = FormatRule( - help: "Remove redundant access control modifiers." - ) { formatter in - formatter.forEach(.keyword("extension")) { i, _ in - var acl = "" - guard formatter.modifiersForDeclaration(at: i, contains: { - acl = $1 - return aclModifiers.contains(acl) - }), let startIndex = formatter.index(of: .startOfScope("{"), after: i), - var endIndex = formatter.index(of: .endOfScope("}"), after: startIndex) else { - return - } - if acl == "private" { acl = "fileprivate" } - while let aclIndex = formatter.lastIndex(of: .keyword(acl), in: startIndex + 1 ..< endIndex) { - formatter.removeToken(at: aclIndex) - if formatter.token(at: aclIndex)?.isSpace == true { - formatter.removeToken(at: aclIndex) - } - endIndex = aclIndex - } - } - } - - /// Remove unused private and fileprivate declarations - public let unusedPrivateDeclaration = FormatRule( - help: "Remove unused private and fileprivate declarations.", - disabledByDefault: true, - options: ["preservedecls"] - ) { formatter in - guard !formatter.options.fragment else { return } - - // Only remove unused properties, functions, or typealiases. - // - This rule doesn't currently support removing unused types, - // and it's more difficult to track the usage of other declaration - // types like `init`, `subscript`, `operator`, etc. - let allowlist = ["let", "var", "func", "typealias"] - let disallowedModifiers = ["override", "@objc", "@IBAction", "@IBSegueAction", "@IBOutlet", "@IBDesignable", "@IBInspectable", "@NSManaged", "@GKInspectable"] - - // Collect all of the `private` or `fileprivate` declarations in the file - var privateDeclarations: [Declaration] = [] - formatter.forEachRecursiveDeclaration { declaration in - let declarationModifiers = Set(declaration.modifiers) - let hasDisallowedModifiers = disallowedModifiers.contains(where: { declarationModifiers.contains($0) }) - - guard allowlist.contains(declaration.keyword), - let name = declaration.name, - !formatter.options.preservedPrivateDeclarations.contains(name), - !hasDisallowedModifiers - else { return } - - switch formatter.visibility(of: declaration) { - case .fileprivate, .private: - privateDeclarations.append(declaration) - case .none, .open, .public, .package, .internal: - break - } - } - - // Count the usage of each identifier in the file - var usage: [String: Int] = [:] - formatter.forEach(.identifier) { _, token in - usage[token.string, default: 0] += 1 - } - - // Remove any private or fileprivate declaration whose name only - // appears a single time in the source file - for declaration in privateDeclarations.reversed() { - // Strip backticks from name for a normalized base name for cases like `default` - guard let name = declaration.name?.trimmingCharacters(in: CharacterSet(charactersIn: "`")) else { continue } - // Check for regular usage, common property wrapper prefixes, and protected names - let variants = [name, "_\(name)", "$\(name)", "`\(name)`"] - let count = variants.compactMap { usage[$0] }.reduce(0, +) - if count <= 1 { - formatter.removeTokens(in: declaration.originalRange) - } - } - } - - /// Replace `fileprivate` with `private` where possible - public let redundantFileprivate = FormatRule( - help: "Prefer `private` over `fileprivate` where equivalent." - ) { formatter in - guard !formatter.options.fragment else { return } - - var hasUnreplacedFileprivates = false - formatter.forEach(.keyword("fileprivate")) { i, _ in - // check if definition is at file-scope - if formatter.index(of: .startOfScope, before: i) == nil { - formatter.replaceToken(at: i, with: .keyword("private")) - } else { - hasUnreplacedFileprivates = true - } - } - guard hasUnreplacedFileprivates else { - return - } - let importRanges = formatter.parseImports() - var fileJustContainsOneType: Bool? - func ifCodeInRange(_ range: CountableRange) -> Bool { - var index = range.lowerBound - while index < range.upperBound, let nextIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: index ..< range.upperBound) - { - guard let importRange = importRanges.first(where: { - $0.contains(where: { $0.range.contains(nextIndex) }) - }) else { - return true - } - index = importRange.last!.range.upperBound + 1 - } - return false - } - func isTypeInitialized(_ name: String, in range: CountableRange) -> Bool { - for i in range { - switch formatter.tokens[i] { - case .identifier(name): - guard let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) else { - break - } - switch formatter.tokens[nextIndex] { - case .operator(".", .infix): - if formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("init") { - return true - } - case .startOfScope("("): - return true - case .startOfScope("{"): - if formatter.isStartOfClosure(at: nextIndex) { - return true - } - default: - break - } - case .identifier("init"): - // TODO: this will return true if *any* type is initialized using type inference. - // Is there a way to narrow this down a bit? - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator(".", .prefix) { - return true - } - default: - break - } - } - return false - } - // TODO: improve this logic to handle shadowing - func areMembers(_ names: [String], of type: String, - referencedIn range: CountableRange) -> Bool - { - var i = range.lowerBound - while i < range.upperBound { - switch formatter.tokens[i] { - case .keyword("struct"), .keyword("extension"), .keyword("enum"), .keyword("actor"), - .keyword("class") where formatter.declarationType(at: i) == "class": - guard let startIndex = formatter.index(of: .startOfScope("{"), after: i), - let endIndex = formatter.endOfScope(at: startIndex) - else { - break - } - guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - formatter.tokens[nameIndex] != .identifier(type) - else { - i = endIndex - break - } - for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] - where names.contains(name) - { - return true - } - i = endIndex - case let .identifier(name) where names.contains(name): - if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .operator(".", .infix) - }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: dotIndex) - != .identifier("self") - { - return true - } - default: - break - } - i += 1 - } - return false - } - func isInitOverridden(for type: String, in range: CountableRange) -> Bool { - for i in range { - if case .keyword("init") = formatter.tokens[i], - let scopeStart = formatter.index(of: .startOfScope("{"), after: i), - formatter.index(of: .identifier("super"), after: scopeStart) != nil, - let scopeIndex = formatter.index(of: .startOfScope("{"), before: i), - let colonIndex = formatter.index(of: .delimiter(":"), before: scopeIndex), - formatter.next( - .nonSpaceOrCommentOrLinebreak, - in: colonIndex + 1 ..< scopeIndex - ) == .identifier(type) - { - return true - } - } - return false - } - formatter.forEach(.keyword("fileprivate")) { i, _ in - // Check if definition is a member of a file-scope type - guard formatter.options.swiftVersion >= "4", - let scopeIndex = formatter.index(of: .startOfScope, before: i, if: { - $0 == .startOfScope("{") - }), let typeIndex = formatter.index(of: .keyword, before: scopeIndex, if: { - ["class", "actor", "struct", "enum", "extension"].contains($0.string) - }), let nameIndex = formatter.index(of: .identifier, in: typeIndex ..< scopeIndex), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nameIndex)?.isOperator(".") == false, - case let .identifier(typeName) = formatter.tokens[nameIndex], - let endIndex = formatter.index(of: .endOfScope, after: scopeIndex), - formatter.currentScope(at: typeIndex) == nil - else { - return - } - // Get member type - guard let keywordIndex = formatter.index(of: .keyword, in: i + 1 ..< endIndex), - let memberType = formatter.declarationType(at: keywordIndex), - // TODO: check if member types are exposed in the interface, otherwise convert them too - ["let", "var", "func", "init"].contains(memberType) - else { - return - } - // Check that type doesn't (potentially) conform to a protocol - // TODO: use a whitelist of known protocols to make this check less blunt - guard !formatter.tokens[typeIndex ..< scopeIndex].contains(.delimiter(":")) else { - return - } - // Check for code outside of main type definition - let startIndex = formatter.startOfModifiers(at: typeIndex, includingAttributes: true) - if fileJustContainsOneType == nil { - fileJustContainsOneType = !ifCodeInRange(0 ..< startIndex) && - !ifCodeInRange(endIndex + 1 ..< formatter.tokens.count) - } - if fileJustContainsOneType == true { - formatter.replaceToken(at: i, with: .keyword("private")) - return - } - // Check if type name is initialized outside type, and if so don't - // change any fileprivate members in case we break memberwise initializer - // TODO: check if struct contains an overridden init; if so we can skip this check - if formatter.tokens[typeIndex] == .keyword("struct"), - isTypeInitialized(typeName, in: 0 ..< startIndex) || - isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count) - { - return - } - // Check if member is referenced outside type - if memberType == "init" { - // Make initializer private if it's not called anywhere - if !isTypeInitialized(typeName, in: 0 ..< startIndex), - !isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count), - !isInitOverridden(for: typeName, in: 0 ..< startIndex), - !isInitOverridden(for: typeName, in: endIndex + 1 ..< formatter.tokens.count) - { - formatter.replaceToken(at: i, with: .keyword("private")) - } - } else if let _names = formatter.namesInDeclaration(at: keywordIndex), - case let names = _names + _names.map({ "$\($0)" }), - !areMembers(names, of: typeName, referencedIn: 0 ..< startIndex), - !areMembers(names, of: typeName, referencedIn: endIndex + 1 ..< formatter.tokens.count) - { - formatter.replaceToken(at: i, with: .keyword("private")) - } - } - } - - /// Reorders "yoda conditions" where constant is placed on lhs of a comparison - public let yodaConditions = FormatRule( - help: "Prefer constant values to be on the right-hand-side of expressions.", - options: ["yodaswap"] - ) { formatter in - func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { - var index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: range) - while var i = index { - switch formatter.tokens[i] { - case .startOfScope where isConstant(at: i): - guard let endIndex = formatter.index(of: .endOfScope, after: i) else { - return false - } - i = endIndex - fallthrough - case _ where isConstant(at: i), .delimiter(","), .delimiter(":"): - index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< range.upperBound) - case .identifier: - guard let nextIndex = - formatter.index(of: .nonSpaceOrComment, in: i + 1 ..< range.upperBound), - formatter.tokens[nextIndex] == .delimiter(":") - else { - return false - } - // Identifier is a label - index = nextIndex - default: - return false - } - } - return true - } - func isConstant(at index: Int) -> Bool { - var index = index - while case .operator(_, .postfix) = formatter.tokens[index] { - index -= 1 - } - guard let token = formatter.token(at: index) else { - return false - } - switch token { - case .number, .identifier("true"), .identifier("false"), .identifier("nil"): - return true - case .endOfScope("]"), .endOfScope(")"): - guard let startIndex = formatter.index(of: .startOfScope, before: index), - !formatter.isSubscriptOrFunctionCall(at: startIndex) - else { - return false - } - return valuesInRangeAreConstant(startIndex + 1 ..< index) - case .startOfScope("["), .startOfScope("("): - guard !formatter.isSubscriptOrFunctionCall(at: index), - let endIndex = formatter.index(of: .endOfScope, after: index) - else { - return false - } - return valuesInRangeAreConstant(index + 1 ..< endIndex) - case .startOfScope, .endOfScope: - // TODO: what if string contains interpolation? - return token.isStringDelimiter - case _ where formatter.options.yodaSwap == .literalsOnly: - // Don't treat .members as constant - return false - case .operator(".", .prefix) where formatter.token(at: index + 1)?.isIdentifier == true, - .identifier where formatter.token(at: index - 1) == .operator(".", .prefix) && - formatter.token(at: index - 2) != .operator("\\", .prefix): - return true - default: - return false - } - } - func isOperator(at index: Int?) -> Bool { - guard let index = index else { - return false - } - switch formatter.tokens[index] { - // Discount operators with higher precedence than == - case .operator("=", .infix), - .operator("&&", .infix), .operator("||", .infix), - .operator("?", .infix), .operator(":", .infix): - return false - case .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: - return false - } - } - func startOfValue(at index: Int) -> Int? { - var index = index - while case .operator(_, .postfix)? = formatter.token(at: index) { - index -= 1 - } - if case .endOfScope? = formatter.token(at: index) { - guard let i = formatter.index(of: .startOfScope, before: index) else { - return nil - } - index = i - } - while case .operator(_, .prefix)? = formatter.token(at: index - 1) { - index -= 1 - } - return index - } - - formatter.forEachToken { i, token in - guard case let .operator(op, .infix) = token, - let opIndex = ["==", "!=", "<", "<=", ">", ">="].firstIndex(of: op), - let prevIndex = formatter.index(of: .nonSpace, before: i), - isConstant(at: prevIndex), let startIndex = startOfValue(at: prevIndex), - !isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex)), - let nextIndex = formatter.index(of: .nonSpace, after: i), !isConstant(at: nextIndex) || - isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextIndex)), - let endIndex = formatter.endOfExpression(at: nextIndex, upTo: [ - .operator("&&", .infix), .operator("||", .infix), - .operator("?", .infix), .operator(":", .infix), - ]) - else { - return - } - let inverseOp = ["==", "!=", ">", ">=", "<", "<="][opIndex] - let expression = Array(formatter.tokens[nextIndex ... endIndex]) - let constant = Array(formatter.tokens[startIndex ... prevIndex]) - formatter.replaceTokens(in: nextIndex ... endIndex, with: constant) - formatter.replaceToken(at: i, with: .operator(inverseOp, .infix)) - formatter.replaceTokens(in: startIndex ... prevIndex, with: expression) - } - } - - public let leadingDelimiters = FormatRule( - help: "Move leading delimiters to the end of the previous line.", - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.delimiter) { i, _ in - guard let endOfLine = formatter.index(of: .nonSpace, before: i, if: { - $0.isLinebreak - }) else { - return - } - let nextIndex = formatter.index(of: .nonSpace, after: i) ?? (i + 1) - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) - formatter.insertLinebreak(at: nextIndex) - formatter.removeTokens(in: i + 1 ..< nextIndex) - guard case .commentBody? = formatter.last(.nonSpace, before: endOfLine) else { - formatter.removeTokens(in: endOfLine ..< i) - return - } - let startIndex = formatter.index(of: .nonSpaceOrComment, before: endOfLine) ?? -1 - formatter.removeTokens(in: endOfLine ..< i) - let comment = Array(formatter.tokens[startIndex + 1 ..< endOfLine]) - formatter.insert(comment, at: endOfLine + 1) - formatter.removeTokens(in: startIndex + 1 ..< endOfLine) - } - } - - public let wrapAttributes = FormatRule( - help: "Wrap @attributes onto a separate line, or keep them on the same line.", - options: ["funcattributes", "typeattributes", "varattributes", "storedvarattrs", "computedvarattrs", "complexattrs", "noncomplexattrs"], - sharedOptions: ["linebreaks", "maxwidth"] - ) { formatter in - formatter.forEach(.attribute) { i, _ in - // Ignore sequential attributes - guard let endIndex = formatter.endOfAttribute(at: i), - var keywordIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - after: endIndex, if: { $0.isKeyword || $0.isModifierKeyword } - ) - else { - return - } - - // Skip modifiers - while formatter.isModifier(at: keywordIndex), - let nextIndex = formatter.index(of: .keyword, after: keywordIndex) - { - keywordIndex = nextIndex - } - - // Check which `AttributeMode` option to use - var attributeMode: AttributeMode - switch formatter.tokens[keywordIndex].string { - case "func", "init", "subscript": - attributeMode = formatter.options.funcAttributes - case "class", "actor", "struct", "enum", "protocol", "extension": - attributeMode = formatter.options.typeAttributes - case "var", "let": - let storedOrComputedAttributeMode: AttributeMode - if formatter.isStoredProperty(atIntroducerIndex: keywordIndex) { - storedOrComputedAttributeMode = formatter.options.storedVarAttributes - } else { - storedOrComputedAttributeMode = formatter.options.computedVarAttributes - } - - // If the relevant `storedvarattrs` or `computedvarattrs` option hasn't been configured, - // fall back to the previous (now deprecated) `varattributes` option. - if storedOrComputedAttributeMode == .preserve { - attributeMode = formatter.options.varAttributes - } else { - attributeMode = storedOrComputedAttributeMode - } - default: - return - } - - // If the complexAttributes option is configured, it takes precedence over other options - // if this is a complex attributes with arguments. - let attributeName = formatter.tokens[i].string - let isComplexAttribute = formatter.isComplexAttribute(at: i) - && !formatter.options.complexAttributesExceptions.contains(attributeName) - - if isComplexAttribute, formatter.options.complexAttributes != .preserve { - attributeMode = formatter.options.complexAttributes - } - - // Apply the `AttributeMode` - switch attributeMode { - case .preserve: - return - case .prevLine: - // Make sure there's a newline immediately following the attribute - if let nextIndex = formatter.index(of: .nonSpaceOrComment, after: endIndex), - formatter.token(at: nextIndex)?.isLinebreak != true - { - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) - formatter.insertLinebreak(at: nextIndex) - // Remove any trailing whitespace left on the line with the attributes - if let prevToken = formatter.token(at: nextIndex - 1), prevToken.isSpace { - formatter.removeToken(at: nextIndex - 1) - } - } - case .sameLine: - // Make sure there isn't a newline immediately following the attribute - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), - formatter.tokens[(endIndex + 1) ..< nextIndex].contains(where: { $0.isLinebreak }) - { - // If unwrapping the attribute causes the line to exceed the max width, - // leave it as-is. The existing formatting is likely better than how - // this would be re-unwrapped by the wrap rule. - let startOfLine = formatter.startOfLine(at: i) - let endOfLine = formatter.endOfLine(at: i) - let startOfNextLine = formatter.startOfLine(at: nextIndex, excludingIndent: true) - let endOfNextLine = formatter.endOfLine(at: nextIndex) - let combinedLine = formatter.tokens[startOfLine ... endOfLine].map { $0.string }.joined() - + formatter.tokens[startOfNextLine ..< endOfNextLine].map { $0.string }.joined() - - if formatter.options.maxWidth > 0, combinedLine.count > formatter.options.maxWidth { - return - } - - // Replace the newline with a space so the attribute doesn't - // merge with the next token. - formatter.replaceTokens(in: (endIndex + 1) ..< nextIndex, with: .space(" ")) - } - } - } - } - - public let preferKeyPath = FormatRule( - help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax." - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard formatter.options.swiftVersion >= "5.2", - var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) - else { - return - } - var prevToken = formatter.tokens[prevIndex] - var label: String? - if prevToken == .delimiter(":"), - let labelIndex = formatter.index(of: .nonSpace, before: prevIndex), - case let .identifier(name) = formatter.tokens[labelIndex], - let prevIndex2 = formatter.index(of: .nonSpaceOrLinebreak, before: labelIndex) - { - label = name - prevToken = formatter.tokens[prevIndex2] - prevIndex = prevIndex2 - } - let parenthesized = prevToken == .startOfScope("(") - if parenthesized { - prevToken = formatter.last(.nonSpaceOrLinebreak, before: prevIndex) ?? prevToken - } - guard case let .identifier(name) = prevToken, - ["map", "flatMap", "compactMap", "allSatisfy", "filter", "contains"].contains(name), - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .identifier("$0") - }), - let endIndex = formatter.endOfScope(at: i), - let lastIndex = formatter.index(of: .nonSpaceOrLinebreak, before: endIndex) - else { - return - } - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), - formatter.isLabel(at: nextIndex) - { - return - } - if name == "contains" { - if label != "where" { - return - } - } else if label != nil { - return - } - var replacementTokens: [Token] - if nextIndex == lastIndex { - // TODO: add this when https://bugs.swift.org/browse/SR-12897 is fixed - // replacementTokens = tokenize("\\.self") - return - } else { - let tokens = formatter.tokens[nextIndex + 1 ... lastIndex] - guard tokens.allSatisfy({ $0.isSpace || $0.isIdentifier || $0.isOperator(".") }) else { - return - } - replacementTokens = [.operator("\\", .prefix)] + tokens - } - if let label = label { - replacementTokens = [.identifier(label), .delimiter(":"), .space(" ")] + replacementTokens - } - if !parenthesized { - replacementTokens = [.startOfScope("(")] + replacementTokens + [.endOfScope(")")] - } - formatter.replaceTokens(in: prevIndex + 1 ... endIndex, with: replacementTokens) - } - } - - public let organizeDeclarations = FormatRule( - help: "Organize declarations within class, struct, enum, actor, and extension bodies.", - runOnceOnly: true, - disabledByDefault: true, - orderAfter: ["extensionAccessControl", "redundantFileprivate"], - options: [ - "categorymark", "markcategories", "beforemarks", - "lifecycle", "organizetypes", "structthreshold", "classthreshold", - "enumthreshold", "extensionlength", "organizationmode", - "visibilityorder", "typeorder", "visibilitymarks", "typemarks", - ], - sharedOptions: ["sortedpatterns", "lineaftermarks"] - ) { formatter in - guard !formatter.options.fragment else { return } - - formatter.mapRecursiveDeclarations { declaration in - switch declaration { - // Organize the body of type declarations - case let .type(kind, open, body, close, originalRange): - let organizedType = formatter.organizeDeclaration((kind, open, body, close)) - return .type( - kind: organizedType.kind, - open: organizedType.open, - body: organizedType.body, - close: organizedType.close, - originalRange: originalRange - ) - - case .conditionalCompilation, .declaration: - return declaration - } - } - } - - public let extensionAccessControl = FormatRule( - help: "Configure the placement of an extension's access control keyword.", - options: ["extensionacl"] - ) { formatter in - guard !formatter.options.fragment else { return } - - let declarations = formatter.parseDeclarations() - let updatedDeclarations = formatter.mapRecursiveDeclarations(declarations) { declaration, _ in - guard case let .type("extension", open, body, close, _) = declaration else { - return declaration - } - - let visibilityKeyword = formatter.visibility(of: declaration) - // `private` visibility at top level of file is equivalent to `fileprivate` - let extensionVisibility = (visibilityKeyword == .private) ? .fileprivate : visibilityKeyword - - switch formatter.options.extensionACLPlacement { - // If all declarations in the extension have the same visibility, - // remove the keyword from the individual declarations and - // place it on the extension itself. - case .onExtension: - if extensionVisibility == nil, - let delimiterIndex = declaration.openTokens.firstIndex(of: .delimiter(":")), - declaration.openTokens.firstIndex(of: .keyword("where")).map({ $0 > delimiterIndex }) ?? true - { - // Extension adds protocol conformance so can't have visibility modifier - return declaration - } - - let visibilityOfBodyDeclarations = formatter - .mapDeclarations(body) { - formatter.visibility(of: $0) ?? extensionVisibility ?? .internal - } - .compactMap { $0 } - - let counts = Set(visibilityOfBodyDeclarations).sorted().map { visibility in - (visibility, count: visibilityOfBodyDeclarations.filter { $0 == visibility }.count) - } - - guard let memberVisibility = counts.max(by: { $0.count < $1.count })?.0, - memberVisibility <= extensionVisibility ?? .public, - // Check that most common level is also most visible - memberVisibility == visibilityOfBodyDeclarations.max(), - // `private` can't be hoisted without changing code behavior - // (private applied at extension level is equivalent to `fileprivate`) - memberVisibility > .private - else { return declaration } - - if memberVisibility > extensionVisibility ?? .internal { - // Check type being extended does not have lower visibility - for d in declarations where d.name == declaration.name { - if case let .type(kind, _, _, _, _) = d { - if kind != "extension", formatter.visibility(of: d) ?? .internal < memberVisibility { - // Cannot make extension with greater visibility than type being extended - return declaration - } - break - } - } - } - - let extensionWithUpdatedVisibility: Declaration - if memberVisibility == extensionVisibility || - (memberVisibility == .internal && visibilityKeyword == nil) - { - extensionWithUpdatedVisibility = declaration - } else { - extensionWithUpdatedVisibility = formatter.add(memberVisibility, to: declaration) - } - - return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in - let visibility = formatter.visibility(of: bodyDeclaration) - if memberVisibility > visibility ?? extensionVisibility ?? .internal { - if visibility == nil { - return formatter.add(.internal, to: bodyDeclaration) - } - return bodyDeclaration - } - return formatter.remove(memberVisibility, from: bodyDeclaration) - } - - // Move the extension's visibility keyword to each individual declaration - case .onDeclarations: - // If the extension visibility is unspecified then there isn't any work to do - guard let extensionVisibility = extensionVisibility else { - return declaration - } - - // Remove the visibility keyword from the extension declaration itself - let extensionWithUpdatedVisibility = formatter.remove(visibilityKeyword!, from: declaration) - - // And apply the extension's visibility to each of its child declarations - // that don't have an explicit visibility keyword - return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in - if formatter.visibility(of: bodyDeclaration) == nil { - // If there was no explicit visibility keyword, then this declaration - // was using the visibility of the extension itself. - return formatter.add(extensionVisibility, to: bodyDeclaration) - } else { - // Keep the existing visibility - return bodyDeclaration - } - } - } - } - - let updatedTokens = updatedDeclarations.flatMap { $0.tokens } - formatter.replaceTokens(in: formatter.tokens.indices, with: updatedTokens) - } - - public let markTypes = FormatRule( - help: "Add a MARK comment before top-level types and extensions.", - runOnceOnly: true, - disabledByDefault: true, - options: ["marktypes", "typemark", "markextensions", "extensionmark", "groupedextension"], - sharedOptions: ["lineaftermarks"] - ) { formatter in - var declarations = formatter.parseDeclarations() - - // Do nothing if there is only one top-level declaration in the file (excluding imports) - let declarationsWithoutImports = declarations.filter { $0.keyword != "import" } - guard declarationsWithoutImports.count > 1 else { - return - } - - for (index, declaration) in declarations.enumerated() { - guard case let .type(kind, open, body, close, _) = declaration else { continue } - - guard var typeName = declaration.name else { - continue - } - - let markMode: MarkMode - let commentTemplate: String - let isGroupedExtension: Bool - switch declaration.keyword { - case "extension": - // TODO: this should be stored in declaration at parse time - markMode = formatter.options.markExtensions - - // We provide separate mark comment customization points for - // extensions that are "grouped" with (e.g. following) their extending type, - // vs extensions that are completely separate. - // - // struct Foo { } - // extension Foo { } // This extension is "grouped" with its extending type - // extension String { } // This extension is standalone (not grouped with any type) - // - let isGroupedWithExtendingType: Bool - if let indexOfExtendingType = declarations[.. [Token] in - var openingFormatter = Formatter(openingTokens) - - guard let keywordIndex = openingFormatter.index(after: -1, where: { - $0.string == declaration.keyword - }) else { return openingTokens } - - // If this declaration is extension, check if it has any conformances - var conformanceNames: String? - if declaration.keyword == "extension", - var conformanceSearchIndex = openingFormatter.index(of: .delimiter(":"), after: keywordIndex) - { - var conformances = [String]() - - let endOfConformances = openingFormatter.index(of: .keyword("where"), after: keywordIndex) - ?? openingFormatter.index(of: .startOfScope("{"), after: keywordIndex) - ?? openingFormatter.tokens.count - - while let token = openingFormatter.token(at: conformanceSearchIndex), - conformanceSearchIndex < endOfConformances - { - if token.isIdentifier { - let (fullyQualifiedName, next) = openingFormatter.fullyQualifiedName(startingAt: conformanceSearchIndex) - conformances.append(fullyQualifiedName) - conformanceSearchIndex = next - } - - conformanceSearchIndex += 1 - } - - if !conformances.isEmpty { - conformanceNames = conformances.joined(separator: ", ") - } - } - - // Build the types expected mark comment by replacing `%t`s with the type name - // and `%c`s with the list of conformances added in the extension (if applicable) - var markForType: String? - - if !commentTemplate.contains("%c") { - markForType = commentTemplate.replacingOccurrences(of: "%t", with: typeName) - } else if commentTemplate.contains("%c"), let conformanceNames = conformanceNames { - markForType = commentTemplate - .replacingOccurrences(of: "%t", with: typeName) - .replacingOccurrences(of: "%c", with: conformanceNames) - } - - // If this is an extension without any conformances, but contains exactly - // one body declaration (a type), we can mark the extension with the nested type's name - // (e.g. `// MARK: Foo.Bar`). - if declaration.keyword == "extension", - conformanceNames == nil - { - // Find all of the nested extensions, so we can form the fully qualified - // name of the inner-most type (e.g. `Foo.Bar.Baaz.Quux`). - var extensions = [declaration] - - while let innerExtension = extensions.last, - let extensionBody = innerExtension.body, - extensionBody.count == 1, - extensionBody[0].keyword == "extension" - { - extensions.append(extensionBody[0]) - } - - let innermostExtension = extensions.last! - let extensionNames = extensions.compactMap { $0.name }.joined(separator: ".") - - if let extensionBody = innermostExtension.body, - extensionBody.count == 1, - let nestedType = extensionBody.first, - nestedType.definesType, - let nestedTypeName = nestedType.name - { - let fullyQualifiedName = "\(extensionNames).\(nestedTypeName)" - - if isGroupedExtension { - markForType = "// \(formatter.options.groupedExtensionMarkComment)" - .replacingOccurrences(of: "%c", with: fullyQualifiedName) - } else { - markForType = "// \(formatter.options.typeMarkComment)" - .replacingOccurrences(of: "%t", with: fullyQualifiedName) - } - } - } - - guard let expectedComment = markForType else { - return openingFormatter.tokens - } - - // Remove any lines that have the same prefix as the comment template - // - We can't really do exact matches here like we do for `organizeDeclaration` - // category separators, because there's a much wider variety of options - // that a user could use for the type name (orphaned renames, etc.) - var commentPrefixes = Set(["// MARK: ", "// MARK: - "]) - - if let typeNameSymbolIndex = commentTemplate.firstIndex(of: "%") { - commentPrefixes.insert(String(commentTemplate.prefix(upTo: typeNameSymbolIndex))) - } - - openingFormatter.forEach(.startOfScope("//")) { index, _ in - let startOfLine = openingFormatter.startOfLine(at: index) - let endOfLine = openingFormatter.endOfLine(at: index) - - let commentLine = sourceCode(for: Array(openingFormatter.tokens[index ... endOfLine])) - - for commentPrefix in commentPrefixes { - if commentLine.lowercased().hasPrefix(commentPrefix.lowercased()) { - // If we found a line that matched the comment prefix, - // remove it and any linebreak immediately after it. - if openingFormatter.token(at: endOfLine + 1)?.isLinebreak == true { - openingFormatter.removeToken(at: endOfLine + 1) - } - - openingFormatter.removeTokens(in: startOfLine ... endOfLine) - break - } - } - } - - // When inserting a mark before the first declaration, - // we should make sure we place it _after_ the file header. - var markInsertIndex = 0 - if index == 0 { - // Search for the end of the file header, which ends when we hit a - // blank line or any non-space/comment/lintbreak - var endOfFileHeader = 0 - - while openingFormatter.token(at: endOfFileHeader)?.isSpaceOrCommentOrLinebreak == true { - endOfFileHeader += 1 - - if openingFormatter.token(at: endOfFileHeader)?.isLinebreak == true, - openingFormatter.next(.nonSpace, after: endOfFileHeader)?.isLinebreak == true - { - markInsertIndex = endOfFileHeader + 2 - break - } - } - } - - // Insert the expected comment at the start of the declaration - let endMarkDeclaration = formatter.options.lineAfterMarks ? "\n\n" : "\n" - openingFormatter.insert(tokenize("\(expectedComment)\(endMarkDeclaration)"), at: markInsertIndex) - - // If the previous declaration doesn't end in a blank line, - // add an additional linebreak to balance the mark. - if index != 0 { - declarations[index - 1] = formatter.mapClosingTokens(in: declarations[index - 1]) { - formatter.endingWithBlankLine($0) - } - } - - return openingFormatter.tokens - } - } - - let updatedTokens = declarations.flatMap { $0.tokens } - formatter.replaceTokens(in: 0 ..< formatter.tokens.count, with: updatedTokens) - } - - public let sortDeclarations = FormatRule( - help: """ - Sorts the body of declarations with // swiftformat:sort - and declarations between // swiftformat:sort:begin and - // swiftformat:sort:end comments. - """, - options: ["sortedpatterns"], - sharedOptions: ["organizetypes"] - ) { formatter in - formatter.forEachToken( - where: { - $0.isCommentBody && $0.string.contains("swiftformat:sort") - || $0.isDeclarationTypeKeyword(including: Array(Token.swiftTypeKeywords)) - } - ) { index, token in - - let rangeToSort: ClosedRange - let numberOfLeadingLinebreaks: Int - - // For `:sort:begin`, directives, we sort the declarations - // between the `:begin` and and `:end` comments - let shouldBePartiallySorted = token.string.contains("swiftformat:sort:begin") - - let identifier = formatter.next(.identifier, after: index) - let shouldBeSortedByNamePattern = formatter.options.alphabeticallySortedDeclarationPatterns.contains { - identifier?.string.contains($0) ?? false - } - let shouldBeSortedByMarkComment = token.isCommentBody && !token.string.contains(":sort:") - // For `:sort` directives and types with matching name pattern, we sort the declarations - // between the open and close brace of the following type - let shouldBeFullySorted = shouldBeSortedByNamePattern || shouldBeSortedByMarkComment - - if shouldBePartiallySorted { - guard let endCommentIndex = formatter.tokens[index...].firstIndex(where: { - $0.isComment && $0.string.contains("swiftformat:sort:end") - }), - let sortRangeStart = formatter.index(of: .nonSpaceOrComment, after: index), - let firstRangeToken = formatter.index(of: .nonLinebreak, after: sortRangeStart), - let lastRangeToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: endCommentIndex - 2) - else { return } - - rangeToSort = sortRangeStart ... lastRangeToken - numberOfLeadingLinebreaks = firstRangeToken - sortRangeStart - } else if shouldBeFullySorted { - guard let typeOpenBrace = formatter.index(of: .startOfScope("{"), after: index), - let typeCloseBrace = formatter.endOfScope(at: typeOpenBrace), - let firstTypeBodyToken = formatter.index(of: .nonLinebreak, after: typeOpenBrace), - let lastTypeBodyToken = formatter.index(of: .nonLinebreak, before: typeCloseBrace), - let declarationKeyword = formatter.lastSignificantKeyword(at: typeOpenBrace), - lastTypeBodyToken > typeOpenBrace - else { return } - - // Sorting the body of a type conflicts with the `organizeDeclarations` - // keyword if enabled for this type of declaration. In that case, - // defer to the sorting implementation in `organizeDeclarations`. - if formatter.options.enabledRules.contains(FormatRules.organizeDeclarations.name), - formatter.options.organizeTypes.contains(declarationKeyword) - { - return - } - - rangeToSort = firstTypeBodyToken ... lastTypeBodyToken - // We don't include any leading linebreaks in the range to sort, - // since `firstTypeBodyToken` is the first `nonLinebreak` in the body - numberOfLeadingLinebreaks = 0 - } else { - return - } - - var declarations = Formatter(Array(formatter.tokens[rangeToSort])) - .parseDeclarations() - .enumerated() - .sorted(by: { lhs, rhs -> Bool in - let (lhsIndex, lhsDeclaration) = lhs - let (rhsIndex, rhsDeclaration) = rhs - - // Primarily sort by name, to alphabetize - if let lhsName = lhsDeclaration.name, - let rhsName = rhsDeclaration.name, - lhsName != rhsName - { - return lhsName.localizedCompare(rhsName) == .orderedAscending - } - - // Otherwise preserve the existing order - else { - return lhsIndex < rhsIndex - } - - }) - .map { $0.element } - - // Make sure there's at least one newline between each declaration - for i in 0 ..< max(0, declarations.count - 1) { - let declaration = declarations[i] - let nextDeclaration = declarations[i + 1] - - if declaration.tokens.last?.isLinebreak == false, - nextDeclaration.tokens.first?.isLinebreak == false - { - declarations[i + 1] = formatter.mapOpeningTokens(in: nextDeclaration) { openTokens in - let openFormatter = Formatter(openTokens) - openFormatter.insertLinebreak(at: 0) - return openFormatter.tokens - } - } - } - - var sortedFormatter = Formatter(declarations.flatMap { $0.tokens }) - - // Make sure the type has the same number of leading line breaks - // as it did before sorting - if let currentLeadingLinebreakCount = sortedFormatter.tokens.firstIndex(where: { !$0.isLinebreak }) { - if numberOfLeadingLinebreaks != currentLeadingLinebreakCount { - sortedFormatter.removeTokens(in: 0 ..< currentLeadingLinebreakCount) - - for _ in 0 ..< numberOfLeadingLinebreaks { - sortedFormatter.insertLinebreak(at: 0) - } - } - - } else { - for _ in 0 ..< numberOfLeadingLinebreaks { - sortedFormatter.insertLinebreak(at: 0) - } - } - - // There are always expected to be zero trailing line breaks, - // so we remove any trailing line breaks - // (this is because `typeBodyRange` specifically ends before the first - // trailing linebreak) - while sortedFormatter.tokens.last?.isLinebreak == true { - sortedFormatter.removeLastToken() - } - - if Array(formatter.tokens[rangeToSort]) != sortedFormatter.tokens { - formatter.replaceTokens( - in: rangeToSort, - with: sortedFormatter.tokens - ) - } - } - } - - public let assertionFailures = FormatRule( - help: """ - Changes all instances of assert(false, ...) to assertionFailure(...) - and precondition(false, ...) to preconditionFailure(...). - """ - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .identifier("assert"), .identifier("precondition"): - guard let scopeStart = formatter.index(of: .nonSpace, after: i, if: { - $0 == .startOfScope("(") - }), let identifierIndex = formatter.index(of: .nonSpaceOrLinebreak, after: scopeStart, if: { - $0 == .identifier("false") - }), var endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: identifierIndex) else { - return - } - - // if there are more arguments, replace the comma and space as well - if formatter.tokens[endIndex] == .delimiter(",") { - endIndex = formatter.index(of: .nonSpace, after: endIndex) ?? endIndex - } - - let replacements = ["assert": "assertionFailure", "precondition": "preconditionFailure"] - formatter.replaceTokens(in: i ..< endIndex, with: [ - .identifier(replacements[token.string]!), .startOfScope("("), - ]) - default: - break - } - } - } - - public let acronyms = FormatRule( - help: "Capitalize acronyms when the first character is capitalized.", - disabledByDefault: true, - options: ["acronyms"] - ) { formatter in - formatter.forEachToken { index, token in - guard token.is(.identifier) || token.isComment else { return } - - var updatedText = token.string - - for acronym in formatter.options.acronyms { - let find = acronym.capitalized - let replace = acronym.uppercased() - - for replaceCandidateRange in token.string.ranges(of: find) { - let acronymShouldBeCapitalized: Bool - - if replaceCandidateRange.upperBound < token.string.indices.last! { - let indexAfterMatch = replaceCandidateRange.upperBound - let characterAfterMatch = token.string[indexAfterMatch] - - // Only treat this as an acronym if the next character is uppercased, - // to prevent "Id" from matching strings like "Identifier". - if characterAfterMatch.isUppercase || characterAfterMatch.isWhitespace { - acronymShouldBeCapitalized = true - } - - // But if the next character is 's', and then the character after the 's' is uppercase, - // allow the acronym to be capitalized (to handle the plural case, `Ids` to `IDs`) - else if characterAfterMatch == Character("s") { - if indexAfterMatch < token.string.indices.last! { - let characterAfterNext = token.string[token.string.index(after: indexAfterMatch)] - acronymShouldBeCapitalized = (characterAfterNext.isUppercase || characterAfterNext.isWhitespace) - } else { - acronymShouldBeCapitalized = true - } - } else { - acronymShouldBeCapitalized = false - } - } else { - acronymShouldBeCapitalized = true - } - - if acronymShouldBeCapitalized { - updatedText.replaceSubrange(replaceCandidateRange, with: replace) - } - } - } - - if token.string != updatedText { - let updatedToken: Token - switch token { - case .identifier: - updatedToken = .identifier(updatedText) - case .commentBody: - updatedToken = .commentBody(updatedText) - default: - return - } - - formatter.replaceToken(at: index, with: updatedToken) - } - } - } - - public let blockComments = FormatRule( - help: "Convert block comments to consecutive single line comments.", - disabledByDefault: true - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .startOfScope("/*"): - guard var endIndex = formatter.endOfScope(at: i) else { - return formatter.fatalError("Expected */", at: i) - } - - // We can only convert block comments to single-line comments - // if there are no non-comment tokens on the same line. - // - For example, we can't convert `if foo { /* code */ }` - // to a line comment because it would comment out the closing brace. - // - // To guard against this, we verify that there is only - // comment or whitespace tokens on the remainder of this line - guard formatter.next(.nonSpace, after: endIndex)?.isLinebreak != false else { - return - } - - var isDocComment = false - var stripLeadingStars = true - func replaceCommentBody(at index: Int) -> Int { - var delta = 0 - var space = "" - if case let .space(s) = formatter.tokens[index] { - formatter.removeToken(at: index) - space = s - delta -= 1 - } - if case let .commentBody(body)? = formatter.token(at: index) { - var body = Substring(body) - if stripLeadingStars { - if body.hasPrefix("*") { - body = body.drop(while: { $0 == "*" }) - } else { - stripLeadingStars = false - } - } - let prefix = isDocComment ? "/" : "" - if !prefix.isEmpty || !body.isEmpty, !body.hasPrefix(" ") { - space += " " - } - formatter.replaceToken( - at: index, - with: .commentBody(prefix + space + body) - ) - } else if isDocComment { - formatter.insert(.commentBody("/"), at: index) - delta += 1 - } - return delta - } - - // Replace opening delimiter - var startIndex = i - let indent = formatter.currentIndentForLine(at: i) - if case let .commentBody(body) = formatter.tokens[i + 1] { - isDocComment = body.hasPrefix("*") - let commentBody = body.drop(while: { $0 == "*" }) - formatter.replaceToken(at: i + 1, with: .commentBody("/" + commentBody)) - } - formatter.replaceToken(at: i, with: .startOfScope("//")) - if let nextToken = formatter.token(at: i + 1), - nextToken.isSpaceOrLinebreak || nextToken.string == (isDocComment ? "/" : ""), - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i + 1), - nextIndex > i + 2 - { - let range = i + 1 ..< nextIndex - formatter.removeTokens(in: range) - endIndex -= range.count - startIndex = i + 1 - endIndex += replaceCommentBody(at: startIndex) - } - - // Replace ending delimiter - if let i = formatter.index(of: .nonSpace, before: endIndex, if: { - $0.isLinebreak - }) { - let range = i ... endIndex - formatter.removeTokens(in: range) - endIndex -= range.count - } - - // remove /* and */ - var index = i - while index <= endIndex { - switch formatter.tokens[index] { - case .startOfScope("/*"): - formatter.removeToken(at: index) - endIndex -= 1 - if formatter.tokens[index - 1].isSpace { - formatter.removeToken(at: index - 1) - index -= 1 - endIndex -= 1 - } - case .endOfScope("*/"): - formatter.removeToken(at: index) - endIndex -= 1 - if formatter.tokens[index - 1].isSpace { - formatter.removeToken(at: index - 1) - index -= 1 - endIndex -= 1 - } - case .linebreak: - endIndex += formatter.insertSpace(indent, at: index + 1) - guard let i = formatter.index(of: .nonSpace, after: index) else { - index += 1 - continue - } - index = i - formatter.insert(.startOfScope("//"), at: index) - var delta = 1 + replaceCommentBody(at: index + 1) - index += delta - endIndex += delta - default: - index += 1 - } - } - default: - break - } - } - } - - public let redundantClosure = FormatRule( - help: """ - Removes redundant closures bodies, containing a single statement, - which are called immediately. - """, - disabledByDefault: false, - orderAfter: ["redundantReturn"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { closureStartIndex, _ in - var startIndex = closureStartIndex - if formatter.isStartOfClosure(at: closureStartIndex), - var closureEndIndex = formatter.endOfScope(at: closureStartIndex), - // Closures that are called immediately are redundant - // (as long as there's exactly one statement inside them) - var closureCallOpenParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureEndIndex), - var closureCallCloseParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureCallOpenParenIndex), - formatter.token(at: closureCallOpenParenIndex) == .startOfScope("("), - formatter.token(at: closureCallCloseParenIndex) == .endOfScope(")"), - // Make sure to exclude closures that are completely empty, - // because removing them could break the build. - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex) != closureEndIndex - { - /// Whether or not this closure has a single, simple expression in its body. - /// These closures can always be simplified / removed regardless of the context. - let hasSingleSimpleExpression = formatter.blockBodyHasSingleStatement( - atStartOfScope: closureStartIndex, - includingConditionalStatements: false, - includingReturnStatements: true - ) - - /// Whether or not this closure has a single if/switch expression in its body. - /// Since if/switch expressions are only valid in the `return` position or as an `=` assignment, - /// these closures can only sometimes be simplified / removed. - let hasSingleConditionalExpression = !hasSingleSimpleExpression && - formatter.blockBodyHasSingleStatement( - atStartOfScope: closureStartIndex, - includingConditionalStatements: true, - includingReturnStatements: true, - includingReturnInConditionalStatements: false - ) - - guard hasSingleSimpleExpression || hasSingleConditionalExpression else { - return - } - - // This rule also doesn't support closures with an `in` token. - // - We can't just remove this, because it could have important type information. - // For example, `let double = { () -> Double in 100 }()` and `let double = 100` have different types. - // - We could theoretically support more sophisticated checks / transforms here, - // but this seems like an edge case so we choose not to handle it. - for inIndex in closureStartIndex ... closureEndIndex - where formatter.token(at: inIndex) == .keyword("in") - { - if !formatter.indexIsWithinNestedClosure(inIndex, startOfScopeIndex: closureStartIndex) { - return - } - } - - // If the closure calls a single function, which throws or returns `Never`, - // then removing the closure will cause a compilation failure. - // - We maintain a list of known functions that return `Never`. - // We could expand this to be user-provided if necessary. - for i in closureStartIndex ... closureEndIndex { - switch formatter.tokens[i] { - case .identifier("fatalError"), .identifier("preconditionFailure"), .keyword("throw"): - if !formatter.indexIsWithinNestedClosure(i, startOfScopeIndex: closureStartIndex) { - return - } - default: - break - } - } - - // If closure is preceded by try and/or await then remove those too - if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { - $0 == .keyword("await") - }) { - startIndex = prevIndex - } - if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { - $0 == .keyword("try") - }) { - startIndex = prevIndex - } - - // Since if/switch expressions are only valid in the `return` position or as an `=` assignment, - // these closures can only sometimes be simplified / removed. - if hasSingleConditionalExpression { - // Find the `{` start of scope or `=` and verify that the entire following expression consists of just this closure. - var startOfScopeContainingClosure = formatter.startOfScope(at: startIndex) - var assignmentBeforeClosure = formatter.index(of: .operator("=", .infix), before: startIndex) - - if let assignmentBeforeClosure = assignmentBeforeClosure, formatter.isConditionalStatement(at: assignmentBeforeClosure) { - // Not valid to use conditional expression directly in condition body - return - } - - let potentialStartOfExpressionContainingClosure: Int? - switch (startOfScopeContainingClosure, assignmentBeforeClosure) { - case (nil, nil): - potentialStartOfExpressionContainingClosure = nil - case (.some(let startOfScope), nil): - guard formatter.tokens[startOfScope] == .startOfScope("{") else { return } - potentialStartOfExpressionContainingClosure = startOfScope - case (nil, let .some(assignmentBeforeClosure)): - potentialStartOfExpressionContainingClosure = assignmentBeforeClosure - case let (.some(startOfScope), .some(assignmentBeforeClosure)): - potentialStartOfExpressionContainingClosure = max(startOfScope, assignmentBeforeClosure) - } - - if let potentialStartOfExpressionContainingClosure = potentialStartOfExpressionContainingClosure { - guard var startOfExpressionIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: potentialStartOfExpressionContainingClosure) - else { return } - - // Skip over any return token that may be present - if formatter.tokens[startOfExpressionIndex] == .keyword("return"), - let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfExpressionIndex) - { - startOfExpressionIndex = nextTokenIndex - } - - // Parse the expression and require that entire expression is simply just this closure. - guard let expressionRange = formatter.parseExpressionRange(startingAt: startOfExpressionIndex), - expressionRange == startIndex ... closureCallCloseParenIndex - else { return } - } - } - - // If the closure is a property with an explicit `Void` type, - // we can't remove the closure since the build would break - // if the method is `@discardableResult` - // https://github.com/nicklockwood/SwiftFormat/issues/1236 - if let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex), - formatter.token(at: equalsIndex) == .operator("=", .infix), - let colonIndex = formatter.index(of: .delimiter(":"), before: equalsIndex), - let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), - formatter.endOfVoidType(at: nextIndex) != nil - { - return - } - - // First we remove the spaces and linebreaks between the { } and the remainder of the closure body - // - This requires a bit of bookkeeping, but makes sure we don't remove any - // whitespace characters outside of the closure itself - while formatter.token(at: closureStartIndex + 1)?.isSpaceOrLinebreak == true { - formatter.removeToken(at: closureStartIndex + 1) - - closureCallOpenParenIndex -= 1 - closureCallCloseParenIndex -= 1 - closureEndIndex -= 1 - } - - while formatter.token(at: closureEndIndex - 1)?.isSpaceOrLinebreak == true { - formatter.removeToken(at: closureEndIndex - 1) - - closureCallOpenParenIndex -= 1 - closureCallCloseParenIndex -= 1 - closureEndIndex -= 1 - } - - // remove the trailing }() tokens, working backwards to not invalidate any indices - formatter.removeToken(at: closureCallCloseParenIndex) - formatter.removeToken(at: closureCallOpenParenIndex) - formatter.removeToken(at: closureEndIndex) - - // Remove the initial return token, and any trailing space, if present. - if let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex), - formatter.token(at: returnIndex)?.string == "return" - { - while formatter.token(at: returnIndex + 1)?.isSpaceOrLinebreak == true { - formatter.removeToken(at: returnIndex + 1) - } - - formatter.removeToken(at: returnIndex) - } - - // Finally, remove then open `{` token - formatter.removeTokens(in: startIndex ... closureStartIndex) - } - } - } - - public let redundantOptionalBinding = FormatRule( - help: "Remove redundant identifiers in optional binding conditions.", - // We can convert `if let foo = self.foo` to just `if let foo`, - // but only if `redundantSelf` can first remove the `self.`. - orderAfter: ["redundantSelf"] - ) { formatter in - formatter.forEachToken { i, token in - // `if let foo` conditions were added in Swift 5.7 (SE-0345) - if formatter.options.swiftVersion >= "5.7", - - [.keyword("let"), .keyword("var")].contains(token), - formatter.isConditionalStatement(at: i), - - let identiferIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - let identifier = formatter.token(at: identiferIndex), - - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: identiferIndex, if: { - $0 == .operator("=", .infix) - }), - - let nextIdentifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex, if: { - $0 == identifier - }), - - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIdentifierIndex), - [.startOfScope("{"), .delimiter(","), .keyword("else")].contains(nextToken) - { - formatter.removeTokens(in: identiferIndex + 1 ... nextIdentifierIndex) - } - } - } - - public let opaqueGenericParameters = FormatRule( - help: """ - Use opaque generic parameters (`some Protocol`) instead of generic parameters - with constraints (`T where T: Protocol`, etc) where equivalent. Also supports - primary associated types for common standard library types, so definitions like - `T where T: Collection, T.Element == Foo` are updated to `some Collection`. - """, - options: ["someany"] - ) { formatter in - formatter.forEach(.keyword) { keywordIndex, keyword in - guard // Opaque generic parameter syntax is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - // Apply this rule to any function-like declaration - [.keyword("func"), .keyword("init"), .keyword("subscript")].contains(keyword), - // Validate that this is a generic method using angle bracket syntax, - // and find the indices for all of the key tokens - let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), - let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), - let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), - let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - genericSignatureStartIndex < paramListStartIndex, - genericSignatureEndIndex < paramListStartIndex, - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), - let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) - else { return } - - var genericTypes = [Formatter.GenericType]() - - // Parse the generics in the angle brackets (e.g. ``) - formatter.parseGenericTypes( - from: genericSignatureStartIndex, - to: genericSignatureEndIndex, - into: &genericTypes - ) - - // Parse additional conformances and constraints after the `where` keyword if present - // (e.g. `where Foo: Fooable, Foo.Bar: Barable, Foo.Baaz == Baazable`) - var whereTokenIndex: Int? - if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), - whereIndex < openBraceIndex - { - whereTokenIndex = whereIndex - formatter.parseGenericTypes(from: whereIndex, to: openBraceIndex, into: &genericTypes) - } - - // Parse the return type if present - var arrowTokenIndex: Int? - var returnTypeTokens: [Token]? - if let arrowIndex = formatter.index(of: .operator("->", .infix), after: paramListEndIndex), - arrowIndex < openBraceIndex, arrowIndex < whereTokenIndex ?? openBraceIndex - { - arrowTokenIndex = arrowIndex - let returnTypeRange = (arrowIndex + 1) ..< (whereTokenIndex ?? openBraceIndex) - returnTypeTokens = Array(formatter.tokens[returnTypeRange]) - } - - // Parse thrown error type if present - var errorTypeTokens: [Token]? - if let throwsIndex = formatter.index(of: .keyword("throws"), after: paramListEndIndex), - throwsIndex < arrowTokenIndex ?? whereTokenIndex ?? openBraceIndex, - let openParenIndex = formatter.index(of: .nonSpace, after: throwsIndex, if: { - $0 == .startOfScope("(") - }), - let closeParenIndex = formatter.endOfScope(at: openParenIndex) - { - let errorTypeRange = (openParenIndex + 1) ..< closeParenIndex - errorTypeTokens = Array(formatter.tokens[errorTypeRange]) - } - - let genericParameterListRange = (genericSignatureStartIndex + 1) ..< genericSignatureEndIndex - let genericParameterListTokens = formatter.tokens[genericParameterListRange] - - let parameterListRange = (paramListStartIndex + 1) ..< paramListEndIndex - let parameterListTokens = formatter.tokens[parameterListRange] - - let bodyRange = (openBraceIndex + 1) ..< closeBraceIndex - let bodyTokens = formatter.tokens[bodyRange] - - for genericType in genericTypes { - // If the generic type doesn't occur in the generic parameter list (<...>), - // then we inherited it from the generic context and can't replace the type - // with an opaque parameter. - if !genericParameterListTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - continue - } - - // We can only remove the generic type if it appears exactly once in the parameter list. - // - If the generic type occurs _multiple_ times in the parameter list, - // it isn't eligible to be removed. For example `(T, T) where T: Foo` - // requires the two params to be the same underlying type, but - // `(some Foo, some Foo)` does not. - // - If the generic type occurs _zero_ times in the parameter list - // then removing the generic parameter would also remove any - // potentially-important constraints (for example, if the type isn't - // used in the function parameters / body and is only constrained relative - // to generic types in the parent type scope). If this generic parameter - // is truly unused and redundant then the compiler would emit an error. - let countInParameterList = parameterListTokens.filter { $0.string == genericType.name }.count - if countInParameterList != 1 { - genericType.eligibleToRemove = false - continue - } - - // If the generic type occurs in the body of the function, then it can't be removed - if bodyTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - continue - } - - // If the generic type is referenced in any attributes, then it can't be removed - let startOfModifiers = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) - let modifierTokens = formatter.tokens[startOfModifiers ..< keywordIndex] - if modifierTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - continue - } - - // If the generic type is used in a constraint of any other generic type, then the type - // can't be removed without breaking that other type - let otherGenericTypes = genericTypes.filter { $0.name != genericType.name } - let otherTypeConformances = otherGenericTypes.flatMap { $0.conformances } - for otherTypeConformance in otherTypeConformances { - let conformanceTokens = formatter.tokens[otherTypeConformance.sourceRange] - if conformanceTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - } - } - - // In some weird cases you can also have a generic constraint that references a generic - // type from the parent context with the same name. We can't change these, since it - // can cause the build to break - for conformance in genericType.conformances { - if tokenize(conformance.name).contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - } - } - - // A generic used as a return type is different from an opaque result type (SE-244). - // For example in `-> T where T: Fooable`, the generic type is caller-specified, - // but with `-> some Fooable` the generic type is specified by the function implementation. - // Because those represent different concepts, we can't convert between them, - // so have to mark the generic type as ineligible if it appears in the return type. - if let returnTypeTokens = returnTypeTokens, - returnTypeTokens.contains(where: { $0.string == genericType.name }) - { - genericType.eligibleToRemove = false - continue - } - - // https://github.com/nicklockwood/SwiftFormat/issues/1845 - if let errorTypeTokens = errorTypeTokens, - errorTypeTokens.contains(.identifier(genericType.name)) - { - genericType.eligibleToRemove = false - continue - } - - // If the method that generates the opaque parameter syntax doesn't succeed, - // then this type is ineligible (because it used a generic constraint that - // can't be represented using this syntax). - // TODO: this option probably needs to be captured earlier to support comment directives - if genericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) == nil { - genericType.eligibleToRemove = false - continue - } - - // If the generic type is used as a closure type parameter, it can't be removed or the compiler - // will emit a "'some' cannot appear in parameter position in parameter type " error - for tokenIndex in keywordIndex ... closeBraceIndex { - // Check if this is the start of a closure - if formatter.tokens[tokenIndex] == .startOfScope("("), - tokenIndex != paramListStartIndex, - let endOfScope = formatter.endOfScope(at: tokenIndex), - let tokenAfterParen = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfScope), - [.operator("->", .infix), .keyword("throws"), .identifier("async")].contains(tokenAfterParen), - // Check if the closure type parameters contains this generic type - formatter.tokens[tokenIndex ... endOfScope].contains(where: { $0.string == genericType.name }) - { - genericType.eligibleToRemove = false - } - } - - // Extract the comma-separated list of function parameters, - // so we can check conditions on the individual parameters - let parameterListTokenIndices = (paramListStartIndex + 1) ..< paramListEndIndex - - // Split the parameter list at each comma that's directly within the paren list scope - let parameters = parameterListTokenIndices - .split(whereSeparator: { index in - let token = formatter.tokens[index] - return token == .delimiter(",") - && formatter.endOfScope(at: index) == paramListEndIndex - }) - .map { parameterIndices in - parameterIndices.map { index in - formatter.tokens[index] - } - } - - for parameterTokens in parameters { - // Variadic parameters don't support opaque generic syntax, so we have to check - // if any use cases of this type in the parameter list are variadic - if parameterTokens.contains(.operator("...", .postfix)), - parameterTokens.contains(.identifier(genericType.name)) - { - genericType.eligibleToRemove = false - } - } - } - - let genericsEligibleToRemove = genericTypes.filter { $0.eligibleToRemove } - let sourceRangesToRemove = Set(genericsEligibleToRemove.flatMap { type in - [type.definitionSourceRange] + type.conformances.map { $0.sourceRange } - }) - - // We perform modifications to the function signature in reverse order - // so we don't invalidate any of the indices we've recorded. So first - // we remove components of the where clause. - if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), - whereIndex < openBraceIndex - { - let whereClauseSourceRanges = sourceRangesToRemove.filter { $0.lowerBound > whereIndex } - formatter.removeTokens(in: Array(whereClauseSourceRanges)) - - if let newOpenBraceIndex = formatter.index(of: .startOfScope("{"), after: whereIndex) { - // if where clause is completely empty, we need to remove the where token as well - if formatter.index(of: .nonSpaceOrLinebreak, after: whereIndex) == newOpenBraceIndex { - formatter.removeTokens(in: whereIndex ..< newOpenBraceIndex) - } - // remove trailing comma - else if let commaIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - before: newOpenBraceIndex, if: { $0 == .delimiter(",") } - ) { - formatter.removeToken(at: commaIndex) - if formatter.tokens[commaIndex - 1].isSpace, - formatter.tokens[commaIndex].isSpaceOrLinebreak - { - formatter.removeToken(at: commaIndex - 1) - } - } - } - } - - // Replace all of the uses of generic types that are eligible to remove - // with the corresponding opaque parameter declaration - for index in parameterListRange.reversed() { - if let matchingGenericType = genericsEligibleToRemove.first(where: { $0.name == formatter.tokens[index].string }), - var opaqueParameter = matchingGenericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) - { - // If this instance of the type is followed by a `.` or `?` then we have to wrap the new type in parens - // (e.g. changing `Foo.Type` to `some Any.Type` breaks the build, it needs to be `(some Any).Type`) - if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: index), - [.operator(".", .infix), .operator("?", .postfix)].contains(nextToken) - { - opaqueParameter.insert(.startOfScope("("), at: 0) - opaqueParameter.append(.endOfScope(")")) - } - - formatter.replaceToken(at: index, with: opaqueParameter) - } - } - - // Remove types from the generic parameter list - let genericParameterListSourceRanges = sourceRangesToRemove.filter { $0.lowerBound < genericSignatureEndIndex } - formatter.removeTokens(in: Array(genericParameterListSourceRanges)) - - // If we left a dangling comma at the end of the generic parameter list, we need to clean it up - if let newGenericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - let trailingCommaIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: newGenericSignatureEndIndex), - formatter.tokens[trailingCommaIndex] == .delimiter(",") - { - formatter.removeTokens(in: trailingCommaIndex ..< newGenericSignatureEndIndex) - } - - // If we removed all of the generic types, we also have to remove the angle brackets - if let newGenericSignatureEndIndex = formatter.index(of: .nonSpaceOrLinebreak, after: genericSignatureStartIndex), - formatter.token(at: newGenericSignatureEndIndex) == .endOfScope(">") - { - formatter.removeTokens(in: genericSignatureStartIndex ... newGenericSignatureEndIndex) - } - } - } - - public let genericExtensions = FormatRule( - help: """ - Use angle brackets (`extension Array`) for generic type extensions - instead of type constraints (`extension Array where Element == Foo`). - """, - options: ["generictypes"] - ) { formatter in - formatter.forEach(.keyword("extension")) { extensionIndex, _ in - guard // Angle brackets syntax in extensions is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - let typeNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: extensionIndex), - let extendedType = formatter.token(at: typeNameIndex)?.string, - // If there's already an open angle bracket after the generic type name - // then the extension is already using the target syntax, so there's - // no work to do - formatter.next(.nonSpaceOrCommentOrLinebreak, after: typeNameIndex) != .startOfScope("<"), - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: typeNameIndex), - let whereIndex = formatter.index(of: .keyword("where"), after: typeNameIndex), - whereIndex < openBraceIndex - else { return } - - // Prepopulate a `Self` generic type, which is implicitly present in extension definitions - let selfType = Formatter.GenericType( - name: "Self", - definitionSourceRange: typeNameIndex ... typeNameIndex, - conformances: [ - Formatter.GenericType.GenericConformance( - name: extendedType, - typeName: "Self", - type: .concreteType, - sourceRange: typeNameIndex ... typeNameIndex - ), - ] - ) - - var genericTypes = [selfType] - - // Parse the generic constraints in the where clause - formatter.parseGenericTypes( - from: whereIndex, - to: openBraceIndex, - into: &genericTypes, - qualifyGenericTypeName: { genericTypeName in - // In an extension all types implicitly refer to `Self`. - // For example, `Element == Foo` is actually fully-qualified as - // `Self.Element == Foo`. Using the fully-qualified `Self.Element` name - // here makes it so the generic constraint is populated as a child - // of `selfType`. - if !genericTypeName.hasPrefix("Self.") { - return "Self." + genericTypeName - } else { - return genericTypeName - } - } - ) - - var knownGenericTypes: [(name: String, genericTypes: [String])] = [ - (name: "Collection", genericTypes: ["Element"]), - (name: "Sequence", genericTypes: ["Element"]), - (name: "Array", genericTypes: ["Element"]), - (name: "Set", genericTypes: ["Element"]), - (name: "Dictionary", genericTypes: ["Key", "Value"]), - (name: "Optional", genericTypes: ["Wrapped"]), - ] - - // Users can provide additional generic types via the `generictypes` option - for userProvidedType in formatter.options.genericTypes.components(separatedBy: ";") { - guard let openAngleBracket = userProvidedType.firstIndex(of: "<"), - let closeAngleBracket = userProvidedType.firstIndex(of: ">") - else { continue } - - let typeName = String(userProvidedType[.. Bool { - // Check if this is a special type of comment that isn't documentation - if case let .commentBody(body)? = formatter.next(.nonSpace, after: index), body.isCommentDirective { - return false - } - - // Check if this token defines a declaration that supports doc comments - var declarationToken = formatter.tokens[nextDeclarationIndex] - if declarationToken.isAttribute || declarationToken.isModifierKeyword, - let index = formatter.index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword }) - { - declarationToken = formatter.tokens[index] - } - guard declarationToken.isDeclarationTypeKeyword(excluding: ["import"]) else { - return false - } - - // Only use doc comments on declarations in type bodies, or top-level declarations - if let startOfEnclosingScope = formatter.index(of: .startOfScope, before: index) { - switch formatter.tokens[startOfEnclosingScope] { - case .startOfScope("#if"): - break - case .startOfScope("{"): - guard let scope = formatter.lastSignificantKeyword(at: startOfEnclosingScope, excluding: ["where"]), - ["class", "actor", "struct", "enum", "protocol", "extension"].contains(scope) - else { - return false - } - default: - return false - } - } - - // If there are blank lines between comment and declaration, comment is not treated as doc comment - let trailingTokens = formatter.tokens[(endOfComment - 1) ... nextDeclarationIndex] - let lines = trailingTokens.split(omittingEmptySubsequences: false, whereSeparator: \.isLinebreak) - if lines.contains(where: { $0.allSatisfy(\.isSpace) }) { - return false - } - - // Only comments at the start of a line can be doc comments - if let previousToken = formatter.index(of: .nonSpaceOrLinebreak, before: index) { - let commentLine = formatter.startOfLine(at: index) - let previousTokenLine = formatter.startOfLine(at: previousToken) - - if commentLine == previousTokenLine { - return false - } - } - - // Comments inside conditional statements are not doc comments - return !formatter.isConditionalStatement(at: index) - } - - var commentIndices = [index] - if token == .startOfScope("//") { - var i = index - while let prevLineIndex = formatter.index(of: .linebreak, before: i), - case let lineStartIndex = formatter.startOfLine(at: prevLineIndex, excludingIndent: true), - formatter.token(at: lineStartIndex) == .startOfScope("//") - { - commentIndices.append(lineStartIndex) - i = lineStartIndex - } - i = index - while let nextLineIndex = formatter.index(of: .linebreak, after: i), - let lineStartIndex = formatter.index(of: .nonSpace, after: nextLineIndex), - formatter.token(at: lineStartIndex) == .startOfScope("//") - { - commentIndices.append(lineStartIndex) - i = lineStartIndex - } - } - - let useDocComment = shouldBeDocComment(at: index, endOfComment: endOfComment) - guard commentIndices.allSatisfy({ - shouldBeDocComment(at: $0, endOfComment: endOfComment) == useDocComment - }) else { - return - } - - // Determine whether or not this is the start of a list of sequential declarations, like: - // - // // The placeholder names we use in test cases - // case foo - // case bar - // case baaz - // - // In these cases it's not obvious whether or not the comment refers to the property or - // the entire group, so we preserve the existing formatting. - var preserveRegularComments = false - if useDocComment, - let declarationKeyword = formatter.index(after: endOfComment, where: \.isDeclarationTypeKeyword), - let endOfDeclaration = formatter.endOfDeclaration(atDeclarationKeyword: declarationKeyword, fallBackToEndOfScope: false), - let nextDeclarationKeyword = formatter.index(after: endOfDeclaration, where: \.isDeclarationTypeKeyword) - { - let linebreaksBetweenDeclarations = formatter.tokens[declarationKeyword ... nextDeclarationKeyword] - .filter { $0.isLinebreak }.count - - // If there is only a single line break between the start of this declaration and the subsequent declaration, - // then they are written sequentially in a block. In this case, don't convert regular comments to doc comments. - if linebreaksBetweenDeclarations == 1 { - preserveRegularComments = true - } - } - - // Doc comment tokens like `///` and `/**` aren't parsed as a - // single `.startOfScope` token -- they're parsed as: - // `.startOfScope("//"), .commentBody("/ ...")` or - // `.startOfScope("/*"), .commentBody("* ...")` - let startOfDocCommentBody: String - switch token.string { - case "//": - startOfDocCommentBody = "/" - case "/*": - startOfDocCommentBody = "*" - default: - return - } - - let isDocComment = formatter.isDocComment(startOfComment: index) - - if isDocComment, - let commentBody = formatter.token(at: index + 1), - commentBody.isCommentBody - { - if useDocComment, !isDocComment, !preserveRegularComments { - let updatedCommentBody = "\(startOfDocCommentBody)\(commentBody.string)" - formatter.replaceToken(at: index + 1, with: .commentBody(updatedCommentBody)) - } else if !useDocComment, isDocComment, !formatter.options.preserveDocComments { - let prefix = commentBody.string.prefix(while: { String($0) == startOfDocCommentBody }) - - // Do nothing if this is a unusual comment like `//////////////////` - // or `/****************`. We can't just remove one of the tokens, because - // that would make this rule have a different output each time, but we - // shouldn't remove all of them since that would be unexpected. - if prefix.count > 1 { - return - } - - formatter.replaceToken( - at: index + 1, - with: .commentBody(String(commentBody.string.dropFirst())) - ) - } - - } else if useDocComment, !preserveRegularComments { - formatter.insert(.commentBody(startOfDocCommentBody), at: index + 1) - } - } - } - - public let conditionalAssignment = FormatRule( - help: "Assign properties using if / switch expressions.", - orderAfter: ["redundantReturn"], - options: ["condassignment"] - ) { formatter in - // If / switch expressions were added in Swift 5.9 (SE-0380) - guard formatter.options.swiftVersion >= "5.9" else { - return - } - - formatter.forEach(.keyword) { startOfConditional, keywordToken in - // Look for an if/switch expression where the first branch starts with `identifier =` - guard ["if", "switch"].contains(keywordToken.string), - let conditionalBranches = formatter.conditionalBranches(at: startOfConditional), - var startOfFirstBranch = conditionalBranches.first?.startOfBranch - else { return } - - // Traverse any nested if/switch branches until we find the first code branch - while let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), - ["if", "switch"].contains(formatter.tokens[firstTokenInBranch].string), - let nestedConditionalBranches = formatter.conditionalBranches(at: firstTokenInBranch), - let startOfNestedBranch = nestedConditionalBranches.first?.startOfBranch - { - startOfFirstBranch = startOfNestedBranch - } - - // Check if the first branch starts with the pattern `lvalue =`. - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), - let lvalueRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: lvalueRange.upperBound), - formatter.tokens[equalsIndex] == .operator("=", .infix) - else { return } - - // Whether or not the conditional statement that starts at the given index - // has branches that are exhaustive - func conditionalBranchesAreExhaustive( - conditionKeywordIndex: Int, - branches: [Formatter.ConditionalBranch] - ) - -> Bool - { - // Switch statements are compiler-guaranteed to be exhaustive - if formatter.tokens[conditionKeywordIndex] == .keyword("switch") { - return true - } - - // If statements are only exhaustive if the last branch - // is `else` (not `else if`). - else if formatter.tokens[conditionKeywordIndex] == .keyword("if"), - let lastCondition = branches.last, - let tokenBeforeLastCondition = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lastCondition.startOfBranch) - { - return formatter.tokens[tokenBeforeLastCondition] == .keyword("else") - } - - return false - } - - // Whether or not the given conditional branch body qualifies as a single statement - // that assigns a value to `identifier`. This is either: - // 1. a single assignment to `lvalue =` - // 2. a single `if` or `switch` statement where each of the branches also qualify, - // and the statement is exhaustive. - func isExhaustiveSingleStatementAssignment(_ branch: Formatter.ConditionalBranch) -> Bool { - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return false } - - // If this is an if/switch statement, verify that all of the branches are also - // single-statement assignments and that the statement is exhaustive. - if let conditionalBranches = formatter.conditionalBranches(at: firstTokenIndex), - let lastConditionalStatement = conditionalBranches.last - { - let allBranchesAreExhaustiveSingleStatement = conditionalBranches.allSatisfy { branch in - isExhaustiveSingleStatementAssignment(branch) - } - - let isOnlyStatementInScope = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lastConditionalStatement.endOfBranch)?.isEndOfScope == true - - let isExhaustive = conditionalBranchesAreExhaustive( - conditionKeywordIndex: firstTokenIndex, - branches: conditionalBranches - ) - - return allBranchesAreExhaustiveSingleStatement - && isOnlyStatementInScope - && isExhaustive - } - - // Otherwise we expect this to be of the pattern `lvalue = (statement)` - else if let firstExpressionRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), - formatter.tokens[firstExpressionRange] == formatter.tokens[lvalueRange], - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), - formatter.tokens[equalsIndex] == .operator("=", .infix), - let valueStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) - { - // We know this branch starts with `identifier =`, but have to check that the - // remaining code in the branch is a single statement. To do that we can - // create a temporary formatter with the branch body _excluding_ `identifier =`. - let assignmentStatementRange = valueStartIndex ..< branch.endOfBranch - var tempScopeTokens = [Token]() - tempScopeTokens.append(.startOfScope("{")) - tempScopeTokens.append(contentsOf: formatter.tokens[assignmentStatementRange]) - tempScopeTokens.append(.endOfScope("}")) - - let tempFormatter = Formatter(tempScopeTokens, options: formatter.options) - guard tempFormatter.blockBodyHasSingleStatement( - atStartOfScope: 0, - includingConditionalStatements: true, - includingReturnStatements: false - ) else { - return false - } - - // In Swift 5.9, there's a bug that prevents you from writing an - // if or switch expression using an `as?` on one of the branches: - // https://github.com/apple/swift/issues/68764 - // - // let result = if condition { - // foo as? String - // } else { - // "bar" - // } - // - if tempFormatter.conditionalBranchHasUnsupportedCastOperator(startOfScopeIndex: 0) { - return false - } - - return true - } - - return false - } - - guard conditionalBranches.allSatisfy(isExhaustiveSingleStatementAssignment), - conditionalBranchesAreExhaustive(conditionKeywordIndex: startOfConditional, branches: conditionalBranches) - else { - return - } - - // Removes the `identifier =` from each conditional branch - func removeAssignmentFromAllBranches() { - formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch), - let firstExpressionRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), - formatter.tokens[equalsIndex] == .operator("=", .infix), - let valueStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) - else { return } - - formatter.removeTokens(in: firstTokenIndex ..< valueStartIndex) - } - } - - // If this expression follows a property like `let identifier: Type`, we just - // have to insert an `=` between property and the conditional. - // - Find the introducer (let/var), parse the property, and verify that the identifier - // matches the identifier assigned on each conditional branch. - if let introducerIndex = formatter.indexOfLastSignificantKeyword(at: startOfConditional, excluding: ["if", "switch"]), - ["let", "var"].contains(formatter.tokens[introducerIndex].string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - formatter.tokens[lvalueRange.lowerBound].string == property.identifier, - property.value == nil, - let typeRange = property.type?.range, - let nextTokenAfterProperty = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: typeRange.upperBound), - nextTokenAfterProperty == startOfConditional - { - removeAssignmentFromAllBranches() - - let rangeBetweenTypeAndConditional = (typeRange.upperBound + 1) ..< startOfConditional - - // If there are no comments between the type and conditional, - // we reformat it from: - // - // let foo: Foo\n - // if condition { - // - // to: - // - // let foo: Foo = if condition { - // - if formatter.tokens[rangeBetweenTypeAndConditional].allSatisfy(\.isSpaceOrLinebreak) { - formatter.replaceTokens(in: rangeBetweenTypeAndConditional, with: [ - .space(" "), - .operator("=", .infix), - .space(" "), - ]) - } - - // But if there are comments, then we shouldn't just delete them. - // Instead we just insert `= ` after the type. - else { - formatter.insert([.operator("=", .infix), .space(" ")], at: startOfConditional) - } - } - - // Otherwise we insert an `identifier =` before the if/switch expression - else if !formatter.options.conditionalAssignmentOnlyAfterNewProperties { - // In this case we should only apply the conversion if this is a top-level condition, - // and not nested in some parent condition. In large complex if/switch conditions - // with multiple layers of nesting, for example, this prevents us from making any - // changes unless the entire set of nested conditions can be converted as a unit. - // - First attempt to find and parse a parent if / switch condition. - var startOfParentScope = formatter.startOfScope(at: startOfConditional) - - // If we're inside a switch case, expand to look at the whole switch statement - while let currentStartOfParentScope = startOfParentScope, - formatter.tokens[currentStartOfParentScope] == .startOfScope(":"), - let caseToken = formatter.index(of: .endOfScope("case"), before: currentStartOfParentScope) - { - startOfParentScope = formatter.startOfScope(at: caseToken) - } - - if let startOfParentScope = startOfParentScope, - let mostRecentIfOrSwitch = formatter.index(of: .keyword, before: startOfParentScope, if: { ["if", "switch"].contains($0.string) }), - let conditionalBranches = formatter.conditionalBranches(at: mostRecentIfOrSwitch), - let startOfFirstParentBranch = conditionalBranches.first?.startOfBranch, - let endOfLastParentBranch = conditionalBranches.last?.endOfBranch, - // If this condition is contained within a parent condition, do nothing. - // We should only convert the entire set of nested conditions together as a unit. - (startOfFirstParentBranch ... endOfLastParentBranch).contains(startOfConditional) - { return } - - let lvalueTokens = formatter.tokens[lvalueRange] - - // Now we can remove the `identifier =` from each branch, - // and instead add it before the if / switch expression. - removeAssignmentFromAllBranches() - - let identifierEqualsTokens = lvalueTokens + [ - .space(" "), - .operator("=", .infix), - .space(" "), - ] - - formatter.insert(identifierEqualsTokens, at: startOfConditional) - } - } - } - - public let sortTypealiases = FormatRule( - help: "Sort protocol composition typealiases alphabetically." - ) { formatter in - formatter.forEach(.keyword("typealias")) { typealiasIndex, _ in - guard let (equalsIndex, andTokenIndices, endIndex) = formatter.parseProtocolCompositionTypealias(at: typealiasIndex), - let typealiasNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) - else { - return - } - - var seenTypes = Set() - - // Split the typealias into individual elements. - // Any comments on their own line are grouped with the following element. - let delimiters = [equalsIndex] + andTokenIndices - var parsedElements: [(startIndex: Int, delimiterIndex: Int, endIndex: Int, type: String, allTokens: [Token], isDuplicate: Bool)] = [] - - for delimiter in delimiters.indices { - let endOfPreviousElement = parsedElements.last?.endIndex ?? typealiasNameIndex - let elementStartIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfPreviousElement) ?? delimiters[delimiter] - - // Start with the end index just being the end of the type name - var elementEndIndex: Int - let nextElementIsOnSameLine: Bool - if delimiter == delimiters.indices.last { - elementEndIndex = endIndex - nextElementIsOnSameLine = false - } else { - let nextDelimiterIndex = delimiters[delimiter + 1] - elementEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: nextDelimiterIndex) ?? (nextDelimiterIndex - 1) - - let endOfLine = formatter.endOfLine(at: elementEndIndex) - nextElementIsOnSameLine = formatter.endOfLine(at: nextDelimiterIndex) == endOfLine - } - - // Handle comments in multiline typealiases - if !nextElementIsOnSameLine { - // Any comments on the same line as the type name should be considered part of this element. - // Any comments after the linebreak are consisidered part of the next element. - // To do that we just extend this element to the end of the current line. - elementEndIndex = formatter.endOfLine(at: elementEndIndex) - 1 - } - - let tokens = Array(formatter.tokens[elementStartIndex ... elementEndIndex]) - let typeName = tokens - .filter { !$0.isSpaceOrCommentOrLinebreak && !$0.isOperator } - .map { $0.string }.joined() - - // While we're here, also filter out any duplicates. - // Since we're sorting, duplicates would sit right next to each other - // which makes them especially obvious. - let isDuplicate = seenTypes.contains(typeName) - seenTypes.insert(typeName) - - parsedElements.append(( - startIndex: elementStartIndex, - delimiterIndex: delimiters[delimiter], - endIndex: elementEndIndex, - type: typeName, - allTokens: tokens, - isDuplicate: isDuplicate - )) - } - - // Sort each element by type name - var sortedElements = parsedElements.sorted(by: { lhsElement, rhsElement in - lhsElement.type.lexicographicallyPrecedes(rhsElement.type) - }) - - // Don't modify the file if the typealias is already sorted - if parsedElements.map(\.startIndex) == sortedElements.map(\.startIndex) { - return - } - - let firstNonDuplicateIndex = sortedElements.firstIndex(where: { !$0.isDuplicate }) - - for elementIndex in sortedElements.indices { - // Revalidate all of the delimiters after sorting - // (the first delimiter should be `=` and all others should be `&` - let delimiterIndexInTokens = sortedElements[elementIndex].delimiterIndex - sortedElements[elementIndex].startIndex - - if elementIndex == firstNonDuplicateIndex { - sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("=", .infix) - } else { - sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("&", .infix) - } - - // Make sure there's always a linebreak after any comments, to prevent - // them from accidentially commenting out following elements of the typealias - if elementIndex != sortedElements.indices.last, - sortedElements[elementIndex].allTokens.last?.isComment == true, - let nextToken = formatter.nextToken(after: parsedElements[elementIndex].endIndex), - !nextToken.isLinebreak - { - sortedElements[elementIndex].allTokens.append(.linebreak("\n", 0)) - } - - // If this element starts with a comment, that's because the comment - // was originally on a line all by itself. To preserve this, make sure - // there's a linebreak before the comment. - if elementIndex != sortedElements.indices.first, - sortedElements[elementIndex].allTokens.first?.isComment == true, - let previousToken = formatter.lastToken(before: parsedElements[elementIndex].startIndex, where: { !$0.isSpace }), - !previousToken.isLinebreak - { - sortedElements[elementIndex].allTokens.insert(.linebreak("\n", 0), at: 0) - } - } - - // Replace each index in the parsed list with the corresponding index in the sorted list, - // working backwards to not invalidate any existing indices - for (originalElement, newElement) in zip(parsedElements, sortedElements).reversed() { - if newElement.isDuplicate, let tokenBeforeElement = formatter.index(of: .nonSpaceOrLinebreak, before: originalElement.startIndex) { - formatter.removeTokens(in: (tokenBeforeElement + 1) ... originalElement.endIndex) - } else { - formatter.replaceTokens( - in: originalElement.startIndex ... originalElement.endIndex, - with: newElement.allTokens - ) - } - } - } - } - - public let redundantInternal = FormatRule( - help: "Remove redundant internal access control." - ) { formatter in - formatter.forEach(.keyword("internal")) { internalKeywordIndex, _ in - // Don't remove import acl - if formatter.next(.nonSpaceOrComment, after: internalKeywordIndex) == .keyword("import") { - return - } - - // If we're inside an extension, then `internal` is only redundant if the extension itself is `internal`. - if let startOfScope = formatter.startOfScope(at: internalKeywordIndex), - let typeKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScope, excluding: ["where"]), - formatter.tokens[typeKeywordIndex] == .keyword("extension"), - // In the language grammar, the ACL level always directly precedes the - // `extension` keyword if present. - let previousToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeKeywordIndex), - ["public", "package", "internal", "private", "fileprivate"].contains(previousToken.string), - previousToken.string != "internal" - { - // The extension has an explicit ACL other than `internal`, so is not internal. - // We can't remove the `internal` keyword since the declaration would change - // to the ACL of the extension. - return - } - - guard formatter.token(at: internalKeywordIndex + 1)?.isSpace == true else { return } - - formatter.removeTokens(in: internalKeywordIndex ... (internalKeywordIndex + 1)) - } - } - - public let preferForLoop = FormatRule( - help: "Convert functional `forEach` calls to for loops.", - options: ["anonymousforeach", "onelineforeach"] - ) { formatter in - formatter.forEach(.identifier("forEach")) { forEachIndex, _ in - // Make sure this is a function call preceded by a `.` - guard let functionCallDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: forEachIndex), - formatter.tokens[functionCallDotIndex] == .operator(".", .infix), - let indexAfterForEach = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: forEachIndex), - let indexBeforeFunctionCallDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: functionCallDotIndex) - else { return } - - // Parse either `{ ... }` or `({ ... })` - let forEachCallOpenParenIndex: Int? - let closureOpenBraceIndex: Int - let closureCloseBraceIndex: Int - let forEachCallCloseParenIndex: Int? - - switch formatter.tokens[indexAfterForEach] { - case .startOfScope("{"): - guard let endOfClosureScope = formatter.endOfScope(at: indexAfterForEach) else { return } - - forEachCallOpenParenIndex = nil - closureOpenBraceIndex = indexAfterForEach - closureCloseBraceIndex = endOfClosureScope - forEachCallCloseParenIndex = nil - - case .startOfScope("("): - guard let endOfFunctionCall = formatter.endOfScope(at: indexAfterForEach), - let indexAfterOpenParen = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterForEach), - formatter.tokens[indexAfterOpenParen] == .startOfScope("{"), - let endOfClosureScope = formatter.endOfScope(at: indexAfterOpenParen) - else { return } - - forEachCallOpenParenIndex = indexAfterForEach - closureOpenBraceIndex = indexAfterOpenParen - closureCloseBraceIndex = endOfClosureScope - forEachCallCloseParenIndex = endOfFunctionCall - - default: - return - } - - // Abort early for single-line loops - guard !formatter.options.preserveSingleLineForEach || formatter - .tokens[closureOpenBraceIndex ..< closureCloseBraceIndex].contains(where: { $0.isLinebreak }) - else { return } - - // Ignore closures with capture lists for now since they're rare - // in this context and add complexity - guard let firstIndexInClosureBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureOpenBraceIndex), - formatter.tokens[firstIndexInClosureBody] != .startOfScope("[") - else { return } - - // Parse the value that `forEach` is being called on - let forLoopSubjectRange: ClosedRange - var forLoopSubjectIdentifier: String? - - // Parse a functional chain backwards from the `forEach` token - var currentIndex = forEachIndex - - // Returns the start index of the chain component ending at the given index - func startOfChainComponent(at index: Int) -> Int? { - // The previous item in a dot chain can either be: - // 1. an identifier like `foo.` - // 2. a function call like `foo(...).` - // 3. a subscript like `foo[...]. - // 4. a trailing closure like `map { ... }` - // 5. Some other combination of parens / subscript like `(foo).` - // or even `foo["bar"]()()`. - // And any of these can be preceeded by one of the others - switch formatter.tokens[index] { - case let .identifier(identifierName): - // Allowlist certain dot chain elements that should be ignored. - // For example, in `foos.reversed().forEach { ... }` we want - // `forLoopSubjectIdentifier` to be `foos` rather than `reversed`. - let chainElementsToIgnore = Set([ - "reversed", "sorted", "shuffled", "enumerated", "dropFirst", "dropLast", - "map", "flatMap", "compactMap", "filter", "reduce", "lazy", - ]) - - if forLoopSubjectIdentifier == nil || chainElementsToIgnore.contains(forLoopSubjectIdentifier ?? "") { - // Since we have to pick a single identifier to represent the subject of the for loop, - // just use the last identifier in the chain - forLoopSubjectIdentifier = identifierName - } - - return index - - case .endOfScope(")"), .endOfScope("]"): - let closingParenIndex = index - guard let startOfScopeIndex = formatter.startOfScope(at: closingParenIndex), - let previousNonSpaceNonCommentIndex = formatter.index(of: .nonSpaceOrComment, before: startOfScopeIndex) - else { return nil } - - // When we find parens for a function call or braces for a subscript, - // continue parsing at the previous non-space non-comment token. - // - If the previous token is a newline then this isn't a function call - // and we'd stop parsing. `foo ()` is a function call but `foo\n()` isn't. - return startOfChainComponent(at: previousNonSpaceNonCommentIndex) ?? startOfScopeIndex - - case .endOfScope("}"): - // Stop parsing if we reach a trailing closure. - // Converting this to a for loop would result in unusual looking syntax like - // `for string in strings.map { $0.uppercased() } { print(string) }` - // which causes a warning to be emitted: "trailing closure in this context is - // confusable with the body of the statement; pass as a parenthesized argument - // to silence this warning". - return nil - - default: - return nil - } - } - - while let previousDotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: currentIndex), - formatter.tokens[previousDotIndex] == .operator(".", .infix), - let tokenBeforeDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: previousDotIndex) - { - guard let startOfChainComponent = startOfChainComponent(at: tokenBeforeDotIndex) else { - // If we parse a dot we expect to parse at least one additional component in the chain. - // Otherwise we'd have a malformed chain that starts with a dot, so abort. - return - } - - currentIndex = startOfChainComponent - } - - guard currentIndex != forEachIndex else { return } - forLoopSubjectRange = currentIndex ... indexBeforeFunctionCallDot - - // If there is a `try` before the `forEach` we cannot know if the subject is async/throwing or the body, - // which makes it impossible to know if we should move it or *remove* it, so we must abort (same for await). - if let tokenIndexBeforeForLoop = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: currentIndex), - var prevToken = formatter.token(at: tokenIndexBeforeForLoop) - { - if prevToken.isUnwrapOperator { - prevToken = formatter.last(.nonSpaceOrComment, before: tokenIndexBeforeForLoop) ?? .space("") - } - if [.keyword("try"), .keyword("await")].contains(prevToken) { - return - } - } - - // If the chain includes linebreaks, don't convert it to a for loop. - // - // In this case converting something like: - // - // placeholderStrings - // .filter { $0.style == .fooBar } - // .map { $0.uppercased() } - // .forEach { print($0) } - // - // to: - // - // for placeholderString in placeholderStrings - // .filter { $0.style == .fooBar } - // .map { $0.uppercased() } { print($0) } - // - // would be a pretty obvious downgrade. - if formatter.tokens[forLoopSubjectRange].contains(where: \.isLinebreak) { - return - } - - /// The names of the argument to the `forEach` closure. - /// e.g. `["foo"]` in `forEach { foo in ... }` - /// or `["foo, bar"]` in `forEach { (foo: Foo, bar: Bar) in ... }` - let forEachValueNames: [String] - let inKeywordIndex: Int? - let isAnonymousClosure: Bool - - if let argumentList = formatter.parseClosureArgumentList(at: closureOpenBraceIndex) { - isAnonymousClosure = false - forEachValueNames = argumentList.argumentNames - inKeywordIndex = argumentList.inKeywordIndex - } else { - isAnonymousClosure = true - inKeywordIndex = nil - - if formatter.options.preserveAnonymousForEach { - return - } - - // We can't introduce an identifier that matches a keyword or already exists in - // the loop body so choose the first eligible option from a set of potential names - var eligibleValueNames = ["item", "element", "value"] - if var identifier = forLoopSubjectIdentifier?.singularized(), !identifier.isSwiftKeyword { - eligibleValueNames = [identifier] + eligibleValueNames - } - - // The chosen name shouldn't already exist in the closure body - guard let chosenValueName = eligibleValueNames.first(where: { name in - !formatter.tokens[closureOpenBraceIndex ... closureCloseBraceIndex].contains(where: { $0.string == name }) - }) else { return } - - forEachValueNames = [chosenValueName] - } - - // Validate that the closure body is eligible to be converted to a for loop - for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { - guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } - - // We can only handle anonymous closures that just use $0, since we don't have good names to - // use for other arguments like $1, $2, etc. If the closure has an anonymous argument - // other than just $0 then we have to ignore it. - if formatter.tokens[closureBodyIndex].string.hasPrefix("$"), - let intValue = Int(formatter.tokens[closureBodyIndex].string.dropFirst()), - intValue != 0 - { - return - } - - // We can convert `return`s to `continue`, but only when `return` is the last token in the scope. - // It's legal to write something like `return print("foo")` in a `forEach` as long as - // you're still returning a `Void` value. Since `continue print("foo")` isn't legal, - // we should just ignore this closure. - if formatter.tokens[closureBodyIndex] == .keyword("return"), - let tokenAfterReturnKeyword = formatter.next(.nonSpaceOrComment, after: closureBodyIndex), - !(tokenAfterReturnKeyword.isLinebreak || tokenAfterReturnKeyword == .endOfScope("}")) - { - return - } - } - - // Start updating the `forEach` call to a `for .. in .. {` loop - for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { - guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } - - // The for loop won't have any `$0` identifiers anymore, so we have to - // update those to the value at the current loop index - if isAnonymousClosure, formatter.tokens[closureBodyIndex].string == "$0" { - formatter.replaceToken(at: closureBodyIndex, with: .identifier(forEachValueNames[0])) - } - - // In a `forEach` closure, `return` continues to the next loop iteration. - // To get the same behavior in a for loop we convert `return`s to `continue`s. - if formatter.tokens[closureBodyIndex] == .keyword("return") { - formatter.replaceToken(at: closureBodyIndex, with: .keyword("continue")) - } - } - - if let forEachCallCloseParenIndex = forEachCallCloseParenIndex { - formatter.removeToken(at: forEachCallCloseParenIndex) - } - - // Construct the new for loop - var newTokens: [Token] = [ - .keyword("for"), - .space(" "), - ] - - let forEachValueNameTokens: [Token] - if forEachValueNames.count == 1 { - newTokens.append(.identifier(forEachValueNames[0])) - } else { - newTokens.append(contentsOf: tokenize("(\(forEachValueNames.joined(separator: ", ")))")) - } - - newTokens.append(contentsOf: [ - .space(" "), - .keyword("in"), - .space(" "), - ]) - - newTokens.append(contentsOf: formatter.tokens[forLoopSubjectRange]) - - newTokens.append(contentsOf: [ - .space(" "), - .startOfScope("{"), - ]) - - formatter.replaceTokens( - in: (forLoopSubjectRange.lowerBound) ... (inKeywordIndex ?? closureOpenBraceIndex), - with: newTokens - ) - } - } - - public let noExplicitOwnership = FormatRule( - help: "Don't use explicit ownership modifiers (borrowing / consuming).", - disabledByDefault: true - ) { formatter in - formatter.forEachToken { keywordIndex, token in - guard [.identifier("borrowing"), .identifier("consuming")].contains(token), - let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: keywordIndex) - else { return } - - // Use of `borrowing` and `consuming` as ownership modifiers - // immediately precede a valid type, or the `func` keyword. - // You could also simply use these names as a property, - // like `let borrowing = foo` or `func myFunc(borrowing foo: Foo)`. - // As a simple heuristic to detect the difference, attempt to parse the - // following tokens as a type, and require that it doesn't start with lower-case letter. - let isValidOwnershipModifier: Bool - if formatter.tokens[nextTokenIndex] == .keyword("func") { - isValidOwnershipModifier = true - } - - else if let type = formatter.parseType(at: nextTokenIndex), - type.name.first?.isLowercase == false - { - isValidOwnershipModifier = true - } - - else { - isValidOwnershipModifier = false - } - - if isValidOwnershipModifier { - formatter.removeTokens(in: keywordIndex ..< nextTokenIndex) - } - } - } - - public let wrapMultilineConditionalAssignment = FormatRule( - help: "Wrap multiline conditional assignment expressions after the assignment operator.", - disabledByDefault: true, - orderAfter: ["conditionalAssignment"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.keyword) { startOfCondition, keywordToken in - guard [.keyword("if"), .keyword("switch")].contains(keywordToken), - let assignmentIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfCondition), - formatter.tokens[assignmentIndex] == .operator("=", .infix), - let endOfPropertyDefinition = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex) - else { return } - - // Verify the RHS of the assignment is an if/switch expression - guard let startOfConditionalExpression = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: assignmentIndex), - ["if", "switch"].contains(formatter.tokens[startOfConditionalExpression].string), - let conditionalBranches = formatter.conditionalBranches(at: startOfConditionalExpression), - let lastBranch = conditionalBranches.last - else { return } - - // If the entire expression is on a single line, we leave the formatting as-is - guard !formatter.onSameLine(startOfConditionalExpression, lastBranch.endOfBranch) else { - return - } - - // The `=` should be on the same line as the rest of the property - if !formatter.onSameLine(endOfPropertyDefinition, assignmentIndex), - formatter.last(.nonSpaceOrComment, before: assignmentIndex)?.isLinebreak == true, - let previousToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex), - formatter.onSameLine(endOfPropertyDefinition, previousToken) - { - // Move the assignment operator to follow the previous token. - // Also remove any trailing space after the previous position - // of the assignment operator. - if formatter.tokens[assignmentIndex + 1].isSpaceOrLinebreak { - formatter.removeToken(at: assignmentIndex + 1) - } - - formatter.removeToken(at: assignmentIndex) - formatter.insert([.space(" "), .operator("=", .infix)], at: previousToken + 1) - } - - // And there should be a line break between the `=` and the `if` / `switch` keyword - else if !formatter.tokens[(assignmentIndex + 1) ..< startOfConditionalExpression].contains(where: \.isLinebreak) { - formatter.insertLinebreak(at: startOfConditionalExpression - 1) - } - } - } - - public let blankLineAfterSwitchCase = FormatRule( - help: """ - Insert a blank line after multiline switch cases (excluding the last case, - which is followed by a closing brace). - """, - disabledByDefault: true, - orderAfter: ["redundantBreak"] - ) { formatter in - formatter.forEach(.keyword("switch")) { switchIndex, _ in - guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } - - for switchCase in switchCases.reversed() { - // Any switch statement that spans multiple lines should be followed by a blank line - // (excluding the last case, which is followed by a closing brace). - if switchCase.spansMultipleLines, - !switchCase.isLastCase, - !switchCase.isFollowedByBlankLine - { - switchCase.insertTrailingBlankLine(using: formatter) - } - - // The last case should never be followed by a blank line, since it's - // already followed by a closing brace. - if switchCase.isLastCase, - switchCase.isFollowedByBlankLine - { - switchCase.removeTrailingBlankLine(using: formatter) - } - } - } - } - - public let consistentSwitchCaseSpacing = FormatRule( - help: "Ensures consistent spacing among all of the cases in a switch statement.", - orderAfter: ["blankLineAfterSwitchCase"] - ) { formatter in - formatter.forEach(.keyword("switch")) { switchIndex, _ in - guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } - - // When counting the switch cases, exclude the last case (which should never have a trailing blank line). - let countWithTrailingBlankLine = switchCases.filter { $0.isFollowedByBlankLine && !$0.isLastCase }.count - let countWithoutTrailingBlankLine = switchCases.filter { !$0.isFollowedByBlankLine && !$0.isLastCase }.count - - // We want the spacing to be consistent for all switch cases, - // so use whichever formatting is used for the majority of cases. - var allCasesShouldHaveBlankLine = countWithTrailingBlankLine >= countWithoutTrailingBlankLine - - // When the `blankLinesBetweenChainedFunctions` rule is enabled, and there is a switch case - // that is required to span multiple lines, then all cases must span multiple lines. - // (Since if this rule removed the blank line from that case, it would contradict the other rule) - if formatter.options.enabledRules.contains(FormatRules.blankLineAfterSwitchCase.name), - switchCases.contains(where: { $0.spansMultipleLines && !$0.isLastCase }) - { - allCasesShouldHaveBlankLine = true - } - - for switchCase in switchCases.reversed() { - if !switchCase.isFollowedByBlankLine, allCasesShouldHaveBlankLine, !switchCase.isLastCase { - switchCase.insertTrailingBlankLine(using: formatter) - } - - if switchCase.isFollowedByBlankLine, !allCasesShouldHaveBlankLine || switchCase.isLastCase { - switchCase.removeTrailingBlankLine(using: formatter) - } - } - } - } - - public let redundantProperty = FormatRule( - help: "Simplifies redundant property definitions that are immediately returned.", - disabledByDefault: true, - orderAfter: ["propertyType"] - ) { formatter in - formatter.forEach(.keyword) { introducerIndex, introducerToken in - // Find properties like `let identifier = value` followed by `return identifier` - guard ["let", "var"].contains(introducerToken.string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - let (assignmentIndex, expressionRange) = property.value, - let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: expressionRange.upperBound), - formatter.tokens[returnIndex] == .keyword("return"), - let returnedValueIndex = formatter.index(of: .nonSpaceOrComment, after: returnIndex), - let returnedExpression = formatter.parseExpressionRange(startingAt: returnedValueIndex, allowConditionalExpressions: true), - formatter.tokens[returnedExpression] == [.identifier(property.identifier)] - else { return } - - let returnRange = formatter.startOfLine(at: returnIndex) ... formatter.endOfLine(at: returnedExpression.upperBound) - let propertyRange = introducerIndex ... expressionRange.upperBound - - guard !propertyRange.overlaps(returnRange) else { return } - - // Remove the line with the `return identifier` statement. - formatter.removeTokens(in: returnRange) - - // If there's nothing but whitespace between the end of the expression - // and the return statement, we can remove all of it. But if there's a comment, - // we should preserve it. - let rangeBetweenExpressionAndReturn = (expressionRange.upperBound + 1) ..< (returnRange.lowerBound - 1) - if formatter.tokens[rangeBetweenExpressionAndReturn].allSatisfy(\.isSpaceOrLinebreak) { - formatter.removeTokens(in: rangeBetweenExpressionAndReturn) - } - - // Replace the `let identifier = value` with `return value` - formatter.replaceTokens( - in: introducerIndex ..< expressionRange.lowerBound, - with: [.keyword("return"), .space(" ")] - ) - } - } - - public let redundantTypedThrows = FormatRule(help: "Converts `throws(any Error)` to `throws`, and converts `throws(Never)` to non-throwing.") { formatter in - - formatter.forEach(.keyword("throws")) { throwsIndex, _ in - guard // Typed throws was added in Swift 6.0: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md - formatter.options.swiftVersion >= "6.0", - let startOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: throwsIndex), - formatter.tokens[startOfScope] == .startOfScope("("), - let endOfScope = formatter.endOfScope(at: startOfScope) - else { return } - - let throwsTypeRange = (startOfScope + 1) ..< endOfScope - let throwsType: String = formatter.tokens[throwsTypeRange].map { $0.string }.joined() - - if throwsType == "Never" { - if formatter.tokens[endOfScope + 1].isSpace { - formatter.removeTokens(in: throwsIndex ... endOfScope + 1) - } else { - formatter.removeTokens(in: throwsIndex ... endOfScope) - } - } - - // We don't remove `(Error)` because we can't guarantee it will reference the `Swift.Error` protocol - // (it's relatively common to define a custom error like `enum Error: Swift.Error { ... }`). - if throwsType == "any Error" || throwsType == "any Swift.Error" || throwsType == "Swift.Error" { - formatter.removeTokens(in: startOfScope ... endOfScope) - } - } - } - - public let propertyType = FormatRule( - help: "Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`).", - disabledByDefault: true, - orderAfter: ["redundantType"], - options: ["inferredtypes", "preservesymbols"], - sharedOptions: ["redundanttype"] - ) { formatter in - formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in - // Preserve all properties in conditional statements like `if let foo = Bar() { ... }` - guard !formatter.isConditionalStatement(at: equalsIndex) else { return } - - // Determine whether the type should use the inferred syntax (`let foo = Foo()`) - // of the explicit syntax (`let foo: Foo = .init()`). - let useInferredType: Bool - switch formatter.options.redundantType { - case .inferred: - useInferredType = true - - case .explicit: - useInferredType = false - - case .inferLocalsOnly: - switch formatter.declarationScope(at: equalsIndex) { - case .global, .type: - useInferredType = false - case .local: - useInferredType = true - } - } - - guard let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), - ["var", "let"].contains(formatter.tokens[introducerIndex].string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - let rhsExpressionRange = property.value?.expressionRange - else { return } - - let rhsStartIndex = rhsExpressionRange.lowerBound - - if useInferredType { - guard let type = property.type else { return } - let typeTokens = formatter.tokens[type.range] - - // Preserve the existing formatting if the LHS type is optional. - // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` - // is invalid if `.foo` is defined on `Foo` but not `Foo?`. - guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } - - // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). - // - The `extension MyProtocol where Self == MyType { ... }` syntax - // creates static members where `let foo: any MyProtocol = .myType` - // is valid, but `let foo = (any MyProtocol).myType` isn't. - guard typeTokens.first?.string != "any" else { return } - - // Preserve the existing formatting if the RHS expression has a top-level infix operator. - // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to - // `let value = ClosedRange.zero ... 10`. - if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in - token.isOperator(ofType: .infix) && token != .operator(".", .infix) - }), - rhsExpressionRange.contains(nextInfixOperatorIndex) - { - return - } - - // Preserve the formatting as-is if the type is manually excluded - if formatter.options.preserveSymbols.contains(type.name) { - return - } - - // If the RHS starts with a leading dot, then we know its accessing some static member on this type. - if formatter.tokens[rhsStartIndex].isOperator(".") { - // Preserve the formatting as-is if the identifier is manually excluded - if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), - formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) - { return } - - // Update the . token from a prefix operator to an infix operator. - formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) - - // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: rhsStartIndex) - } - - // If the RHS is an if/switch expression, check that each branch starts with a leading dot - else if formatter.options.inferredTypesInConditionalExpressions, - ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), - let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) - { - var hasInvalidConditionalBranch = false - formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in - guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { - hasInvalidConditionalBranch = true - return - } - - if !formatter.tokens[firstTokenInBranch].isOperator(".") { - hasInvalidConditionalBranch = true - } - - // Preserve the formatting as-is if the identifier is manually excluded - if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), - formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) - { - hasInvalidConditionalBranch = true - } - } - - guard !hasInvalidConditionalBranch else { return } - - // Insert a copy of the type on the RHS before the dot in each branch - formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in - guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } - - // Update the . token from a prefix operator to an infix operator. - formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) - - // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: dotIndex) - } - } - - else { - return - } - - // Remove the colon and explicit type before the equals token - formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) - } - - // If using explicit types, convert properties to the format `let foo: Foo = .init()`. - else { - guard // When parsing the type, exclude lowercase identifiers so `foo` isn't parsed as a type, - // and so `Foo.init` is parsed as `Foo` instead of `Foo.init`. - let rhsType = formatter.parseType(at: rhsStartIndex, excludeLowercaseIdentifiers: true), - property.type == nil, - let indexAfterIdentifier = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: property.identifierIndex), - formatter.tokens[indexAfterIdentifier] != .delimiter(":"), - let indexAfterType = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsType.range.upperBound), - [.operator(".", .infix), .startOfScope("(")].contains(formatter.tokens[indexAfterType]), - !rhsType.name.contains(".") - else { return } - - // Preserve the existing formatting if the RHS expression has a top-level operator. - // - `let foo = Foo.foo.bar` would not be valid to convert to `let foo: Foo = .foo.bar`. - let operatorSearchIndex = formatter.tokens[indexAfterType].isStartOfScope ? (indexAfterType - 1) : indexAfterType - if let nextInfixOperatorIndex = formatter.index(after: operatorSearchIndex, where: { token in - token.isOperator(ofType: .infix) - }), - rhsExpressionRange.contains(nextInfixOperatorIndex) - { - return - } - - // Preserve any types that have been manually excluded. - // Preserve any `Void` types and tuples, since they're special and don't support things like `.init` - guard !(formatter.options.preserveSymbols + ["Void"]).contains(rhsType.name), - !rhsType.name.hasPrefix("(") - else { return } - - // A type name followed by a `(` is an implicit `.init(`. Insert a `.init` - // so that the init call stays valid after we move the type to the LHS. - if formatter.tokens[indexAfterType] == .startOfScope("(") { - // Preserve the existing format if `init` is manually excluded - if formatter.options.preserveSymbols.contains("init") { - return - } - - formatter.insert([.operator(".", .prefix), .identifier("init")], at: indexAfterType) - } - - // If the type name is followed by an infix `.` operator, convert it to a prefix operator. - else if formatter.tokens[indexAfterType] == .operator(".", .infix) { - // Exclude types with dots followed by a member access. - // - For example with something like `Color.Theme.themeColor`, we don't know - // if the property is `static var themeColor: Color` or `static var themeColor: Color.Theme`. - // - This isn't a problem with something like `Color.Theme()`, which we can reasonably assume - // is an initializer - if rhsType.name.contains(".") { return } - - // Preserve the formatting as-is if the identifier is manually excluded. - // Don't convert `let foo = Foo.self` to `let foo: Foo = .self`, since `.self` returns the metatype - let symbolsToExclude = formatter.options.preserveSymbols + ["self"] - if let indexAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterType), - symbolsToExclude.contains(formatter.tokens[indexAfterDot].string) - { return } - - formatter.replaceToken(at: indexAfterType, with: .operator(".", .prefix)) - } - - // Move the type name to the LHS of the property, followed by a colon - let typeTokens = formatter.tokens[rhsType.range] - formatter.removeTokens(in: rhsType.range) - formatter.insert([.delimiter(":"), .space(" ")] + typeTokens, at: property.identifierIndex + 1) - } - } - } - - public let docCommentsBeforeAttributes = FormatRule( - help: "Place doc comments on declarations before any attributes.", - orderAfter: ["docComments"] - ) { formatter in - formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in - // Parse the attributes on this declaration if present - let startOfAttributes = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) - guard formatter.tokens[startOfAttributes].isAttribute else { return } - - let attributes = formatter.attributes(startingAt: startOfAttributes) - guard !attributes.isEmpty else { return } - - let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex - - // If there's a doc comment between the attributes and the rest of the declaration, - // move it above the attributes. - guard let linebreakAfterAttributes = formatter.index(of: .linebreak, after: attributesRange.upperBound), - let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: linebreakAfterAttributes), - indexAfterAttributes < keywordIndex, - let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), - formatter.isDocComment(startOfComment: indexAfterAttributes) - else { return } - - let commentRange = indexAfterAttributes ..< restOfDeclaration - let comment = formatter.tokens[commentRange] - - formatter.removeTokens(in: commentRange) - formatter.insert(comment, at: startOfAttributes) - } - } -} diff --git a/Sources/Rules/Acronyms.swift b/Sources/Rules/Acronyms.swift new file mode 100644 index 00000000..89f1959b --- /dev/null +++ b/Sources/Rules/Acronyms.swift @@ -0,0 +1,76 @@ +// +// Acronyms.swift +// SwiftFormat +// +// Created by Cal Stephens on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let acronyms = FormatRule( + help: "Capitalize acronyms when the first character is capitalized.", + disabledByDefault: true, + options: ["acronyms"] + ) { formatter in + formatter.forEachToken { index, token in + guard token.is(.identifier) || token.isComment else { return } + + var updatedText = token.string + + for acronym in formatter.options.acronyms { + let find = acronym.capitalized + let replace = acronym.uppercased() + + for replaceCandidateRange in token.string.ranges(of: find) { + let acronymShouldBeCapitalized: Bool + + if replaceCandidateRange.upperBound < token.string.indices.last! { + let indexAfterMatch = replaceCandidateRange.upperBound + let characterAfterMatch = token.string[indexAfterMatch] + + // Only treat this as an acronym if the next character is uppercased, + // to prevent "Id" from matching strings like "Identifier". + if characterAfterMatch.isUppercase || characterAfterMatch.isWhitespace { + acronymShouldBeCapitalized = true + } + + // But if the next character is 's', and then the character after the 's' is uppercase, + // allow the acronym to be capitalized (to handle the plural case, `Ids` to `IDs`) + else if characterAfterMatch == Character("s") { + if indexAfterMatch < token.string.indices.last! { + let characterAfterNext = token.string[token.string.index(after: indexAfterMatch)] + acronymShouldBeCapitalized = (characterAfterNext.isUppercase || characterAfterNext.isWhitespace) + } else { + acronymShouldBeCapitalized = true + } + } else { + acronymShouldBeCapitalized = false + } + } else { + acronymShouldBeCapitalized = true + } + + if acronymShouldBeCapitalized { + updatedText.replaceSubrange(replaceCandidateRange, with: replace) + } + } + } + + if token.string != updatedText { + let updatedToken: Token + switch token { + case .identifier: + updatedToken = .identifier(updatedText) + case .commentBody: + updatedToken = .commentBody(updatedText) + default: + return + } + + formatter.replaceToken(at: index, with: updatedToken) + } + } + } +} diff --git a/Sources/Rules/AndOperator.swift b/Sources/Rules/AndOperator.swift new file mode 100644 index 00000000..f928869e --- /dev/null +++ b/Sources/Rules/AndOperator.swift @@ -0,0 +1,85 @@ +// +// AndOperator.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/14/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace the `&&` operator with `,` where applicable + static let andOperator = FormatRule( + help: "Prefer comma over `&&` in `if`, `guard` or `while` conditions." + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .keyword("if"), .keyword("guard"), + .keyword("while") where formatter.last(.keyword, before: i) != .keyword("repeat"): + break + default: + return + } + guard var endIndex = formatter.index(of: .startOfScope("{"), after: i) else { + return + } + if formatter.options.swiftVersion < "5.3", formatter.isInResultBuilder(at: i) { + return + } + var index = i + 1 + var chevronIndex: Int? + outer: while index < endIndex { + switch formatter.tokens[index] { + case .operator("&&", .infix): + let endOfGroup = formatter.index(of: .delimiter(","), after: index) ?? endIndex + var nextOpIndex = index + while let next = formatter.index(of: .operator, after: nextOpIndex) { + if formatter.tokens[next] == .operator("||", .infix) { + index = endOfGroup + continue outer + } + nextOpIndex = next + } + if let chevronIndex = chevronIndex, + formatter.index(of: .operator(">", .infix), in: index ..< endIndex) != nil + { + // Check if this would cause ambiguity for chevrons + var tokens = Array(formatter.tokens[i ... endIndex]) + tokens[index - i] = .delimiter(",") + tokens.append(.endOfScope("}")) + let reparsed = tokenize(sourceCode(for: tokens)) + if reparsed[chevronIndex - i] == .startOfScope("<") { + return + } + } + formatter.replaceToken(at: index, with: .delimiter(",")) + if formatter.tokens[index - 1] == .space(" ") { + formatter.removeToken(at: index - 1) + endIndex -= 1 + index -= 1 + } else if let prevIndex = formatter.index(of: .nonSpace, before: index), + formatter.tokens[prevIndex].isLinebreak, let nonLinbreak = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) + { + formatter.removeToken(at: index) + formatter.insert(.delimiter(","), at: nonLinbreak + 1) + if formatter.tokens[index + 1] == .space(" ") { + formatter.removeToken(at: index + 1) + endIndex -= 1 + } + } + case .operator("<", .infix): + chevronIndex = index + case .operator("||", .infix), .operator("=", .infix), .keyword("try"): + index = formatter.index(of: .delimiter(","), after: index) ?? endIndex + case .startOfScope: + index = formatter.endOfScope(at: index) ?? endIndex + default: + break + } + index += 1 + } + } + } +} diff --git a/Sources/Rules/AnyObjectProtocol.swift b/Sources/Rules/AnyObjectProtocol.swift new file mode 100644 index 00000000..5c913313 --- /dev/null +++ b/Sources/Rules/AnyObjectProtocol.swift @@ -0,0 +1,31 @@ +// +// AnyObjectProtocol.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/23/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Prefer `AnyObject` over `class` for class-based protocols + static let anyObjectProtocol = FormatRule( + help: "Prefer `AnyObject` over `class` in protocol definitions." + ) { formatter in + formatter.forEach(.keyword("protocol")) { i, _ in + guard formatter.options.swiftVersion >= "4.1", + let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0.isIdentifier + }), let colonIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { + $0 == .delimiter(":") + }), let classIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex, if: { + $0 == .keyword("class") + }) + else { + return + } + formatter.replaceToken(at: classIndex, with: .identifier("AnyObject")) + } + } +} diff --git a/Sources/Rules/ApplicationMain.swift b/Sources/Rules/ApplicationMain.swift new file mode 100644 index 00000000..8a5984f3 --- /dev/null +++ b/Sources/Rules/ApplicationMain.swift @@ -0,0 +1,32 @@ +// +// ApplicationMain.swift +// SwiftFormat +// +// Created by Nick Lockwood on 5/20/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace the obsolete `@UIApplicationMain` and `@NSApplicationMain` + /// attributes with `@main` in Swift 5.3 and above, per SE-0383 + static let applicationMain = FormatRule( + help: """ + Replace obsolete @UIApplicationMain and @NSApplicationMain attributes + with @main for Swift 5.3 and above. + """ + ) { formatter in + guard formatter.options.swiftVersion >= "5.3" else { + return + } + formatter.forEachToken(where: { + [ + .keyword("@UIApplicationMain"), + .keyword("@NSApplicationMain"), + ].contains($0) + }) { i, _ in + formatter.replaceToken(at: i, with: .keyword("@main")) + } + } +} diff --git a/Sources/Rules/AssertionFailures.swift b/Sources/Rules/AssertionFailures.swift new file mode 100644 index 00000000..c3ffb919 --- /dev/null +++ b/Sources/Rules/AssertionFailures.swift @@ -0,0 +1,43 @@ +// +// AssertionFailures.swift +// SwiftFormat +// +// Created by sanjanapruthi on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let assertionFailures = FormatRule( + help: """ + Changes all instances of assert(false, ...) to assertionFailure(...) + and precondition(false, ...) to preconditionFailure(...). + """ + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .identifier("assert"), .identifier("precondition"): + guard let scopeStart = formatter.index(of: .nonSpace, after: i, if: { + $0 == .startOfScope("(") + }), let identifierIndex = formatter.index(of: .nonSpaceOrLinebreak, after: scopeStart, if: { + $0 == .identifier("false") + }), var endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: identifierIndex) else { + return + } + + // if there are more arguments, replace the comma and space as well + if formatter.tokens[endIndex] == .delimiter(",") { + endIndex = formatter.index(of: .nonSpace, after: endIndex) ?? endIndex + } + + let replacements = ["assert": "assertionFailure", "precondition": "preconditionFailure"] + formatter.replaceTokens(in: i ..< endIndex, with: [ + .identifier(replacements[token.string]!), .startOfScope("("), + ]) + default: + break + } + } + } +} diff --git a/Sources/Rules/BlankLineAfterImports.swift b/Sources/Rules/BlankLineAfterImports.swift new file mode 100644 index 00000000..9f49a6c1 --- /dev/null +++ b/Sources/Rules/BlankLineAfterImports.swift @@ -0,0 +1,40 @@ +// +// BlankLineAfterImports.swift +// SwiftFormat +// +// Created by Tsungyu Yu on 5/1/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Insert blank line after import statements + static let blankLineAfterImports = FormatRule( + help: """ + Insert blank line after import statements. + """, + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.keyword("import")) { currentImportIndex, _ in + guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), + var nextIndex = formatter.index(of: .nonSpace, after: endOfLine) + else { + return + } + var keyword: Token = formatter.tokens[nextIndex] + while keyword == .startOfScope("#if") || keyword.isModifierKeyword || keyword.isAttribute, + let index = formatter.index(of: .keyword, after: nextIndex) + { + nextIndex = index + keyword = formatter.tokens[nextIndex] + } + switch formatter.tokens[nextIndex] { + case .linebreak, .keyword("import"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"): + break + default: + formatter.insertLinebreak(at: endOfLine) + } + } + } +} diff --git a/Sources/Rules/BlankLineAfterSwitchCase.swift b/Sources/Rules/BlankLineAfterSwitchCase.swift new file mode 100644 index 00000000..1c7d0bcc --- /dev/null +++ b/Sources/Rules/BlankLineAfterSwitchCase.swift @@ -0,0 +1,43 @@ +// +// BlankLineAfterSwitchCase.swift +// SwiftFormat +// +// Created by Cal Stephens on 2/1/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let blankLineAfterSwitchCase = FormatRule( + help: """ + Insert a blank line after multiline switch cases (excluding the last case, + which is followed by a closing brace). + """, + disabledByDefault: true, + orderAfter: [.redundantBreak] + ) { formatter in + formatter.forEach(.keyword("switch")) { switchIndex, _ in + guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } + + for switchCase in switchCases.reversed() { + // Any switch statement that spans multiple lines should be followed by a blank line + // (excluding the last case, which is followed by a closing brace). + if switchCase.spansMultipleLines, + !switchCase.isLastCase, + !switchCase.isFollowedByBlankLine + { + switchCase.insertTrailingBlankLine(using: formatter) + } + + // The last case should never be followed by a blank line, since it's + // already followed by a closing brace. + if switchCase.isLastCase, + switchCase.isFollowedByBlankLine + { + switchCase.removeTrailingBlankLine(using: formatter) + } + } + } + } +} diff --git a/Sources/Rules/BlankLinesAroundMark.swift b/Sources/Rules/BlankLinesAroundMark.swift new file mode 100644 index 00000000..b95180de --- /dev/null +++ b/Sources/Rules/BlankLinesAroundMark.swift @@ -0,0 +1,38 @@ +// +// BlankLinesAroundMark.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/29/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Adds a blank line around MARK: comments + static let blankLinesAroundMark = FormatRule( + help: "Insert blank line before and after `MARK:` comments.", + options: ["lineaftermarks"], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEachToken { i, token in + guard case let .commentBody(comment) = token, comment.hasPrefix("MARK:"), + let startIndex = formatter.index(of: .nonSpace, before: i), + formatter.tokens[startIndex] == .startOfScope("//") else { return } + if let nextIndex = formatter.index(of: .linebreak, after: i), + let nextToken = formatter.next(.nonSpace, after: nextIndex), + !nextToken.isLinebreak, nextToken != .endOfScope("}"), + formatter.options.lineAfterMarks + { + formatter.insertLinebreak(at: nextIndex) + } + if formatter.options.insertBlankLines, + let lastIndex = formatter.index(of: .linebreak, before: startIndex), + let lastToken = formatter.last(.nonSpace, before: lastIndex), + !lastToken.isLinebreak, lastToken != .startOfScope("{") + { + formatter.insertLinebreak(at: lastIndex) + } + } + } +} diff --git a/Sources/Rules/BlankLinesAtEndOfScope.swift b/Sources/Rules/BlankLinesAtEndOfScope.swift new file mode 100644 index 00000000..76058fbb --- /dev/null +++ b/Sources/Rules/BlankLinesAtEndOfScope.swift @@ -0,0 +1,63 @@ +// +// BlankLinesAtEndOfScope.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/30/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines immediately before a closing brace, bracket, paren or chevron + /// unless it's followed by more code on the same line (e.g. } else { ) + static let blankLinesAtEndOfScope = FormatRule( + help: "Remove trailing blank line at the end of a scope.", + orderAfter: [.organizeDeclarations], + sharedOptions: ["typeblanklines"] + ) { formatter in + formatter.forEach(.startOfScope) { startOfScopeIndex, _ in + guard let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex) else { return } + let endOfScope = formatter.tokens[endOfScopeIndex] + + guard ["}", ")", "]", ">"].contains(endOfScope.string), + // If there is extra code after the closing scope on the same line, ignore it + (formatter.next(.nonSpaceOrComment, after: endOfScopeIndex).map { $0.isLinebreak }) ?? true + else { return } + + // Consumers can choose whether or not this rule should apply to type bodies + if !formatter.options.removeStartOrEndBlankLinesFromTypes, + ["class", "actor", "struct", "enum", "protocol", "extension"].contains( + formatter.lastSignificantKeyword(at: startOfScopeIndex, excluding: ["where"])) + { + return + } + + // Find previous non-space token + var index = endOfScopeIndex - 1 + var indexOfFirstLineBreak: Int? + var indexOfLastLineBreak: Int? + loop: while let token = formatter.token(at: index) { + switch token { + case .linebreak: + indexOfFirstLineBreak = index + if indexOfLastLineBreak == nil { + indexOfLastLineBreak = index + } + case .space: + break + default: + break loop + } + index -= 1 + } + if formatter.options.removeBlankLines, + let indexOfFirstLineBreak = indexOfFirstLineBreak, + indexOfFirstLineBreak != indexOfLastLineBreak + { + formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak!) + return + } + } + } +} diff --git a/Sources/Rules/BlankLinesAtStartOfScope.swift b/Sources/Rules/BlankLinesAtStartOfScope.swift new file mode 100644 index 00000000..83479b10 --- /dev/null +++ b/Sources/Rules/BlankLinesAtStartOfScope.swift @@ -0,0 +1,53 @@ +// +// BlankLinesAtStartOfScope.swift +// SwiftFormat +// +// Created by Nick Lockwood on 2/1/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines immediately after an opening brace, bracket, paren or chevron + static let blankLinesAtStartOfScope = FormatRule( + help: "Remove leading blank line at the start of a scope.", + orderAfter: [.organizeDeclarations], + options: ["typeblanklines"] + ) { formatter in + formatter.forEach(.startOfScope) { i, token in + guard ["{", "(", "[", "<"].contains(token.string), + let indexOfFirstLineBreak = formatter.index(of: .nonSpaceOrComment, after: i), + // If there is extra code on the same line, ignore it + formatter.tokens[indexOfFirstLineBreak].isLinebreak + else { return } + + // Consumers can choose whether or not this rule should apply to type bodies + if !formatter.options.removeStartOrEndBlankLinesFromTypes, + ["class", "actor", "struct", "enum", "protocol", "extension"].contains( + formatter.lastSignificantKeyword(at: i, excluding: ["where"])) + { + return + } + + // Find next non-space token + var index = indexOfFirstLineBreak + 1 + var indexOfLastLineBreak = indexOfFirstLineBreak + loop: while let token = formatter.token(at: index) { + switch token { + case .linebreak: + indexOfLastLineBreak = index + case .space: + break + default: + break loop + } + index += 1 + } + if formatter.options.removeBlankLines, indexOfFirstLineBreak != indexOfLastLineBreak { + formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak) + return + } + } + } +} diff --git a/Sources/Rules/BlankLinesBetweenChainedFunctions.swift b/Sources/Rules/BlankLinesBetweenChainedFunctions.swift new file mode 100644 index 00000000..33b2ff13 --- /dev/null +++ b/Sources/Rules/BlankLinesBetweenChainedFunctions.swift @@ -0,0 +1,43 @@ +// +// BlankLinesBetweenChainedFunctions.swift +// SwiftFormat +// +// Created by Nick Lockwood on 7/28/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines between chained functions but keep the linebreaks + static let blankLinesBetweenChainedFunctions = FormatRule( + help: """ + Remove blank lines between chained functions but keep the linebreaks. + """ + ) { formatter in + formatter.forEach(.operator(".", .infix)) { i, _ in + let endOfLine = formatter.endOfLine(at: i) + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfLine), + formatter.tokens[nextIndex] == .operator(".", .infix), + // Make sure to preserve any code comment between the two lines + let nextTokenOrComment = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine) + { + if formatter.tokens[nextTokenOrComment].isComment { + if formatter.options.enabledRules.contains(FormatRule.blankLinesAroundMark.name), + case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenOrComment), + body.hasPrefix("MARK:") + { + return + } + if let endOfComment = formatter.index(of: .comment, before: nextIndex) { + let endOfLine = formatter.endOfLine(at: endOfComment) + let startOfLine = formatter.startOfLine(at: nextIndex) + formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) + } + } + let startOfLine = formatter.startOfLine(at: nextTokenOrComment) + formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) + } + } + } +} diff --git a/Sources/Rules/BlankLinesBetweenImports.swift b/Sources/Rules/BlankLinesBetweenImports.swift new file mode 100644 index 00000000..ed4230ee --- /dev/null +++ b/Sources/Rules/BlankLinesBetweenImports.swift @@ -0,0 +1,32 @@ +// +// BlankLinesBetweenImports.swift +// SwiftFormat +// +// Created by Huy Vo on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines between import statements + static let blankLinesBetweenImports = FormatRule( + help: """ + Remove blank lines between import statements. + """, + disabledByDefault: true, + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.keyword("import")) { currentImportIndex, _ in + guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), + let nextImportIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine, if: { + $0 == .keyword("@testable") || $0 == .keyword("import") + }) + else { + return + } + + formatter.replaceTokens(in: endOfLine ..< nextImportIndex, with: formatter.linebreakToken(for: currentImportIndex + 1)) + } + } +} diff --git a/Sources/Rules/BlankLinesBetweenScopes.swift b/Sources/Rules/BlankLinesBetweenScopes.swift new file mode 100644 index 00000000..5805f825 --- /dev/null +++ b/Sources/Rules/BlankLinesBetweenScopes.swift @@ -0,0 +1,109 @@ +// +// BlankLinesBetweenScopes.swift +// SwiftFormat +// +// Created by Nick Lockwood on 9/7/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Adds a blank line immediately after a closing brace, unless followed by another closing brace + static let blankLinesBetweenScopes = FormatRule( + help: """ + Insert blank line before class, struct, enum, extension, protocol or function + declarations. + """, + sharedOptions: ["linebreaks"] + ) { formatter in + var spaceableScopeStack = [true] + var isSpaceableScopeType = false + formatter.forEachToken(onlyWhereEnabled: false) { i, token in + outer: switch token { + case .keyword("class"), + .keyword("actor"), + .keyword("struct"), + .keyword("extension"), + .keyword("enum"): + isSpaceableScopeType = + (formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import")) + case .keyword("func"), .keyword("var"): + isSpaceableScopeType = false + case .startOfScope("{"): + spaceableScopeStack.append(isSpaceableScopeType) + isSpaceableScopeType = false + case .endOfScope("}"): + spaceableScopeStack.removeLast() + guard spaceableScopeStack.last == true, + let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: i), + formatter.lastIndex(of: .linebreak, in: openingBraceIndex + 1 ..< i) != nil + else { + // Inline braces + break + } + var i = i + if let nextTokenIndex = formatter.index(of: .nonSpace, after: i, if: { + $0 == .startOfScope("(") + }), let closingParenIndex = formatter.index(of: + .endOfScope(")"), after: nextTokenIndex) + { + i = closingParenIndex + } + guard let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i), + formatter.isEnabled, formatter.options.insertBlankLines, + let firstLinebreakIndex = formatter.index(of: .linebreak, in: i + 1 ..< nextTokenIndex), + formatter.index(of: .linebreak, in: firstLinebreakIndex + 1 ..< nextTokenIndex) == nil + else { + break + } + if var nextNonCommentIndex = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) + { + while formatter.tokens[nextNonCommentIndex] == .startOfScope("#if"), + let nextIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + after: formatter.endOfLine(at: nextNonCommentIndex) + ) + { + nextNonCommentIndex = nextIndex + } + switch formatter.tokens[nextNonCommentIndex] { + case .error, .endOfScope, + .operator(".", _), .delimiter(","), .delimiter(":"), + .keyword("else"), .keyword("catch"), .keyword("#else"): + break outer + case .keyword("while"): + if let previousBraceIndex = formatter.index(of: .startOfScope("{"), before: i), + formatter.last(.nonSpaceOrCommentOrLinebreak, before: previousBraceIndex) + == .keyword("repeat") + { + break outer + } + default: + if formatter.isLabel(at: nextNonCommentIndex), let colonIndex + = formatter.index(of: .delimiter(":"), after: nextNonCommentIndex), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) + == .startOfScope("{") + { + break outer + } + } + } + switch formatter.tokens[nextTokenIndex] { + case .startOfScope("//"): + if case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenIndex), + body.trimmingCharacters(in: .whitespaces).lowercased().hasPrefix("sourcery:") + { + break + } + formatter.insertLinebreak(at: firstLinebreakIndex) + default: + formatter.insertLinebreak(at: firstLinebreakIndex) + } + default: + break + } + } + } +} diff --git a/Sources/Rules/BlockComments.swift b/Sources/Rules/BlockComments.swift new file mode 100644 index 00000000..7267d52b --- /dev/null +++ b/Sources/Rules/BlockComments.swift @@ -0,0 +1,138 @@ +// +// BlockComments.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/6/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let blockComments = FormatRule( + help: "Convert block comments to consecutive single line comments.", + disabledByDefault: true + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .startOfScope("/*"): + guard var endIndex = formatter.endOfScope(at: i) else { + return formatter.fatalError("Expected */", at: i) + } + + // We can only convert block comments to single-line comments + // if there are no non-comment tokens on the same line. + // - For example, we can't convert `if foo { /* code */ }` + // to a line comment because it would comment out the closing brace. + // + // To guard against this, we verify that there is only + // comment or whitespace tokens on the remainder of this line + guard formatter.next(.nonSpace, after: endIndex)?.isLinebreak != false else { + return + } + + var isDocComment = false + var stripLeadingStars = true + func replaceCommentBody(at index: Int) -> Int { + var delta = 0 + var space = "" + if case let .space(s) = formatter.tokens[index] { + formatter.removeToken(at: index) + space = s + delta -= 1 + } + if case let .commentBody(body)? = formatter.token(at: index) { + var body = Substring(body) + if stripLeadingStars { + if body.hasPrefix("*") { + body = body.drop(while: { $0 == "*" }) + } else { + stripLeadingStars = false + } + } + let prefix = isDocComment ? "/" : "" + if !prefix.isEmpty || !body.isEmpty, !body.hasPrefix(" ") { + space += " " + } + formatter.replaceToken( + at: index, + with: .commentBody(prefix + space + body) + ) + } else if isDocComment { + formatter.insert(.commentBody("/"), at: index) + delta += 1 + } + return delta + } + + // Replace opening delimiter + var startIndex = i + let indent = formatter.currentIndentForLine(at: i) + if case let .commentBody(body) = formatter.tokens[i + 1] { + isDocComment = body.hasPrefix("*") + let commentBody = body.drop(while: { $0 == "*" }) + formatter.replaceToken(at: i + 1, with: .commentBody("/" + commentBody)) + } + formatter.replaceToken(at: i, with: .startOfScope("//")) + if let nextToken = formatter.token(at: i + 1), + nextToken.isSpaceOrLinebreak || nextToken.string == (isDocComment ? "/" : ""), + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i + 1), + nextIndex > i + 2 + { + let range = i + 1 ..< nextIndex + formatter.removeTokens(in: range) + endIndex -= range.count + startIndex = i + 1 + endIndex += replaceCommentBody(at: startIndex) + } + + // Replace ending delimiter + if let i = formatter.index(of: .nonSpace, before: endIndex, if: { + $0.isLinebreak + }) { + let range = i ... endIndex + formatter.removeTokens(in: range) + endIndex -= range.count + } + + // remove /* and */ + var index = i + while index <= endIndex { + switch formatter.tokens[index] { + case .startOfScope("/*"): + formatter.removeToken(at: index) + endIndex -= 1 + if formatter.tokens[index - 1].isSpace { + formatter.removeToken(at: index - 1) + index -= 1 + endIndex -= 1 + } + case .endOfScope("*/"): + formatter.removeToken(at: index) + endIndex -= 1 + if formatter.tokens[index - 1].isSpace { + formatter.removeToken(at: index - 1) + index -= 1 + endIndex -= 1 + } + case .linebreak: + endIndex += formatter.insertSpace(indent, at: index + 1) + guard let i = formatter.index(of: .nonSpace, after: index) else { + index += 1 + continue + } + index = i + formatter.insert(.startOfScope("//"), at: index) + var delta = 1 + replaceCommentBody(at: index + 1) + index += delta + endIndex += delta + default: + index += 1 + } + } + default: + break + } + } + } +} diff --git a/Sources/Rules/Braces.swift b/Sources/Rules/Braces.swift new file mode 100644 index 00000000..ec179af9 --- /dev/null +++ b/Sources/Rules/Braces.swift @@ -0,0 +1,89 @@ +// +// Braces.swift +// SwiftFormat +// +// Created by Nick Lockwood on 10/27/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement brace-wrapping rules + static let braces = FormatRule( + help: "Wrap braces in accordance with selected style (K&R or Allman).", + options: ["allman"], + sharedOptions: ["linebreaks", "maxwidth", "indent", "tabwidth", "assetliterals"] + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard let closingBraceIndex = formatter.endOfScope(at: i), + // Check this isn't an inline block + formatter.index(of: .linebreak, in: i + 1 ..< closingBraceIndex) != nil, + let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + ![.delimiter(","), .keyword("in")].contains(prevToken), + !prevToken.is(.startOfScope) + else { + return + } + if let penultimateToken = formatter.last(.nonSpaceOrComment, before: closingBraceIndex), + !penultimateToken.isLinebreak + { + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: closingBraceIndex) + formatter.insertLinebreak(at: closingBraceIndex) + if formatter.token(at: closingBraceIndex - 1)?.isSpace == true { + formatter.removeToken(at: closingBraceIndex - 1) + } + } + if formatter.options.allmanBraces { + // Implement Allman-style braces, where opening brace appears on the next line + switch formatter.last(.nonSpace, before: i) ?? .space("") { + case .identifier, .keyword, .endOfScope, .number, + .operator("?", .postfix), .operator("!", .postfix): + formatter.insertLinebreak(at: i) + if let breakIndex = formatter.index(of: .linebreak, after: i + 1), + let nextIndex = formatter.index(of: .nonSpace, after: breakIndex, if: { $0.isLinebreak }) + { + formatter.removeTokens(in: breakIndex ..< nextIndex) + } + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) + if formatter.tokens[i - 1].isSpace { + formatter.removeToken(at: i - 1) + } + default: + break + } + } else { + // Implement K&R-style braces, where opening brace appears on the same line + guard let prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), + formatter.tokens[prevIndex ..< i].contains(where: { $0.isLinebreak }), + !formatter.tokens[prevIndex].isComment + else { + return + } + + var maxWidth = formatter.options.maxWidth + if maxWidth == 0 { + maxWidth = .max + } + + // Check that unwrapping wouldn't exceed line length + let endOfLine = formatter.endOfLine(at: i) + let length = formatter.lineLength(from: i, upTo: endOfLine) + let prevLineLength = formatter.lineLength(at: prevIndex) + guard prevLineLength + length + 1 <= maxWidth else { + return + } + + // Avoid conflicts with wrapMultilineStatementBraces + // (Can't refer to `FormatRule.wrapMultilineStatementBraces` directly + // because it creates a cicrcular reference) + if formatter.options.enabledRules.contains("wrapMultilineStatementBraces"), + formatter.shouldWrapMultilineStatementBrace(at: i) + { + return + } + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) + } + } + } +} diff --git a/Sources/Rules/ConditionalAssignment.swift b/Sources/Rules/ConditionalAssignment.swift new file mode 100644 index 00000000..c7188f8b --- /dev/null +++ b/Sources/Rules/ConditionalAssignment.swift @@ -0,0 +1,251 @@ +// +// ConditionalAssignment.swift +// SwiftFormat +// +// Created by Cal Stephens on 2/14/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let conditionalAssignment = FormatRule( + help: "Assign properties using if / switch expressions.", + orderAfter: [.redundantReturn], + options: ["condassignment"] + ) { formatter in + // If / switch expressions were added in Swift 5.9 (SE-0380) + guard formatter.options.swiftVersion >= "5.9" else { + return + } + + formatter.forEach(.keyword) { startOfConditional, keywordToken in + // Look for an if/switch expression where the first branch starts with `identifier =` + guard ["if", "switch"].contains(keywordToken.string), + let conditionalBranches = formatter.conditionalBranches(at: startOfConditional), + var startOfFirstBranch = conditionalBranches.first?.startOfBranch + else { return } + + // Traverse any nested if/switch branches until we find the first code branch + while let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), + ["if", "switch"].contains(formatter.tokens[firstTokenInBranch].string), + let nestedConditionalBranches = formatter.conditionalBranches(at: firstTokenInBranch), + let startOfNestedBranch = nestedConditionalBranches.first?.startOfBranch + { + startOfFirstBranch = startOfNestedBranch + } + + // Check if the first branch starts with the pattern `lvalue =`. + guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), + let lvalueRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), + let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: lvalueRange.upperBound), + formatter.tokens[equalsIndex] == .operator("=", .infix) + else { return } + + guard conditionalBranches.allSatisfy({ formatter.isExhaustiveSingleStatementAssignment($0, lvalueRange: lvalueRange) }), + formatter.conditionalBranchesAreExhaustive(conditionKeywordIndex: startOfConditional, branches: conditionalBranches) + else { + return + } + + // If this expression follows a property like `let identifier: Type`, we just + // have to insert an `=` between property and the conditional. + // - Find the introducer (let/var), parse the property, and verify that the identifier + // matches the identifier assigned on each conditional branch. + if let introducerIndex = formatter.indexOfLastSignificantKeyword(at: startOfConditional, excluding: ["if", "switch"]), + ["let", "var"].contains(formatter.tokens[introducerIndex].string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + formatter.tokens[lvalueRange.lowerBound].string == property.identifier, + property.value == nil, + let typeRange = property.type?.range, + let nextTokenAfterProperty = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: typeRange.upperBound), + nextTokenAfterProperty == startOfConditional + { + formatter.removeAssignmentFromAllBranches(of: conditionalBranches) + + let rangeBetweenTypeAndConditional = (typeRange.upperBound + 1) ..< startOfConditional + + // If there are no comments between the type and conditional, + // we reformat it from: + // + // let foo: Foo\n + // if condition { + // + // to: + // + // let foo: Foo = if condition { + // + if formatter.tokens[rangeBetweenTypeAndConditional].allSatisfy(\.isSpaceOrLinebreak) { + formatter.replaceTokens(in: rangeBetweenTypeAndConditional, with: [ + .space(" "), + .operator("=", .infix), + .space(" "), + ]) + } + + // But if there are comments, then we shouldn't just delete them. + // Instead we just insert `= ` after the type. + else { + formatter.insert([.operator("=", .infix), .space(" ")], at: startOfConditional) + } + } + + // Otherwise we insert an `identifier =` before the if/switch expression + else if !formatter.options.conditionalAssignmentOnlyAfterNewProperties { + // In this case we should only apply the conversion if this is a top-level condition, + // and not nested in some parent condition. In large complex if/switch conditions + // with multiple layers of nesting, for example, this prevents us from making any + // changes unless the entire set of nested conditions can be converted as a unit. + // - First attempt to find and parse a parent if / switch condition. + var startOfParentScope = formatter.startOfScope(at: startOfConditional) + + // If we're inside a switch case, expand to look at the whole switch statement + while let currentStartOfParentScope = startOfParentScope, + formatter.tokens[currentStartOfParentScope] == .startOfScope(":"), + let caseToken = formatter.index(of: .endOfScope("case"), before: currentStartOfParentScope) + { + startOfParentScope = formatter.startOfScope(at: caseToken) + } + + if let startOfParentScope = startOfParentScope, + let mostRecentIfOrSwitch = formatter.index(of: .keyword, before: startOfParentScope, if: { ["if", "switch"].contains($0.string) }), + let conditionalBranches = formatter.conditionalBranches(at: mostRecentIfOrSwitch), + let startOfFirstParentBranch = conditionalBranches.first?.startOfBranch, + let endOfLastParentBranch = conditionalBranches.last?.endOfBranch, + // If this condition is contained within a parent condition, do nothing. + // We should only convert the entire set of nested conditions together as a unit. + (startOfFirstParentBranch ... endOfLastParentBranch).contains(startOfConditional) + { return } + + let lvalueTokens = formatter.tokens[lvalueRange] + + // Now we can remove the `identifier =` from each branch, + // and instead add it before the if / switch expression. + formatter.removeAssignmentFromAllBranches(of: conditionalBranches) + + let identifierEqualsTokens = lvalueTokens + [ + .space(" "), + .operator("=", .infix), + .space(" "), + ] + + formatter.insert(identifierEqualsTokens, at: startOfConditional) + } + } + } +} + +private extension Formatter { + // Whether or not the conditional statement that starts at the given index + // has branches that are exhaustive + func conditionalBranchesAreExhaustive( + conditionKeywordIndex: Int, + branches: [Formatter.ConditionalBranch] + ) + -> Bool + { + // Switch statements are compiler-guaranteed to be exhaustive + if tokens[conditionKeywordIndex] == .keyword("switch") { + return true + } + + // If statements are only exhaustive if the last branch + // is `else` (not `else if`). + else if tokens[conditionKeywordIndex] == .keyword("if"), + let lastCondition = branches.last, + let tokenBeforeLastCondition = index(of: .nonSpaceOrCommentOrLinebreak, before: lastCondition.startOfBranch) + { + return tokens[tokenBeforeLastCondition] == .keyword("else") + } + + return false + } + + // Whether or not the given conditional branch body qualifies as a single statement + // that assigns a value to `identifier`. This is either: + // 1. a single assignment to `lvalue =` + // 2. a single `if` or `switch` statement where each of the branches also qualify, + // and the statement is exhaustive. + func isExhaustiveSingleStatementAssignment(_ branch: Formatter.ConditionalBranch, lvalueRange: ClosedRange) -> Bool { + guard let firstTokenIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return false } + + // If this is an if/switch statement, verify that all of the branches are also + // single-statement assignments and that the statement is exhaustive. + if let conditionalBranches = conditionalBranches(at: firstTokenIndex), + let lastConditionalStatement = conditionalBranches.last + { + let allBranchesAreExhaustiveSingleStatement = conditionalBranches.allSatisfy { branch in + isExhaustiveSingleStatementAssignment(branch, lvalueRange: lvalueRange) + } + + let isOnlyStatementInScope = next(.nonSpaceOrCommentOrLinebreak, after: lastConditionalStatement.endOfBranch)?.isEndOfScope == true + + let isExhaustive = conditionalBranchesAreExhaustive( + conditionKeywordIndex: firstTokenIndex, + branches: conditionalBranches + ) + + return allBranchesAreExhaustiveSingleStatement + && isOnlyStatementInScope + && isExhaustive + } + + // Otherwise we expect this to be of the pattern `lvalue = (statement)` + else if let firstExpressionRange = parseExpressionRange(startingAt: firstTokenIndex), + tokens[firstExpressionRange] == tokens[lvalueRange], + let equalsIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), + tokens[equalsIndex] == .operator("=", .infix), + let valueStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) + { + // We know this branch starts with `identifier =`, but have to check that the + // remaining code in the branch is a single statement. To do that we can + // create a temporary formatter with the branch body _excluding_ `identifier =`. + let assignmentStatementRange = valueStartIndex ..< branch.endOfBranch + var tempScopeTokens = [Token]() + tempScopeTokens.append(.startOfScope("{")) + tempScopeTokens.append(contentsOf: tokens[assignmentStatementRange]) + tempScopeTokens.append(.endOfScope("}")) + + let tempFormatter = Formatter(tempScopeTokens, options: options) + guard tempFormatter.blockBodyHasSingleStatement( + atStartOfScope: 0, + includingConditionalStatements: true, + includingReturnStatements: false + ) else { + return false + } + + // In Swift 5.9, there's a bug that prevents you from writing an + // if or switch expression using an `as?` on one of the branches: + // https://github.com/apple/swift/issues/68764 + // + // let result = if condition { + // foo as? String + // } else { + // "bar" + // } + // + if tempFormatter.conditionalBranchHasUnsupportedCastOperator(startOfScopeIndex: 0) { + return false + } + + return true + } + + return false + } + + // Removes the `identifier =` from each conditional branch + func removeAssignmentFromAllBranches(of conditionalBranches: [ConditionalBranch]) { + forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in + guard let firstTokenIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch), + let firstExpressionRange = parseExpressionRange(startingAt: firstTokenIndex), + let equalsIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), + tokens[equalsIndex] == .operator("=", .infix), + let valueStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) + else { return } + + removeTokens(in: firstTokenIndex ..< valueStartIndex) + } + } +} diff --git a/Sources/Rules/ConsecutiveBlankLines.swift b/Sources/Rules/ConsecutiveBlankLines.swift new file mode 100644 index 00000000..dbc5ae5a --- /dev/null +++ b/Sources/Rules/ConsecutiveBlankLines.swift @@ -0,0 +1,32 @@ +// +// ConsecutiveBlankLines.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/30/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Collapse all consecutive blank lines into a single blank line + static let consecutiveBlankLines = FormatRule( + help: "Replace consecutive blank lines with a single blank line." + ) { formatter in + formatter.forEach(.linebreak) { i, _ in + guard let prevIndex = formatter.index(of: .nonSpace, before: i, if: { $0.isLinebreak }) else { + return + } + if let scope = formatter.currentScope(at: i), scope.isMultilineStringDelimiter { + return + } + if let nextIndex = formatter.index(of: .nonSpace, after: i) { + if formatter.tokens[nextIndex].isLinebreak { + formatter.removeTokens(in: i + 1 ... nextIndex) + } + } else if !formatter.options.fragment { + formatter.removeTokens(in: i ..< formatter.tokens.count) + } + } + } +} diff --git a/Sources/Rules/ConsecutiveSpaces.swift b/Sources/Rules/ConsecutiveSpaces.swift new file mode 100644 index 00000000..4d720d8a --- /dev/null +++ b/Sources/Rules/ConsecutiveSpaces.swift @@ -0,0 +1,48 @@ +// +// ConsecutiveSpaces.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/30/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Collapse all consecutive space characters to a single space, except at + /// the start of a line or inside a comment or string, as these have no semantic + /// meaning and lead to noise in commits. + static let consecutiveSpaces = FormatRule( + help: "Replace consecutive spaces with a single space." + ) { formatter in + formatter.forEach(.space) { i, token in + switch token { + case .space(""): + formatter.removeToken(at: i) + case .space(" "): + break + default: + guard let prevToken = formatter.token(at: i - 1), + let nextToken = formatter.token(at: i + 1) + else { + return + } + switch prevToken { + case .linebreak, .startOfScope("/*"), .startOfScope("//"), .commentBody: + return + case .endOfScope("*/") where nextToken == .startOfScope("/*") && + formatter.currentScope(at: i) == .startOfScope("/*"): + return + default: + break + } + switch nextToken { + case .linebreak, .endOfScope("*/"), .commentBody: + return + default: + formatter.replaceToken(at: i, with: .space(" ")) + } + } + } + } +} diff --git a/Sources/Rules/ConsistentSwitchCaseSpacing.swift b/Sources/Rules/ConsistentSwitchCaseSpacing.swift new file mode 100644 index 00000000..590e92fe --- /dev/null +++ b/Sources/Rules/ConsistentSwitchCaseSpacing.swift @@ -0,0 +1,47 @@ +// +// ConsistentSwitchCaseSpacing.swift +// SwiftFormat +// +// Created by Cal Stephens on 2/1/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let consistentSwitchCaseSpacing = FormatRule( + help: "Ensures consistent spacing among all of the cases in a switch statement.", + orderAfter: [.blankLineAfterSwitchCase] + ) { formatter in + formatter.forEach(.keyword("switch")) { switchIndex, _ in + guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } + + // When counting the switch cases, exclude the last case (which should never have a trailing blank line). + let countWithTrailingBlankLine = switchCases.filter { $0.isFollowedByBlankLine && !$0.isLastCase }.count + let countWithoutTrailingBlankLine = switchCases.filter { !$0.isFollowedByBlankLine && !$0.isLastCase }.count + + // We want the spacing to be consistent for all switch cases, + // so use whichever formatting is used for the majority of cases. + var allCasesShouldHaveBlankLine = countWithTrailingBlankLine >= countWithoutTrailingBlankLine + + // When the `blankLinesBetweenChainedFunctions` rule is enabled, and there is a switch case + // that is required to span multiple lines, then all cases must span multiple lines. + // (Since if this rule removed the blank line from that case, it would contradict the other rule) + if formatter.options.enabledRules.contains(FormatRule.blankLineAfterSwitchCase.name), + switchCases.contains(where: { $0.spansMultipleLines && !$0.isLastCase }) + { + allCasesShouldHaveBlankLine = true + } + + for switchCase in switchCases.reversed() { + if !switchCase.isFollowedByBlankLine, allCasesShouldHaveBlankLine, !switchCase.isLastCase { + switchCase.insertTrailingBlankLine(using: formatter) + } + + if switchCase.isFollowedByBlankLine, !allCasesShouldHaveBlankLine || switchCase.isLastCase { + switchCase.removeTrailingBlankLine(using: formatter) + } + } + } + } +} diff --git a/Sources/Rules/DocComments.swift b/Sources/Rules/DocComments.swift new file mode 100644 index 00000000..5a2fc4e4 --- /dev/null +++ b/Sources/Rules/DocComments.swift @@ -0,0 +1,177 @@ +// +// DocComments.swift +// SwiftFormat +// +// Created by Cal Stephens on 10/19/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let docComments = FormatRule( + help: "Use doc comments for API declarations, otherwise use regular comments.", + disabledByDefault: true, + orderAfter: [.fileHeader], + options: ["doccomments"] + ) { formatter in + formatter.forEach(.startOfScope) { index, token in + guard [.startOfScope("//"), .startOfScope("/*")].contains(token), + let endOfComment = formatter.endOfScope(at: index), + let nextDeclarationIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfComment) + else { + return + } + + func shouldBeDocComment(at index: Int, endOfComment: Int) -> Bool { + // Check if this is a special type of comment that isn't documentation + if case let .commentBody(body)? = formatter.next(.nonSpace, after: index), body.isCommentDirective { + return false + } + + // Check if this token defines a declaration that supports doc comments + var declarationToken = formatter.tokens[nextDeclarationIndex] + if declarationToken.isAttribute || declarationToken.isModifierKeyword, + let index = formatter.index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword }) + { + declarationToken = formatter.tokens[index] + } + guard declarationToken.isDeclarationTypeKeyword(excluding: ["import"]) else { + return false + } + + // Only use doc comments on declarations in type bodies, or top-level declarations + if let startOfEnclosingScope = formatter.index(of: .startOfScope, before: index) { + switch formatter.tokens[startOfEnclosingScope] { + case .startOfScope("#if"): + break + case .startOfScope("{"): + guard let scope = formatter.lastSignificantKeyword(at: startOfEnclosingScope, excluding: ["where"]), + ["class", "actor", "struct", "enum", "protocol", "extension"].contains(scope) + else { + return false + } + default: + return false + } + } + + // If there are blank lines between comment and declaration, comment is not treated as doc comment + let trailingTokens = formatter.tokens[(endOfComment - 1) ... nextDeclarationIndex] + let lines = trailingTokens.split(omittingEmptySubsequences: false, whereSeparator: \.isLinebreak) + if lines.contains(where: { $0.allSatisfy(\.isSpace) }) { + return false + } + + // Only comments at the start of a line can be doc comments + if let previousToken = formatter.index(of: .nonSpaceOrLinebreak, before: index) { + let commentLine = formatter.startOfLine(at: index) + let previousTokenLine = formatter.startOfLine(at: previousToken) + + if commentLine == previousTokenLine { + return false + } + } + + // Comments inside conditional statements are not doc comments + return !formatter.isConditionalStatement(at: index) + } + + var commentIndices = [index] + if token == .startOfScope("//") { + var i = index + while let prevLineIndex = formatter.index(of: .linebreak, before: i), + case let lineStartIndex = formatter.startOfLine(at: prevLineIndex, excludingIndent: true), + formatter.token(at: lineStartIndex) == .startOfScope("//") + { + commentIndices.append(lineStartIndex) + i = lineStartIndex + } + i = index + while let nextLineIndex = formatter.index(of: .linebreak, after: i), + let lineStartIndex = formatter.index(of: .nonSpace, after: nextLineIndex), + formatter.token(at: lineStartIndex) == .startOfScope("//") + { + commentIndices.append(lineStartIndex) + i = lineStartIndex + } + } + + let useDocComment = shouldBeDocComment(at: index, endOfComment: endOfComment) + guard commentIndices.allSatisfy({ + shouldBeDocComment(at: $0, endOfComment: endOfComment) == useDocComment + }) else { + return + } + + // Determine whether or not this is the start of a list of sequential declarations, like: + // + // // The placeholder names we use in test cases + // case foo + // case bar + // case baaz + // + // In these cases it's not obvious whether or not the comment refers to the property or + // the entire group, so we preserve the existing formatting. + var preserveRegularComments = false + if useDocComment, + let declarationKeyword = formatter.index(after: endOfComment, where: \.isDeclarationTypeKeyword), + let endOfDeclaration = formatter.endOfDeclaration(atDeclarationKeyword: declarationKeyword, fallBackToEndOfScope: false), + let nextDeclarationKeyword = formatter.index(after: endOfDeclaration, where: \.isDeclarationTypeKeyword) + { + let linebreaksBetweenDeclarations = formatter.tokens[declarationKeyword ... nextDeclarationKeyword] + .filter { $0.isLinebreak }.count + + // If there is only a single line break between the start of this declaration and the subsequent declaration, + // then they are written sequentially in a block. In this case, don't convert regular comments to doc comments. + if linebreaksBetweenDeclarations == 1 { + preserveRegularComments = true + } + } + + // Doc comment tokens like `///` and `/**` aren't parsed as a + // single `.startOfScope` token -- they're parsed as: + // `.startOfScope("//"), .commentBody("/ ...")` or + // `.startOfScope("/*"), .commentBody("* ...")` + let startOfDocCommentBody: String + switch token.string { + case "//": + startOfDocCommentBody = "/" + case "/*": + startOfDocCommentBody = "*" + default: + return + } + + let isDocComment = formatter.isDocComment(startOfComment: index) + + if isDocComment, + let commentBody = formatter.token(at: index + 1), + commentBody.isCommentBody + { + if useDocComment, !isDocComment, !preserveRegularComments { + let updatedCommentBody = "\(startOfDocCommentBody)\(commentBody.string)" + formatter.replaceToken(at: index + 1, with: .commentBody(updatedCommentBody)) + } else if !useDocComment, isDocComment, !formatter.options.preserveDocComments { + let prefix = commentBody.string.prefix(while: { String($0) == startOfDocCommentBody }) + + // Do nothing if this is a unusual comment like `//////////////////` + // or `/****************`. We can't just remove one of the tokens, because + // that would make this rule have a different output each time, but we + // shouldn't remove all of them since that would be unexpected. + if prefix.count > 1 { + return + } + + formatter.replaceToken( + at: index + 1, + with: .commentBody(String(commentBody.string.dropFirst())) + ) + } + + } else if useDocComment, !preserveRegularComments { + formatter.insert(.commentBody(startOfDocCommentBody), at: index + 1) + } + } + } +} diff --git a/Sources/Rules/DocCommentsBeforeAttributes.swift b/Sources/Rules/DocCommentsBeforeAttributes.swift new file mode 100644 index 00000000..1e7200be --- /dev/null +++ b/Sources/Rules/DocCommentsBeforeAttributes.swift @@ -0,0 +1,42 @@ +// +// DocCommentsBeforeAttributes.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/22/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let docCommentsBeforeAttributes = FormatRule( + help: "Place doc comments on declarations before any attributes.", + orderAfter: [.docComments] + ) { formatter in + formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in + // Parse the attributes on this declaration if present + let startOfAttributes = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) + guard formatter.tokens[startOfAttributes].isAttribute else { return } + + let attributes = formatter.attributes(startingAt: startOfAttributes) + guard !attributes.isEmpty else { return } + + let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex + + // If there's a doc comment between the attributes and the rest of the declaration, + // move it above the attributes. + guard let linebreakAfterAttributes = formatter.index(of: .linebreak, after: attributesRange.upperBound), + let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: linebreakAfterAttributes), + indexAfterAttributes < keywordIndex, + let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), + formatter.isDocComment(startOfComment: indexAfterAttributes) + else { return } + + let commentRange = indexAfterAttributes ..< restOfDeclaration + let comment = formatter.tokens[commentRange] + + formatter.removeTokens(in: commentRange) + formatter.insert(comment, at: startOfAttributes) + } + } +} diff --git a/Sources/Rules/DuplicateImports.swift b/Sources/Rules/DuplicateImports.swift new file mode 100644 index 00000000..10a3f869 --- /dev/null +++ b/Sources/Rules/DuplicateImports.swift @@ -0,0 +1,35 @@ +// +// DuplicateImports.swift +// SwiftFormat +// +// Created by Nick Lockwood on 2/7/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove duplicate import statements + static let duplicateImports = FormatRule( + help: "Remove duplicate import statements." + ) { formatter in + for var importRanges in formatter.parseImports().reversed() { + for i in importRanges.indices.reversed() { + let range = importRanges.remove(at: i) + guard let j = importRanges.firstIndex(where: { $0.module == range.module }) else { + continue + } + let range2 = importRanges[j] + if Set(range.attributes).isSubset(of: range2.attributes) { + formatter.removeTokens(in: range.range) + continue + } + if j >= i { + formatter.removeTokens(in: range2.range) + importRanges.remove(at: j) + } + importRanges.append(range) + } + } + } +} diff --git a/Sources/Rules/ElseOnSameLine.swift b/Sources/Rules/ElseOnSameLine.swift new file mode 100644 index 00000000..317bb28d --- /dev/null +++ b/Sources/Rules/ElseOnSameLine.swift @@ -0,0 +1,118 @@ +// +// ElseOnSameLine.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that an `else` statement following `if { ... }` appears on the same line + /// as the closing brace. This has no effect on the `else` part of a `guard` statement. + /// Also applies to `catch` after `try` and `while` after `repeat`. + static let elseOnSameLine = FormatRule( + help: """ + Place `else`, `catch` or `while` keyword in accordance with current style (same or + next line). + """, + orderAfter: [.wrapMultilineStatementBraces], + options: ["elseposition", "guardelse"], + sharedOptions: ["allman", "linebreaks"] + ) { formatter in + func bracesContainLinebreak(_ endIndex: Int) -> Bool { + guard let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { + return false + } + return (startIndex ..< endIndex).contains(where: { formatter.tokens[$0].isLinebreak }) + } + formatter.forEachToken { i, token in + switch token { + case .keyword("while"): + if let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .endOfScope("}") + }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex), + formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex) == .keyword("repeat") { + fallthrough + } + case .keyword("else"): + guard var prevIndex = formatter.index(of: .nonSpace, before: i), + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + !$0.isComment + }) + else { + return + } + let isOnNewLine = formatter.tokens[prevIndex].isLinebreak + if isOnNewLine { + prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) ?? prevIndex + } + if formatter.tokens[prevIndex] == .endOfScope("}") { + fallthrough + } + guard let guardIndex = formatter.indexOfLastSignificantKeyword(at: prevIndex + 1, excluding: [ + "var", "let", "case", + ]), formatter.tokens[guardIndex] == .keyword("guard") else { + return + } + let shouldWrap: Bool + switch formatter.options.guardElsePosition { + case .auto: + // Only wrap if else or following brace is on next line + shouldWrap = isOnNewLine || + formatter.tokens[i + 1 ..< nextIndex].contains { $0.isLinebreak } + case .nextLine: + // Only wrap if guard statement spans multiple lines + shouldWrap = isOnNewLine || + formatter.tokens[guardIndex + 1 ..< nextIndex].contains { $0.isLinebreak } + case .sameLine: + shouldWrap = false + } + if shouldWrap { + if !formatter.options.allmanBraces { + formatter.replaceTokens(in: i + 1 ..< nextIndex, with: .space(" ")) + } + if !isOnNewLine { + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: + formatter.linebreakToken(for: prevIndex + 1)) + formatter.insertSpace(formatter.currentIndentForLine(at: guardIndex), at: prevIndex + 2) + } + } else if isOnNewLine { + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) + } + case .keyword("catch"): + guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { + return + } + + let precededByBlankLine = formatter.tokens[prevIndex].isLinebreak + && formatter.lastToken(before: prevIndex, where: { !$0.isSpaceOrComment })?.isLinebreak == true + + if precededByBlankLine { + return + } + + let shouldWrap = formatter.options.allmanBraces || formatter.options.elseOnNextLine + if !shouldWrap, formatter.tokens[prevIndex].isLinebreak { + if let prevBraceIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex, if: { + $0 == .endOfScope("}") + }), bracesContainLinebreak(prevBraceIndex) { + formatter.replaceTokens(in: prevBraceIndex + 1 ..< i, with: .space(" ")) + } + } else if shouldWrap, let token = formatter.token(at: prevIndex), !token.isLinebreak, + let prevBraceIndex = (token == .endOfScope("}")) ? prevIndex : + formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex, if: { + $0 == .endOfScope("}") + }), bracesContainLinebreak(prevBraceIndex) + { + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: + formatter.linebreakToken(for: prevIndex + 1)) + formatter.insertSpace(formatter.currentIndentForLine(at: prevIndex + 1), at: prevIndex + 2) + } + default: + break + } + } + } +} diff --git a/Sources/Rules/EmptyBraces.swift b/Sources/Rules/EmptyBraces.swift new file mode 100644 index 00000000..32388fae --- /dev/null +++ b/Sources/Rules/EmptyBraces.swift @@ -0,0 +1,41 @@ +// +// EmptyBraces.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/2/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove white-space between empty braces + static let emptyBraces = FormatRule( + help: "Remove whitespace inside empty braces.", + options: ["emptybraces"], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard let closingIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .endOfScope("}") + }) else { + return + } + if let token = formatter.next(.nonSpaceOrComment, after: closingIndex), + [.keyword("else"), .keyword("catch")].contains(token) + { + return + } + let range = i + 1 ..< closingIndex + switch formatter.options.emptyBracesSpacing { + case .noSpace: + formatter.removeTokens(in: range) + case .spaced: + formatter.replaceTokens(in: range, with: .space(" ")) + case .linebreak: + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: range.endIndex) + formatter.replaceTokens(in: range, with: formatter.linebreakToken(for: i + 1)) + } + } + } +} diff --git a/Sources/Rules/EnumNamespaces.swift b/Sources/Rules/EnumNamespaces.swift new file mode 100644 index 00000000..43d777b2 --- /dev/null +++ b/Sources/Rules/EnumNamespaces.swift @@ -0,0 +1,124 @@ +// +// EnumNamespaces.swift +// SwiftFormat +// +// Created by Facundo Menzella on 9/20/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Converts types used for hosting only static members into enums to avoid instantiation. + static let enumNamespaces = FormatRule( + help: """ + Convert types used for hosting only static members into enums (an empty enum is + the canonical way to create a namespace in Swift as it can't be instantiated). + """, + options: ["enumnamespaces"] + ) { formatter in + formatter.forEachToken(where: { [.keyword("class"), .keyword("struct")].contains($0) }) { i, token in + if token == .keyword("class") { + guard let next = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i), + // exit if structs only + formatter.options.enumNamespaces != .structsOnly, + // exit if class is a type modifier + !(next.isKeywordOrAttribute || next.isModifierKeyword), + // exit for class as protocol conformance + formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .delimiter(":"), + // exit if not closed for extension + formatter.modifiersForDeclaration(at: i, contains: "final") + else { + return + } + } + guard let braceIndex = formatter.index(of: .startOfScope("{"), after: i), + // exit if import statement + formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import"), + // exit if has attribute(s) + !formatter.modifiersForDeclaration(at: i, contains: { $1.hasPrefix("@") }), + // exit if type is conforming any other types + !formatter.tokens[i ... braceIndex].contains(.delimiter(":")), + let endIndex = formatter.index(of: .endOfScope("}"), after: braceIndex), + case let .identifier(name)? = formatter.next(.identifier, after: i + 1) + else { + return + } + let range = braceIndex + 1 ..< endIndex + if formatter.rangeHostsOnlyStaticMembersAtTopLevel(range), + !formatter.rangeContainsTypeInit(name, in: range), !formatter.rangeContainsSelfAssignment(range) + { + formatter.replaceToken(at: i, with: .keyword("enum")) + + if let finalIndex = formatter.indexOfModifier("final", forDeclarationAt: i), + let nextIndex = formatter.index(of: .nonSpace, after: finalIndex) + { + formatter.removeTokens(in: finalIndex ..< nextIndex) + } + } + } + } +} + +private extension Formatter { + func rangeHostsOnlyStaticMembersAtTopLevel(_ range: Range) -> Bool { + // exit for empty declarations + guard next(.nonSpaceOrCommentOrLinebreak, in: range) != nil else { + return false + } + + var j = range.startIndex + while j < range.endIndex, let token = token(at: j) { + if token == .startOfScope("{"), + let skip = index(of: .endOfScope("}"), after: j) + { + j = skip + continue + } + // exit if there's a explicit init + if token == .keyword("init") { + return false + } else if [.keyword("let"), + .keyword("var"), + .keyword("func")].contains(token), + !modifiersForDeclaration(at: j, contains: "static") + { + return false + } + j += 1 + } + return true + } + + func rangeContainsTypeInit(_ type: String, in range: Range) -> Bool { + for i in range { + guard case let .identifier(name) = tokens[i], + [type, "Self", "self"].contains(name) + else { + continue + } + if let nextIndex = index(of: .nonSpaceOrComment, after: i), + let nextToken = token(at: nextIndex), nextToken == .startOfScope("(") || + (nextToken == .operator(".", .infix) && [.identifier("init"), .identifier("self")] + .contains(next(.nonSpaceOrComment, after: nextIndex) ?? .space(""))) + { + return true + } + } + return false + } + + func rangeContainsSelfAssignment(_ range: Range) -> Bool { + for i in range { + guard case .identifier("self") = tokens[i] else { + continue + } + if let token = last(.nonSpaceOrCommentOrLinebreak, before: i), + [.operator("=", .infix), .delimiter(":"), .startOfScope("(")].contains(token) + { + return true + } + } + return false + } +} diff --git a/Sources/Rules/ExtensionAccessControl.swift b/Sources/Rules/ExtensionAccessControl.swift new file mode 100644 index 00000000..748d5e2e --- /dev/null +++ b/Sources/Rules/ExtensionAccessControl.swift @@ -0,0 +1,121 @@ +// +// ExtensionAccessControl.swift +// SwiftFormat +// +// Created by Cal Stephens on 9/25/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let extensionAccessControl = FormatRule( + help: "Configure the placement of an extension's access control keyword.", + options: ["extensionacl"] + ) { formatter in + guard !formatter.options.fragment else { return } + + let declarations = formatter.parseDeclarations() + let updatedDeclarations = formatter.mapRecursiveDeclarations(declarations) { declaration, _ in + guard case let .type("extension", open, body, close, _) = declaration else { + return declaration + } + + let visibilityKeyword = formatter.visibility(of: declaration) + // `private` visibility at top level of file is equivalent to `fileprivate` + let extensionVisibility = (visibilityKeyword == .private) ? .fileprivate : visibilityKeyword + + switch formatter.options.extensionACLPlacement { + // If all declarations in the extension have the same visibility, + // remove the keyword from the individual declarations and + // place it on the extension itself. + case .onExtension: + if extensionVisibility == nil, + let delimiterIndex = declaration.openTokens.firstIndex(of: .delimiter(":")), + declaration.openTokens.firstIndex(of: .keyword("where")).map({ $0 > delimiterIndex }) ?? true + { + // Extension adds protocol conformance so can't have visibility modifier + return declaration + } + + let visibilityOfBodyDeclarations = formatter + .mapDeclarations(body) { + formatter.visibility(of: $0) ?? extensionVisibility ?? .internal + } + .compactMap { $0 } + + let counts = Set(visibilityOfBodyDeclarations).sorted().map { visibility in + (visibility, count: visibilityOfBodyDeclarations.filter { $0 == visibility }.count) + } + + guard let memberVisibility = counts.max(by: { $0.count < $1.count })?.0, + memberVisibility <= extensionVisibility ?? .public, + // Check that most common level is also most visible + memberVisibility == visibilityOfBodyDeclarations.max(), + // `private` can't be hoisted without changing code behavior + // (private applied at extension level is equivalent to `fileprivate`) + memberVisibility > .private + else { return declaration } + + if memberVisibility > extensionVisibility ?? .internal { + // Check type being extended does not have lower visibility + for d in declarations where d.name == declaration.name { + if case let .type(kind, _, _, _, _) = d { + if kind != "extension", formatter.visibility(of: d) ?? .internal < memberVisibility { + // Cannot make extension with greater visibility than type being extended + return declaration + } + break + } + } + } + + let extensionWithUpdatedVisibility: Declaration + if memberVisibility == extensionVisibility || + (memberVisibility == .internal && visibilityKeyword == nil) + { + extensionWithUpdatedVisibility = declaration + } else { + extensionWithUpdatedVisibility = formatter.add(memberVisibility, to: declaration) + } + + return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in + let visibility = formatter.visibility(of: bodyDeclaration) + if memberVisibility > visibility ?? extensionVisibility ?? .internal { + if visibility == nil { + return formatter.add(.internal, to: bodyDeclaration) + } + return bodyDeclaration + } + return formatter.remove(memberVisibility, from: bodyDeclaration) + } + + // Move the extension's visibility keyword to each individual declaration + case .onDeclarations: + // If the extension visibility is unspecified then there isn't any work to do + guard let extensionVisibility = extensionVisibility else { + return declaration + } + + // Remove the visibility keyword from the extension declaration itself + let extensionWithUpdatedVisibility = formatter.remove(visibilityKeyword!, from: declaration) + + // And apply the extension's visibility to each of its child declarations + // that don't have an explicit visibility keyword + return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in + if formatter.visibility(of: bodyDeclaration) == nil { + // If there was no explicit visibility keyword, then this declaration + // was using the visibility of the extension itself. + return formatter.add(extensionVisibility, to: bodyDeclaration) + } else { + // Keep the existing visibility + return bodyDeclaration + } + } + } + } + + let updatedTokens = updatedDeclarations.flatMap { $0.tokens } + formatter.replaceTokens(in: formatter.tokens.indices, with: updatedTokens) + } +} diff --git a/Sources/Rules/FileHeader.swift b/Sources/Rules/FileHeader.swift new file mode 100644 index 00000000..003833f2 --- /dev/null +++ b/Sources/Rules/FileHeader.swift @@ -0,0 +1,83 @@ +// +// FileHeader.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/7/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Strip header comments from the file + static let fileHeader = FormatRule( + help: "Use specified source file header template for all files.", + runOnceOnly: true, + options: ["header", "dateformat", "timezone"], + sharedOptions: ["linebreaks"] + ) { formatter in + var headerTokens = [Token]() + var directives = [String]() + switch formatter.options.fileHeader { + case .ignore: + return + case var .replace(string): + let file = formatter.options.fileInfo + let options = ReplacementOptions( + dateFormat: formatter.options.dateFormat, + timeZone: formatter.options.timeZone + ) + + for (key, replacement) in formatter.options.fileInfo.replacements { + if let replacementStr = replacement.resolve(file, options) { + while let range = string.range(of: "{\(key.rawValue)}") { + string.replaceSubrange(range, with: replacementStr) + } + } + } + headerTokens = tokenize(string) + directives = headerTokens.compactMap { + guard case let .commentBody(body) = $0 else { + return nil + } + return body.commentDirective + } + } + + guard let headerRange = formatter.headerCommentTokenRange(includingDirectives: directives) else { + return + } + + if headerTokens.isEmpty { + formatter.removeTokens(in: headerRange) + return + } + + var lastHeaderTokenIndex = headerRange.endIndex - 1 + let endIndex = lastHeaderTokenIndex + headerTokens.count + if formatter.tokens.endIndex > endIndex, headerTokens == Array(formatter.tokens[ + lastHeaderTokenIndex + 1 ... endIndex + ]) { + lastHeaderTokenIndex += headerTokens.count + } + let headerLinebreaks = headerTokens.reduce(0) { result, token -> Int in + result + (token.isLinebreak ? 1 : 0) + } + if lastHeaderTokenIndex < formatter.tokens.count - 1 { + headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 1)) + if lastHeaderTokenIndex < formatter.tokens.count - 2, + !formatter.tokens[lastHeaderTokenIndex + 1 ... lastHeaderTokenIndex + 2].allSatisfy({ + $0.isLinebreak + }) + { + headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 2)) + } + } + if let index = formatter.index(of: .nonSpace, after: lastHeaderTokenIndex, if: { + $0.isLinebreak + }) { + lastHeaderTokenIndex = index + } + formatter.replaceTokens(in: headerRange.startIndex ..< lastHeaderTokenIndex + 1, with: headerTokens) + } +} diff --git a/Sources/Rules/GenericExtensions.swift b/Sources/Rules/GenericExtensions.swift new file mode 100644 index 00000000..0cad16f7 --- /dev/null +++ b/Sources/Rules/GenericExtensions.swift @@ -0,0 +1,127 @@ +// +// GenericExtensions.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/18/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let genericExtensions = FormatRule( + help: """ + Use angle brackets (`extension Array`) for generic type extensions + instead of type constraints (`extension Array where Element == Foo`). + """, + options: ["generictypes"] + ) { formatter in + formatter.forEach(.keyword("extension")) { extensionIndex, _ in + guard // Angle brackets syntax in extensions is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + let typeNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: extensionIndex), + let extendedType = formatter.token(at: typeNameIndex)?.string, + // If there's already an open angle bracket after the generic type name + // then the extension is already using the target syntax, so there's + // no work to do + formatter.next(.nonSpaceOrCommentOrLinebreak, after: typeNameIndex) != .startOfScope("<"), + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: typeNameIndex), + let whereIndex = formatter.index(of: .keyword("where"), after: typeNameIndex), + whereIndex < openBraceIndex + else { return } + + // Prepopulate a `Self` generic type, which is implicitly present in extension definitions + let selfType = Formatter.GenericType( + name: "Self", + definitionSourceRange: typeNameIndex ... typeNameIndex, + conformances: [ + Formatter.GenericType.GenericConformance( + name: extendedType, + typeName: "Self", + type: .concreteType, + sourceRange: typeNameIndex ... typeNameIndex + ), + ] + ) + + var genericTypes = [selfType] + + // Parse the generic constraints in the where clause + formatter.parseGenericTypes( + from: whereIndex, + to: openBraceIndex, + into: &genericTypes, + qualifyGenericTypeName: { genericTypeName in + // In an extension all types implicitly refer to `Self`. + // For example, `Element == Foo` is actually fully-qualified as + // `Self.Element == Foo`. Using the fully-qualified `Self.Element` name + // here makes it so the generic constraint is populated as a child + // of `selfType`. + if !genericTypeName.hasPrefix("Self.") { + return "Self." + genericTypeName + } else { + return genericTypeName + } + } + ) + + var knownGenericTypes: [(name: String, genericTypes: [String])] = [ + (name: "Collection", genericTypes: ["Element"]), + (name: "Sequence", genericTypes: ["Element"]), + (name: "Array", genericTypes: ["Element"]), + (name: "Set", genericTypes: ["Element"]), + (name: "Dictionary", genericTypes: ["Key", "Value"]), + (name: "Optional", genericTypes: ["Wrapped"]), + ] + + // Users can provide additional generic types via the `generictypes` option + for userProvidedType in formatter.options.genericTypes.components(separatedBy: ";") { + guard let openAngleBracket = userProvidedType.firstIndex(of: "<"), + let closeAngleBracket = userProvidedType.firstIndex(of: ">") + else { continue } + + let typeName = String(userProvidedType[..= "5.5" else { return } + + formatter.forEachToken(where: { + $0 == .startOfScope("(") || $0 == .startOfScope("[") + }) { i, _ in + formatter.hoistEffectKeyword("await", inScopeAt: i) { prevIndex in + formatter.isSymbol(at: prevIndex, in: formatter.options.asyncCapturing) + } + } + } +} diff --git a/Sources/Rules/HoistPatternLet.swift b/Sources/Rules/HoistPatternLet.swift new file mode 100644 index 00000000..060d1835 --- /dev/null +++ b/Sources/Rules/HoistPatternLet.swift @@ -0,0 +1,167 @@ +// +// HoistPatternLet.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/6/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Move `let` and `var` inside patterns to the beginning + static let hoistPatternLet = FormatRule( + help: "Reposition `let` or `var` bindings within pattern.", + options: ["patternlet"] + ) { formatter in + func indicesOf(_ keyword: String, in range: CountableRange) -> [Int]? { + var indices = [Int]() + var keywordFound = false, identifierFound = false + var count = 0 + for index in range { + switch formatter.tokens[index] { + case .keyword(keyword): + indices.append(index) + keywordFound = true + case .identifier("_"): + break + case .identifier where formatter.last(.nonSpaceOrComment, before: index) != .operator(".", .prefix): + identifierFound = true + if keywordFound { + count += 1 + } + case .delimiter(","): + guard keywordFound || !identifierFound else { + return nil + } + keywordFound = false + identifierFound = false + case .startOfScope("{"): + return nil + case .startOfScope("<"): + // See: https://github.com/nicklockwood/SwiftFormat/issues/768 + return nil + default: + break + } + } + return (keywordFound || !identifierFound) && count > 0 ? indices : nil + } + + formatter.forEach(.startOfScope("(")) { i, _ in + let hoist = formatter.options.hoistPatternLet + // Check if pattern already starts with let/var + guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), + let prevIndex = formatter.index(before: i, where: { + switch $0 { + case .operator(".", _), .keyword("let"), .keyword("var"), + .endOfScope("*/"): + return false + case .endOfScope, .delimiter, .operator, .keyword: + return true + default: + return false + } + }) + else { + return + } + switch formatter.tokens[prevIndex] { + case .endOfScope("case"), .keyword("case"), .keyword("catch"): + break + case .delimiter(","): + loop: for token in formatter.tokens[0 ..< prevIndex].reversed() { + switch token { + case .endOfScope("case"), .keyword("catch"): + break loop + case .keyword("var"), .keyword("let"): + break + case .keyword: + // Tuple assignment + return + default: + break + } + } + default: + return + } + let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: prevIndex) + ?? (prevIndex + 1) + if case let .keyword(keyword) = formatter.tokens[startIndex], + ["let", "var"].contains(keyword) + { + if hoist { + // No changes needed + return + } + // Find variable indices + var indices = [Int]() + var index = i + 1 + var wasParenOrCommaOrLabel = true + while index < endIndex { + let token = formatter.tokens[index] + switch token { + case .delimiter(","), .startOfScope("("), .delimiter(":"): + wasParenOrCommaOrLabel = true + case .identifier("_"), .identifier("true"), .identifier("false"), .identifier("nil"): + wasParenOrCommaOrLabel = false + case let .identifier(name) where wasParenOrCommaOrLabel: + wasParenOrCommaOrLabel = false + let next = formatter.next(.nonSpaceOrComment, after: index) + if next != .operator(".", .infix), next != .delimiter(":") { + indices.append(index) + } + case _ where token.isSpaceOrCommentOrLinebreak: + break + case .startOfScope("["): + guard let next = formatter.endOfScope(at: index) else { + return formatter.fatalError("Expected ]", at: index) + } + index = next + default: + wasParenOrCommaOrLabel = false + } + index += 1 + } + // Insert keyword at indices + for index in indices.reversed() { + formatter.insert([.keyword(keyword), .space(" ")], at: index) + } + // Remove keyword + let range = ((formatter.index(of: .nonSpace, before: startIndex) ?? + (prevIndex - 1)) + 1) ... startIndex + formatter.removeTokens(in: range) + } else if hoist { + // Find let/var keyword indices + var keyword = "let" + guard let indices: [Int] = { + guard let indices = indicesOf(keyword, in: i + 1 ..< endIndex) else { + keyword = "var" + return indicesOf(keyword, in: i + 1 ..< endIndex) + } + return indices + }() else { + return + } + // Remove keywords inside parens + for index in indices.reversed() { + if formatter.tokens[index + 1].isSpace { + formatter.removeToken(at: index + 1) + } + formatter.removeToken(at: index) + } + // Insert keyword before parens + formatter.insert(.keyword(keyword), at: startIndex) + if let nextToken = formatter.token(at: startIndex + 1), !nextToken.isSpaceOrLinebreak { + formatter.insert(.space(" "), at: startIndex + 1) + } + if let prevToken = formatter.token(at: startIndex - 1), + !prevToken.isSpaceOrCommentOrLinebreak, !prevToken.isStartOfScope + { + formatter.insert(.space(" "), at: startIndex) + } + } + } + } +} diff --git a/Sources/Rules/HoistTry.swift b/Sources/Rules/HoistTry.swift new file mode 100644 index 00000000..f8895b9b --- /dev/null +++ b/Sources/Rules/HoistTry.swift @@ -0,0 +1,28 @@ +// +// HoistTry.swift +// SwiftFormat +// +// Created by Facundo Menzella on 2/25/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let hoistTry = FormatRule( + help: "Move inline `try` keyword(s) to start of expression.", + options: ["throwcapturing"] + ) { formatter in + let names = formatter.options.throwCapturing.union(["expect"]) + formatter.forEachToken(where: { + $0 == .startOfScope("(") || $0 == .startOfScope("[") + }) { i, _ in + formatter.hoistEffectKeyword("try", inScopeAt: i) { prevIndex in + guard case let .identifier(name) = formatter.tokens[prevIndex] else { + return false + } + return name.hasPrefix("XCTAssert") || formatter.isSymbol(at: prevIndex, in: names) + } + } + } +} diff --git a/Sources/Rules/Indent.swift b/Sources/Rules/Indent.swift new file mode 100644 index 00000000..5ea3febb --- /dev/null +++ b/Sources/Rules/Indent.swift @@ -0,0 +1,798 @@ +// +// Indent.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Indent code according to standard scope indenting rules. + /// The type (tab or space) and level (2 spaces, 4 spaces, etc.) of the + /// indenting can be configured with the `options` parameter of the formatter. + static let indent = FormatRule( + help: "Indent code in accordance with the scope level.", + orderAfter: [.trailingSpace, .wrap, .wrapArguments], + options: ["indent", "tabwidth", "smarttabs", "indentcase", "ifdef", "xcodeindentation", "indentstrings"], + sharedOptions: ["trimwhitespace", "allman", "wrapconditions", "wrapternary"] + ) { formatter in + var scopeStack: [Token] = [] + var scopeStartLineIndexes: [Int] = [] + var lastNonSpaceOrLinebreakIndex = -1 + var lastNonSpaceIndex = -1 + var indentStack = [""] + var stringBodyIndentStack = [""] + var indentCounts = [1] + var linewrapStack = [false] + var lineIndex = 0 + + func inFunctionDeclarationWhereReturnTypeIsWrappedToStartOfLine(at i: Int) -> Bool { + guard let returnOperatorIndex = formatter.startOfReturnType(at: i) else { + return false + } + return formatter.last(.nonSpaceOrComment, before: returnOperatorIndex)?.isLinebreak == true + } + + func isFirstStackedClosureArgument(at i: Int) -> Bool { + assert(formatter.tokens[i] == .startOfScope("{")) + if let prevIndex = formatter.index(of: .nonSpace, before: i), + let prevToken = formatter.token(at: prevIndex), prevToken == .startOfScope("(") || + (prevToken == .delimiter(":") && formatter.token(at: prevIndex - 1)?.isIdentifier == true + && formatter.last(.nonSpace, before: prevIndex - 1) == .startOfScope("(")), + let endIndex = formatter.endOfScope(at: i), + let commaIndex = formatter.index(of: .nonSpace, after: endIndex, if: { + $0 == .delimiter(",") + }), + formatter.next(.nonSpaceOrComment, after: commaIndex)?.isLinebreak == true + { + return true + } + return false + } + + if formatter.options.fragment, + let firstIndex = formatter.index(of: .nonSpaceOrLinebreak, after: -1), + let indentToken = formatter.token(at: firstIndex - 1), case let .space(string) = indentToken + { + indentStack[0] = string + } + formatter.forEachToken(onlyWhereEnabled: false) { i, token in + func popScope() { + if linewrapStack.last == true { + indentStack.removeLast() + stringBodyIndentStack.removeLast() + } + indentStack.removeLast() + stringBodyIndentStack.removeLast() + indentCounts.removeLast() + linewrapStack.removeLast() + scopeStartLineIndexes.removeLast() + scopeStack.removeLast() + } + + func stringBodyIndent(at i: Int) -> String { + var space = "" + let start = formatter.startOfLine(at: i) + if let index = formatter.index(of: .nonSpace, in: start ..< i), + case let .stringBody(string) = formatter.tokens[index], + string.unicodeScalars.first?.isSpace == true + { + var index = string.startIndex + while index < string.endIndex, string[index].unicodeScalars.first!.isSpace { + space.append(string[index]) + index = string.index(after: index) + } + } + return space + } + + var i = i + switch token { + case let .startOfScope(string): + switch string { + case ":" where scopeStack.last == .endOfScope("case"): + popScope() + case "{" where !formatter.isStartOfClosure(at: i, in: scopeStack.last) && + linewrapStack.last == true: + indentStack.removeLast() + linewrapStack[linewrapStack.count - 1] = false + default: + break + } + // Handle start of scope + scopeStack.append(token) + var indentCount: Int + if lineIndex > scopeStartLineIndexes.last ?? -1 { + indentCount = 1 + } else if token.isMultilineStringDelimiter, let endIndex = formatter.endOfScope(at: i), + let closingIndex = formatter.index(of: .endOfScope(")"), after: endIndex), + formatter.next(.linebreak, in: endIndex + 1 ..< closingIndex) != nil + { + indentCount = 1 + } else if scopeStack.count > 1, scopeStack[scopeStack.count - 2] == .startOfScope(":") { + indentCount = 1 + } else { + indentCount = indentCounts.last! + 1 + } + var indent = indentStack[indentStack.count - indentCount] + + switch string { + case "/*": + if scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("/*") { + // Comments only indent one space + indent += " " + } + case ":": + indent += formatter.options.indent + if formatter.options.indentCase, + scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("#if") + { + indent += formatter.options.indent + } + case "#if": + if let lineIndex = formatter.index(of: .linebreak, after: i), + let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ + .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), + ].contains(nextKeyword) + { + indent = indentStack[indentStack.count - indentCount - 1] + if formatter.options.indentCase { + indent += formatter.options.indent + } + } + switch formatter.options.ifdefIndent { + case .indent: + i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) + indent += formatter.options.indent + case .noIndent: + i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) + case .outdent: + i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) + } + case "{" where isFirstStackedClosureArgument(at: i): + guard var prevIndex = formatter.index(of: .nonSpace, before: i) else { + assertionFailure() + break + } + if formatter.tokens[prevIndex] == .delimiter(":") { + guard formatter.token(at: prevIndex - 1)?.isIdentifier == true, + let parenIndex = formatter.index(of: .nonSpace, before: prevIndex - 1, if: { + $0 == .startOfScope("(") + }) + else { + let stringIndent = stringBodyIndent(at: i) + stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent + indent += stringIndent + formatter.options.indent + break + } + prevIndex = parenIndex + } + let startIndex = formatter.startOfLine(at: i) + indent = formatter.spaceEquivalentToTokens(from: startIndex, upTo: prevIndex + 1) + indentStack[indentStack.count - 1] = indent + indent += formatter.options.indent + indentCount -= 1 + case "{" where formatter.isStartOfClosure(at: i): + // When a trailing closure starts on the same line as the end of a multi-line + // method call the trailing closure body should be double-indented + if let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i), + formatter.tokens[prevIndex] == .endOfScope(")"), + case let prevIndent = formatter.currentIndentForLine(at: prevIndex), + prevIndent == indent + formatter.options.indent + { + if linewrapStack.last == false { + linewrapStack[linewrapStack.count - 1] = true + indentStack.append(prevIndent) + stringBodyIndentStack.append("") + } + indent = prevIndent + } + let stringIndent = stringBodyIndent(at: i) + stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent + indent += stringIndent + formatter.options.indent + case _ where token.isStringDelimiter, "//": + break + case "[", "(": + guard let linebreakIndex = formatter.index(of: .linebreak, after: i), + let nextIndex = formatter.index(of: .nonSpace, after: i), + nextIndex != linebreakIndex + else { + fallthrough + } + if formatter.last(.nonSpaceOrComment, before: linebreakIndex) != .delimiter(","), + formatter.next(.nonSpaceOrComment, after: linebreakIndex) != .delimiter(",") + { + fallthrough + } + let start = formatter.startOfLine(at: i) + // Align indent with previous value + let lastIndentCount = indentCounts.last ?? 0 + if indentCount > lastIndentCount { + indentCount = lastIndentCount + indentCounts[indentCounts.count - 1] = 1 + } + indent = formatter.spaceEquivalentToTokens(from: start, upTo: nextIndex) + default: + let stringIndent = stringBodyIndent(at: i) + stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent + indent += stringIndent + formatter.options.indent + } + indentStack.append(indent) + stringBodyIndentStack.append("") + indentCounts.append(indentCount) + scopeStartLineIndexes.append(lineIndex) + linewrapStack.append(false) + case .space: + if i == 0, !formatter.options.fragment, + formatter.token(at: i + 1)?.isLinebreak != true + { + formatter.removeToken(at: i) + } + case .error("}"), .error("]"), .error(")"), .error(">"): + // Handled over-terminated fragment + if let prevToken = formatter.token(at: i - 1) { + if case let .space(string) = prevToken { + let prevButOneToken = formatter.token(at: i - 2) + if prevButOneToken == nil || prevButOneToken!.isLinebreak { + indentStack[0] = string + } + } else if prevToken.isLinebreak { + indentStack[0] = "" + } + } + return + case .keyword("#else"), .keyword("#elseif"): + var indent = indentStack[indentStack.count - 2] + if scopeStack.last == .startOfScope(":") { + indent = indentStack[indentStack.count - 4] + if formatter.options.indentCase { + indent += formatter.options.indent + } + } + let start = formatter.startOfLine(at: i) + switch formatter.options.ifdefIndent { + case .indent, .noIndent: + i += formatter.insertSpaceIfEnabled(indent, at: start) + case .outdent: + i += formatter.insertSpaceIfEnabled("", at: start) + } + case .keyword("@unknown") where scopeStack.last != .startOfScope("#if"): + var indent = indentStack[indentStack.count - 2] + if formatter.options.indentCase { + indent += formatter.options.indent + } + let start = formatter.startOfLine(at: i) + let stringIndent = stringBodyIndentStack.last! + i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) + case .keyword("in") where scopeStack.last == .startOfScope("{"): + if let startIndex = formatter.index(of: .startOfScope("{"), before: i), + formatter.index(of: .keyword("for"), in: startIndex + 1 ..< i) == nil, + let paramsIndex = formatter.index(of: .startOfScope, in: startIndex + 1 ..< i), + !formatter.tokens[startIndex + 1 ..< paramsIndex].contains(where: { + $0.isLinebreak + }), formatter.tokens[paramsIndex + 1 ..< i].contains(where: { + $0.isLinebreak + }) + { + indentStack[indentStack.count - 1] += formatter.options.indent + } + case .operator("=", .infix): + // If/switch expressions on their own line following an `=` assignment should always be indented + guard let nextKeyword = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + ["if", "switch"].contains(formatter.tokens[nextKeyword].string), + !formatter.onSameLine(i, nextKeyword) + else { fallthrough } + + let indent = (indentStack.last ?? "") + formatter.options.indent + indentStack.append(indent) + stringBodyIndentStack.append("") + indentCounts.append(1) + scopeStartLineIndexes.append(lineIndex) + linewrapStack.append(false) + scopeStack.append(.operator("=", .infix)) + scopeStartLineIndexes.append(lineIndex) + default: + // If this is the final `endOfScope` in a conditional assignment, + // we have to end the scope introduced by that assignment operator. + defer { + if token == .endOfScope("}"), let startOfScope = formatter.startOfScope(at: i) { + // Find the `=` before this start of scope, which isn't itself part of the conditional statement + var previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: startOfScope) + while let currentPreviousAssignmentIndex = previousAssignmentIndex, + formatter.isConditionalStatement(at: currentPreviousAssignmentIndex) + { + previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: currentPreviousAssignmentIndex) + } + + // Make sure the `=` actually created a new scope + if scopeStack.last == .operator("=", .infix), + // Parse the conditional branches following the `=` assignment operator + let previousAssignmentIndex = previousAssignmentIndex, + let nextTokenAfterAssignment = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: previousAssignmentIndex), + let conditionalBranches = formatter.conditionalBranches(at: nextTokenAfterAssignment), + // If this is the very end of the conditional assignment following the `=`, + // then we can end the scope. + conditionalBranches.last?.endOfBranch == i + { + popScope() + } + } + } + + // Handle end of scope + if let scope = scopeStack.last, token.isEndOfScope(scope) { + let indentCount = indentCounts.last! - 1 + popScope() + guard !token.isLinebreak, lineIndex > scopeStartLineIndexes.last ?? -1 else { + break + } + // If indentCount > 0, drop back to previous indent level + if indentCount > 0 { + indentStack.removeLast(indentCount) + stringBodyIndentStack.removeLast(indentCount) + for _ in 0 ..< indentCount { + indentStack.append(indentStack.last ?? "") + stringBodyIndentStack.append(stringBodyIndentStack.last ?? "") + } + } + + // Don't reduce indent if line doesn't start with end of scope + let start = formatter.startOfLine(at: i) + guard let firstIndex = formatter.index(of: .nonSpaceOrComment, after: start - 1) else { + break + } + if firstIndex != i { + break + } + func isInIfdef() -> Bool { + guard scopeStack.last == .startOfScope("#if") else { + return false + } + var index = i - 1 + while index > 0 { + switch formatter.tokens[index] { + case .keyword("switch"): + return false + case .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): + return true + default: + index -= 1 + } + } + return false + } + if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .outdent { + i += formatter.insertSpaceIfEnabled("", at: start) + } else { + var indent = indentStack.last ?? "" + if token.isSwitchCaseOrDefault, + formatter.options.indentCase, !isInIfdef() + { + indent += formatter.options.indent + } + let stringIndent = stringBodyIndentStack.last! + i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) + } + } else if token == .endOfScope("#endif"), indentStack.count > 1 { + var indent = indentStack[indentStack.count - 2] + if scopeStack.last == .startOfScope(":"), indentStack.count > 1 { + indent = indentStack[indentStack.count - 4] + if formatter.options.indentCase { + indent += formatter.options.indent + } + popScope() + } + switch formatter.options.ifdefIndent { + case .indent, .noIndent: + i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) + case .outdent: + i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) + } + if scopeStack.last == .startOfScope("#if") { + popScope() + } + } + } + switch token { + case .endOfScope("case"): + scopeStack.append(token) + var indent = (indentStack.last ?? "") + if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { + indent += formatter.options.indent + } else { + if formatter.options.indentCase { + indent += formatter.options.indent + } + // Align indent with previous case value + indent += formatter.spaceEquivalentToWidth(5) + } + indentStack.append(indent) + stringBodyIndentStack.append("") + indentCounts.append(1) + scopeStartLineIndexes.append(lineIndex) + linewrapStack.append(false) + fallthrough + case .endOfScope("default"), .keyword("@unknown"), + .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): + var index = formatter.startOfLine(at: i) + if index == i || index == i - 1 { + let indent: String + if case let .space(space) = formatter.tokens[index] { + indent = space + } else { + indent = "" + } + index -= 1 + while let prevToken = formatter.token(at: index - 1), prevToken.isComment, + let startIndex = formatter.index(of: .nonSpaceOrComment, before: index), + formatter.tokens[startIndex].isLinebreak + { + // Set indent for comment immediately before this line to match this line + if !formatter.isCommentedCode(at: startIndex + 1) { + formatter.insertSpaceIfEnabled(indent, at: startIndex + 1) + } + if case .endOfScope("*/") = prevToken, + var index = formatter.index(of: .startOfScope("/*"), after: startIndex) + { + while let linebreakIndex = formatter.index(of: .linebreak, after: index) { + formatter.insertSpaceIfEnabled(indent + " ", at: linebreakIndex + 1) + index = linebreakIndex + } + } + index = startIndex + } + } + case .linebreak: + // Detect linewrap + let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) + let _nextToken = nextTokenIndex.map { formatter.tokens[$0] } ?? .space("") + let linewrapped = lastNonSpaceOrLinebreakIndex > -1 && ( + !formatter.isEndOfStatement(at: lastNonSpaceOrLinebreakIndex, in: scopeStack.last) || + (nextTokenIndex.map { formatter.isTrailingClosureLabel(at: $0) } == true) || + !(nextTokenIndex == nil || [ + .endOfScope("}"), .endOfScope("]"), .endOfScope(")"), + ].contains(_nextToken) || _nextToken.isStringBody || + formatter.isStartOfStatement(at: nextTokenIndex!, in: scopeStack.last) || ( + ((_nextToken.isIdentifier && !(_nextToken == .identifier("async") && formatter.currentScope(at: nextTokenIndex!) != .startOfScope("("))) || [ + .keyword("try"), .keyword("await"), + ].contains(_nextToken)) && + formatter.last(.nonSpaceOrCommentOrLinebreak, before: nextTokenIndex!).map { + $0 != .keyword("return") && !$0.isOperator(ofType: .infix) + } ?? false) || ( + _nextToken == .delimiter(",") && [ + "<", "[", "(", "case", + ].contains(formatter.currentScope(at: nextTokenIndex!)?.string ?? "") + ) + ) + ) + + // Determine current indent + var indent = indentStack.last ?? "" + if linewrapped, lineIndex == scopeStartLineIndexes.last { + indent = indentStack.count > 1 ? indentStack[indentStack.count - 2] : "" + } + lineIndex += 1 + + func shouldIndentNextLine(at i: Int) -> Bool { + // If there is a linebreak after certain symbols, we should add + // an additional indentation to the lines at the same indention scope + // after this line. + let endOfLine = formatter.endOfLine(at: i) + switch formatter.token(at: endOfLine - 1) { + case .keyword("return")?, .operator("=", .infix)?: + let endOfNextLine = formatter.endOfLine(at: endOfLine + 1) + switch formatter.last(.nonSpaceOrCommentOrLinebreak, before: endOfNextLine) { + case .operator(_, .infix)?, .delimiter(",")?: + return false + case .endOfScope(")")?: + return !formatter.options.xcodeIndentation + default: + return formatter.lastIndex(of: .startOfScope, + in: i ..< endOfNextLine) == nil + } + default: + return false + } + } + + guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: i), + let nextToken = formatter.token(at: nextNonSpaceIndex) + else { + break + } + + // Begin wrap scope + if linewrapStack.last == true { + if !linewrapped { + indentStack.removeLast() + linewrapStack[linewrapStack.count - 1] = false + indent = indentStack.last! + } else { + let shouldIndentLeadingDotStatement: Bool + if formatter.options.xcodeIndentation { + if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), + formatter.token(at: formatter.startOfLine( + at: prevIndex, excludingIndent: true + )) == .endOfScope("}"), + formatter.index(of: .linebreak, in: prevIndex + 1 ..< i) != nil + { + shouldIndentLeadingDotStatement = false + } else { + shouldIndentLeadingDotStatement = true + } + } else { + shouldIndentLeadingDotStatement = ( + formatter.startOfConditionalStatement(at: i) != nil + && formatter.options.wrapConditions == .beforeFirst + ) + } + if shouldIndentLeadingDotStatement, + formatter.next(.nonSpace, after: i) == .operator(".", .infix), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), + case let lineStart = formatter.index(of: .linebreak, before: prevIndex + 1) ?? + formatter.startOfLine(at: prevIndex), + let startIndex = formatter.index(of: .nonSpace, after: lineStart), + formatter.isStartOfStatement(at: startIndex) || ( + (formatter.tokens[startIndex].isIdentifier || [ + .keyword("try"), .keyword("await"), + ].contains(formatter.tokens[startIndex]) || + formatter.isTrailingClosureLabel(at: startIndex)) && + formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex).map { + $0 != .keyword("return") && !$0.isOperator(ofType: .infix) + } ?? false) + { + indent += formatter.options.indent + indentStack[indentStack.count - 1] = indent + } + + // When inside conditionals, unindent after any commas (which separate conditions) + // that were indented by the block above + if !formatter.options.xcodeIndentation, + formatter.options.wrapConditions == .beforeFirst, + formatter.isConditionalStatement(at: i), + formatter.lastToken(before: i, where: { + $0.is(.nonSpaceOrCommentOrLinebreak) + }) == .delimiter(","), + let conditionBeginIndex = formatter.index(before: i, where: { + ["if", "guard", "while", "for"].contains($0.string) + }), + formatter.currentIndentForLine(at: conditionBeginIndex) + .count < indent.count + formatter.options.indent.count + { + indent = formatter.currentIndentForLine(at: conditionBeginIndex) + formatter.options.indent + indentStack[indentStack.count - 1] = indent + } + + let startOfLineIndex = formatter.startOfLine(at: i, excludingIndent: true) + let startOfLine = formatter.tokens[startOfLineIndex] + + if formatter.options.wrapTernaryOperators == .beforeOperators, + startOfLine == .operator(":", .infix) || startOfLine == .operator("?", .infix) + { + // Push a ? scope onto the stack so we can easily know + // that the next : is the closing operator of this ternary + if startOfLine.string == "?" { + // We smuggle the index of this operator in the scope stack + // so we can recover it trivially when handling the + // corresponding : operator. + scopeStack.append(.operator("?-\(startOfLineIndex)", .infix)) + } + + // Indent any operator-leading lines following a compomnent operator + // of a wrapped ternary operator expression, except for the : + // following a ? + if let nextToken = formatter.next(.nonSpace, after: i), + nextToken.isOperator(ofType: .infix), + nextToken != .operator(":", .infix) + { + indent += formatter.options.indent + indentStack[indentStack.count - 1] = indent + } + } + + // Make sure the indentation for this : operator matches + // the indentation of the previous ? operator + if formatter.options.wrapTernaryOperators == .beforeOperators, + formatter.next(.nonSpace, after: i) == .operator(":", .infix), + let scope = scopeStack.last, + scope.string.hasPrefix("?"), + scope.isOperator(ofType: .infix), + let previousOperatorIndex = scope.string.components(separatedBy: "-").last.flatMap({ Int($0) }) + { + scopeStack.removeLast() + indent = formatter.currentIndentForLine(at: previousOperatorIndex) + indentStack[indentStack.count - 1] = indent + } + } + } else if linewrapped { + func isWrappedDeclaration() -> Bool { + guard let keywordIndex = formatter + .indexOfLastSignificantKeyword(at: i, excluding: [ + "where", "throws", "rethrows", + ]), !formatter.tokens[keywordIndex ..< i].contains(.endOfScope("}")), + case let .keyword(keyword) = formatter.tokens[keywordIndex], + ["class", "actor", "struct", "enum", "protocol", "extension", + "func"].contains(keyword) + else { + return false + } + + let end = formatter.endOfLine(at: i + 1) + guard let lastToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: end + 1), + [.startOfScope("{"), .endOfScope("}")].contains(lastToken) else { return false } + + return true + } + + // Don't indent line starting with dot if previous line was just a closing brace + var lastToken = formatter.tokens[lastNonSpaceOrLinebreakIndex] + if formatter.options.allmanBraces, nextToken == .startOfScope("{"), + formatter.isStartOfClosure(at: nextNonSpaceIndex) + { + // Don't indent further + } else if formatter.token(at: nextTokenIndex ?? -1) == .operator(".", .infix) || + formatter.isLabel(at: nextTokenIndex ?? -1) + { + var lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) + let startToken = formatter.token(at: lineStart) + if let startToken = startToken, [ + .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif") + ].contains(startToken) { + if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lineStart) { + lastNonSpaceOrLinebreakIndex = index + lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) + } + } + if formatter.token(at: lineStart) == .operator(".", .infix), + [.keyword("#else"), .keyword("#elseif"), .endOfScope("#endif")].contains(startToken) + { + indent = formatter.currentIndentForLine(at: lineStart) + } else if formatter.tokens[lineStart ..< lastNonSpaceOrLinebreakIndex].allSatisfy({ + $0.isEndOfScope || $0.isSpaceOrComment + }) { + if lastToken.isEndOfScope { + indent = formatter.currentIndentForLine(at: lastNonSpaceOrLinebreakIndex) + } + if !lastToken.isEndOfScope || lastToken == .endOfScope("case") || + formatter.options.xcodeIndentation, ![ + .endOfScope("}"), .endOfScope(")") + ].contains(lastToken) + { + indent += formatter.options.indent + } + } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { + indent += formatter.linewrapIndent(at: i) + } + } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { + indent += formatter.linewrapIndent(at: i) + } + + linewrapStack[linewrapStack.count - 1] = true + indentStack.append(indent) + stringBodyIndentStack.append("") + } + // Avoid indenting commented code + guard !formatter.isCommentedCode(at: nextNonSpaceIndex) else { + break + } + // Apply indent + switch nextToken { + case .linebreak: + if formatter.options.truncateBlankLines { + formatter.insertSpaceIfEnabled("", at: i + 1) + } else if scopeStack.last?.isStringDelimiter == true, + formatter.token(at: i + 1)?.isSpace == true + { + formatter.insertSpaceIfEnabled(indent, at: i + 1) + } + case .error, .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"), + .startOfScope("#if") where formatter.options.ifdefIndent != .indent: + break + case .startOfScope("/*"), .commentBody, .endOfScope("*/"): + nextNonSpaceIndex = formatter.endOfScope(at: nextNonSpaceIndex) ?? nextNonSpaceIndex + fallthrough + case .startOfScope("//"): + nextNonSpaceIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, + after: nextNonSpaceIndex) ?? nextNonSpaceIndex + nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, + before: nextNonSpaceIndex) ?? nextNonSpaceIndex + if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), + let nextToken = formatter.next(.nonSpace, after: lineIndex), + [.startOfScope("#if"), .keyword("#else"), .keyword("#elseif")].contains(nextToken) + { + break + } + fallthrough + case .startOfScope("#if"): + if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), + let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ + .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), + ].contains(nextKeyword) + { + break + } + formatter.insertSpaceIfEnabled(indent, at: i + 1) + case .endOfScope, .keyword("@unknown"): + if let scope = scopeStack.last { + switch scope { + case .startOfScope("/*"), .startOfScope("#if"), + .keyword("#else"), .keyword("#elseif"), + .startOfScope where scope.isStringDelimiter: + formatter.insertSpaceIfEnabled(indent, at: i + 1) + default: + break + } + } + default: + var lastIndex = lastNonSpaceOrLinebreakIndex > -1 ? lastNonSpaceOrLinebreakIndex : i + while formatter.token(at: lastIndex) == .endOfScope("#endif"), + let index = formatter.index(of: .startOfScope, before: lastIndex, if: { + $0 == .startOfScope("#if") + }) + { + lastIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + before: index + ) ?? index + } + let lastToken = formatter.tokens[lastIndex] + if [.endOfScope("}"), .endOfScope(")")].contains(lastToken), + lastIndex == formatter.startOfLine(at: lastIndex, excludingIndent: true), + formatter.token(at: nextNonSpaceIndex) == .operator(".", .infix) || + (lastToken == .endOfScope("}") && formatter.isLabel(at: nextNonSpaceIndex)) + { + indent = formatter.currentIndentForLine(at: lastIndex) + } + if formatter.options.fragment, lastToken == .delimiter(",") { + break // Can't reliably indent + } + formatter.insertSpaceIfEnabled(indent, at: i + 1) + } + + if linewrapped, shouldIndentNextLine(at: i) { + indentStack[indentStack.count - 1] += formatter.options.indent + } + default: + break + } + // Track token for line wraps + if !token.isSpaceOrComment { + lastNonSpaceIndex = i + if !token.isLinebreak { + lastNonSpaceOrLinebreakIndex = i + } + } + } + + if formatter.options.indentStrings { + formatter.forEach(.startOfScope("\"\"\"")) { stringStartIndex, _ in + let baseIndent = formatter.currentIndentForLine(at: stringStartIndex) + let expectedIndent = baseIndent + formatter.options.indent + + guard let stringEndIndex = formatter.endOfScope(at: stringStartIndex), + // Preserve the default indentation if the opening """ is on a line by itself + formatter.startOfLine(at: stringStartIndex, excludingIndent: true) != stringStartIndex + else { return } + + for linebreakIndex in (stringStartIndex ..< stringEndIndex).reversed() + where formatter.tokens[linebreakIndex].isLinebreak + { + // If this line is completely blank, do nothing + // - This prevents conflicts with the trailingSpace rule + if formatter.nextToken(after: linebreakIndex)?.isLinebreak == true { + continue + } + + let indentIndex = linebreakIndex + 1 + if formatter.tokens[indentIndex].is(.space) { + formatter.replaceToken(at: indentIndex, with: .space(expectedIndent)) + } else { + formatter.insert(.space(expectedIndent), at: indentIndex) + } + } + } + } + } +} diff --git a/Sources/Rules/InitCoderUnavailable.swift b/Sources/Rules/InitCoderUnavailable.swift new file mode 100644 index 00000000..55f3fb29 --- /dev/null +++ b/Sources/Rules/InitCoderUnavailable.swift @@ -0,0 +1,60 @@ +// +// InitCoderUnavailable.swift +// SwiftFormat +// +// Created by Facundo Menzella on 8/20/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Add @available(*, unavailable) to init?(coder aDecoder: NSCoder) + static let initCoderUnavailable = FormatRule( + help: """ + Add `@available(*, unavailable)` attribute to required `init(coder:)` when + it hasn't been implemented. + """, + options: ["initcodernil"], + sharedOptions: ["linebreaks"] + ) { formatter in + let unavailableTokens = tokenize("@available(*, unavailable)") + formatter.forEach(.identifier("required")) { i, _ in + // look for required init?(coder + guard var initIndex = formatter.index(of: .keyword("init"), after: i) else { return } + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { + $0 == .operator("?", .postfix) + }) { + initIndex = nextIndex + } + + guard let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { + $0 == .startOfScope("(") + }), let coderIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { + $0 == .identifier("coder") + }), let endParenIndex = formatter.index(of: .endOfScope(")"), after: coderIndex), + let braceIndex = formatter.index(of: .startOfScope("{"), after: endParenIndex) + else { return } + + // make sure the implementation is empty or fatalError + guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: braceIndex, if: { + [.endOfScope("}"), .identifier("fatalError")].contains($0) + }) else { return } + + if formatter.options.initCoderNil, + formatter.token(at: firstTokenIndex) == .identifier("fatalError"), + let fatalParenEndOfScope = formatter.index(of: .endOfScope, after: firstTokenIndex + 1) + { + formatter.replaceTokens(in: firstTokenIndex ... fatalParenEndOfScope, with: [.identifier("nil")]) + } + + // avoid adding attribute if it's already there + if formatter.modifiersForDeclaration(at: i, contains: "@available") { return } + + let startIndex = formatter.startOfModifiers(at: i, includingAttributes: true) + formatter.insert(.space(formatter.currentIndentForLine(at: startIndex)), at: startIndex) + formatter.insertLinebreak(at: startIndex) + formatter.insert(unavailableTokens, at: startIndex) + } + } +} diff --git a/Sources/Rules/IsEmpty.swift b/Sources/Rules/IsEmpty.swift new file mode 100644 index 00000000..66e048e5 --- /dev/null +++ b/Sources/Rules/IsEmpty.swift @@ -0,0 +1,95 @@ +// +// IsEmpty.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/15/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace count == 0 with isEmpty + static let isEmpty = FormatRule( + help: "Prefer `isEmpty` over comparing `count` against zero.", + disabledByDefault: true + ) { formatter in + formatter.forEach(.identifier("count")) { i, _ in + guard let dotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i, if: { + $0.isOperator(".") + }), let opIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0.isOperator + }), let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: opIndex, if: { + $0 == .number("0", .integer) + }) else { + return + } + var isOptional = false + var index = dotIndex + var wasIdentifier = false + loop: while true { + guard let prev = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) else { + break + } + switch formatter.tokens[prev] { + case .operator("!", _), .operator(".", _): + break // Ignored + case .operator("?", _): + if formatter.tokens[prev - 1].isSpace { + break loop + } + isOptional = true + case let .operator(op, .infix): + guard ["||", "&&", ":"].contains(op) else { + return + } + break loop + case .keyword, .delimiter, .startOfScope: + break loop + case .identifier: + if wasIdentifier { + break loop + } + wasIdentifier = true + index = prev + continue + case .endOfScope: + guard !wasIdentifier, let start = formatter.index(of: .startOfScope, before: prev) else { + break loop + } + wasIdentifier = false + index = start + continue + default: + break + } + wasIdentifier = false + index = prev + } + let isEmpty: Bool + switch formatter.tokens[opIndex] { + case .operator("==", .infix): isEmpty = true + case .operator("!=", .infix), .operator(">", .infix): isEmpty = false + default: return + } + if isEmpty { + if isOptional { + formatter.replaceTokens(in: i ... endIndex, with: [ + .identifier("isEmpty"), .space(" "), .operator("==", .infix), .space(" "), .identifier("true"), + ]) + } else { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) + } + } else { + if isOptional { + formatter.replaceTokens(in: i ... endIndex, with: [ + .identifier("isEmpty"), .space(" "), .operator("!=", .infix), .space(" "), .identifier("true"), + ]) + } else { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) + formatter.insert(.operator("!", .prefix), at: index) + } + } + } + } +} diff --git a/Sources/Rules/LeadingDelimiters.swift b/Sources/Rules/LeadingDelimiters.swift new file mode 100644 index 00000000..347f6e37 --- /dev/null +++ b/Sources/Rules/LeadingDelimiters.swift @@ -0,0 +1,37 @@ +// +// LeadingDelimiters.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/11/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let leadingDelimiters = FormatRule( + help: "Move leading delimiters to the end of the previous line.", + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.delimiter) { i, _ in + guard let endOfLine = formatter.index(of: .nonSpace, before: i, if: { + $0.isLinebreak + }) else { + return + } + let nextIndex = formatter.index(of: .nonSpace, after: i) ?? (i + 1) + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) + formatter.insertLinebreak(at: nextIndex) + formatter.removeTokens(in: i + 1 ..< nextIndex) + guard case .commentBody? = formatter.last(.nonSpace, before: endOfLine) else { + formatter.removeTokens(in: endOfLine ..< i) + return + } + let startIndex = formatter.index(of: .nonSpaceOrComment, before: endOfLine) ?? -1 + formatter.removeTokens(in: endOfLine ..< i) + let comment = Array(formatter.tokens[startIndex + 1 ..< endOfLine]) + formatter.insert(comment, at: endOfLine + 1) + formatter.removeTokens(in: startIndex + 1 ..< endOfLine) + } + } +} diff --git a/Sources/Rules/LinebreakAtEndOfFile.swift b/Sources/Rules/LinebreakAtEndOfFile.swift new file mode 100644 index 00000000..c5bcc472 --- /dev/null +++ b/Sources/Rules/LinebreakAtEndOfFile.swift @@ -0,0 +1,34 @@ +// +// LinebreakAtEndOfFile.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Always end file with a linebreak, to avoid incompatibility with certain unix tools: + /// http://stackoverflow.com/questions/2287967/why-is-it-recommended-to-have-empty-line-in-the-end-of-file + static let linebreakAtEndOfFile = FormatRule( + help: "Add empty blank line at end of file.", + sharedOptions: ["linebreaks"] + ) { formatter in + guard !formatter.options.fragment else { return } + var wasLinebreak = true + formatter.forEachToken(onlyWhereEnabled: false) { _, token in + switch token { + case .linebreak: + wasLinebreak = true + case .space: + break + default: + wasLinebreak = false + } + } + if formatter.isEnabled, !wasLinebreak { + formatter.insertLinebreak(at: formatter.tokens.count) + } + } +} diff --git a/Sources/Rules/Linebreaks.swift b/Sources/Rules/Linebreaks.swift new file mode 100644 index 00000000..7caad332 --- /dev/null +++ b/Sources/Rules/Linebreaks.swift @@ -0,0 +1,21 @@ +// +// Linebreaks.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/25/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Standardise linebreak characters as whatever is specified in the options (\n by default) + static let linebreaks = FormatRule( + help: "Use specified linebreak character for all linebreaks (CR, LF or CRLF).", + options: ["linebreaks"] + ) { formatter in + formatter.forEach(.linebreak) { i, _ in + formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) + } + } +} diff --git a/Sources/Rules/MarkTypes.swift b/Sources/Rules/MarkTypes.swift new file mode 100644 index 00000000..8ed012ab --- /dev/null +++ b/Sources/Rules/MarkTypes.swift @@ -0,0 +1,254 @@ +// +// MarkTypes.swift +// SwiftFormat +// +// Created by Cal Stephens on 9/27/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let markTypes = FormatRule( + help: "Add a MARK comment before top-level types and extensions.", + runOnceOnly: true, + disabledByDefault: true, + options: ["marktypes", "typemark", "markextensions", "extensionmark", "groupedextension"], + sharedOptions: ["lineaftermarks"] + ) { formatter in + var declarations = formatter.parseDeclarations() + + // Do nothing if there is only one top-level declaration in the file (excluding imports) + let declarationsWithoutImports = declarations.filter { $0.keyword != "import" } + guard declarationsWithoutImports.count > 1 else { + return + } + + for (index, declaration) in declarations.enumerated() { + guard case let .type(kind, open, body, close, _) = declaration else { continue } + + guard var typeName = declaration.name else { + continue + } + + let markMode: MarkMode + let commentTemplate: String + let isGroupedExtension: Bool + switch declaration.keyword { + case "extension": + // TODO: this should be stored in declaration at parse time + markMode = formatter.options.markExtensions + + // We provide separate mark comment customization points for + // extensions that are "grouped" with (e.g. following) their extending type, + // vs extensions that are completely separate. + // + // struct Foo { } + // extension Foo { } // This extension is "grouped" with its extending type + // extension String { } // This extension is standalone (not grouped with any type) + // + let isGroupedWithExtendingType: Bool + if let indexOfExtendingType = declarations[.. [Token] in + var openingFormatter = Formatter(openingTokens) + + guard let keywordIndex = openingFormatter.index(after: -1, where: { + $0.string == declaration.keyword + }) else { return openingTokens } + + // If this declaration is extension, check if it has any conformances + var conformanceNames: String? + if declaration.keyword == "extension", + var conformanceSearchIndex = openingFormatter.index(of: .delimiter(":"), after: keywordIndex) + { + var conformances = [String]() + + let endOfConformances = openingFormatter.index(of: .keyword("where"), after: keywordIndex) + ?? openingFormatter.index(of: .startOfScope("{"), after: keywordIndex) + ?? openingFormatter.tokens.count + + while let token = openingFormatter.token(at: conformanceSearchIndex), + conformanceSearchIndex < endOfConformances + { + if token.isIdentifier { + let (fullyQualifiedName, next) = openingFormatter.fullyQualifiedName(startingAt: conformanceSearchIndex) + conformances.append(fullyQualifiedName) + conformanceSearchIndex = next + } + + conformanceSearchIndex += 1 + } + + if !conformances.isEmpty { + conformanceNames = conformances.joined(separator: ", ") + } + } + + // Build the types expected mark comment by replacing `%t`s with the type name + // and `%c`s with the list of conformances added in the extension (if applicable) + var markForType: String? + + if !commentTemplate.contains("%c") { + markForType = commentTemplate.replacingOccurrences(of: "%t", with: typeName) + } else if commentTemplate.contains("%c"), let conformanceNames = conformanceNames { + markForType = commentTemplate + .replacingOccurrences(of: "%t", with: typeName) + .replacingOccurrences(of: "%c", with: conformanceNames) + } + + // If this is an extension without any conformances, but contains exactly + // one body declaration (a type), we can mark the extension with the nested type's name + // (e.g. `// MARK: Foo.Bar`). + if declaration.keyword == "extension", + conformanceNames == nil + { + // Find all of the nested extensions, so we can form the fully qualified + // name of the inner-most type (e.g. `Foo.Bar.Baaz.Quux`). + var extensions = [declaration] + + while let innerExtension = extensions.last, + let extensionBody = innerExtension.body, + extensionBody.count == 1, + extensionBody[0].keyword == "extension" + { + extensions.append(extensionBody[0]) + } + + let innermostExtension = extensions.last! + let extensionNames = extensions.compactMap { $0.name }.joined(separator: ".") + + if let extensionBody = innermostExtension.body, + extensionBody.count == 1, + let nestedType = extensionBody.first, + nestedType.definesType, + let nestedTypeName = nestedType.name + { + let fullyQualifiedName = "\(extensionNames).\(nestedTypeName)" + + if isGroupedExtension { + markForType = "// \(formatter.options.groupedExtensionMarkComment)" + .replacingOccurrences(of: "%c", with: fullyQualifiedName) + } else { + markForType = "// \(formatter.options.typeMarkComment)" + .replacingOccurrences(of: "%t", with: fullyQualifiedName) + } + } + } + + guard let expectedComment = markForType else { + return openingFormatter.tokens + } + + // Remove any lines that have the same prefix as the comment template + // - We can't really do exact matches here like we do for `organizeDeclaration` + // category separators, because there's a much wider variety of options + // that a user could use for the type name (orphaned renames, etc.) + var commentPrefixes = Set(["// MARK: ", "// MARK: - "]) + + if let typeNameSymbolIndex = commentTemplate.firstIndex(of: "%") { + commentPrefixes.insert(String(commentTemplate.prefix(upTo: typeNameSymbolIndex))) + } + + openingFormatter.forEach(.startOfScope("//")) { index, _ in + let startOfLine = openingFormatter.startOfLine(at: index) + let endOfLine = openingFormatter.endOfLine(at: index) + + let commentLine = sourceCode(for: Array(openingFormatter.tokens[index ... endOfLine])) + + for commentPrefix in commentPrefixes { + if commentLine.lowercased().hasPrefix(commentPrefix.lowercased()) { + // If we found a line that matched the comment prefix, + // remove it and any linebreak immediately after it. + if openingFormatter.token(at: endOfLine + 1)?.isLinebreak == true { + openingFormatter.removeToken(at: endOfLine + 1) + } + + openingFormatter.removeTokens(in: startOfLine ... endOfLine) + break + } + } + } + + // When inserting a mark before the first declaration, + // we should make sure we place it _after_ the file header. + var markInsertIndex = 0 + if index == 0 { + // Search for the end of the file header, which ends when we hit a + // blank line or any non-space/comment/lintbreak + var endOfFileHeader = 0 + + while openingFormatter.token(at: endOfFileHeader)?.isSpaceOrCommentOrLinebreak == true { + endOfFileHeader += 1 + + if openingFormatter.token(at: endOfFileHeader)?.isLinebreak == true, + openingFormatter.next(.nonSpace, after: endOfFileHeader)?.isLinebreak == true + { + markInsertIndex = endOfFileHeader + 2 + break + } + } + } + + // Insert the expected comment at the start of the declaration + let endMarkDeclaration = formatter.options.lineAfterMarks ? "\n\n" : "\n" + openingFormatter.insert(tokenize("\(expectedComment)\(endMarkDeclaration)"), at: markInsertIndex) + + // If the previous declaration doesn't end in a blank line, + // add an additional linebreak to balance the mark. + if index != 0 { + declarations[index - 1] = formatter.mapClosingTokens(in: declarations[index - 1]) { + formatter.endingWithBlankLine($0) + } + } + + return openingFormatter.tokens + } + } + + let updatedTokens = declarations.flatMap { $0.tokens } + formatter.replaceTokens(in: 0 ..< formatter.tokens.count, with: updatedTokens) + } +} diff --git a/Sources/Rules/ModifierOrder.swift b/Sources/Rules/ModifierOrder.swift new file mode 100644 index 00000000..5b7e0ea7 --- /dev/null +++ b/Sources/Rules/ModifierOrder.swift @@ -0,0 +1,74 @@ +// +// ModifierOrder.swift +// SwiftFormat +// +// Created by Nick Lockwood on 7/28/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Standardise the order of property modifiers + static let modifierOrder = FormatRule( + help: "Use consistent ordering for member modifiers.", + options: ["modifierorder"] + ) { formatter in + formatter.forEach(.keyword) { i, token in + switch token.string { + case "let", "func", "var", "class", "actor", "extension", "init", "enum", + "struct", "typealias", "subscript", "associatedtype", "protocol": + break + default: + return + } + var modifiers = [String: [Token]]() + var lastModifier: (name: String, tokens: [Token])? + func pushModifier() { + lastModifier.map { modifiers[$0.name] = $0.tokens } + } + var lastIndex = i + var previousIndex = lastIndex + loop: while let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lastIndex) { + switch formatter.tokens[index] { + case .operator(_, .prefix), .operator(_, .infix), .keyword("case"): + // Last modifier was invalid + lastModifier = nil + lastIndex = previousIndex + break loop + case let token where token.isModifierKeyword: + pushModifier() + lastModifier = (token.string, [Token](formatter.tokens[index ..< lastIndex])) + previousIndex = lastIndex + lastIndex = index + case .endOfScope(")"): + if case let .identifier(param)? = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), + let openParenIndex = formatter.index(of: .startOfScope("("), before: index), + let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: openParenIndex), + let token = formatter.token(at: index), token.isModifierKeyword + { + pushModifier() + let modifier = token.string + (param == "set" ? "(set)" : "") + lastModifier = (modifier, [Token](formatter.tokens[index ..< lastIndex])) + previousIndex = lastIndex + lastIndex = index + } else { + break loop + } + default: + // Not a modifier + break loop + } + } + pushModifier() + guard !modifiers.isEmpty else { return } + var sortedModifiers = [Token]() + for modifier in formatter.modifierOrder { + if let tokens = modifiers[modifier] { + sortedModifiers += tokens + } + } + formatter.replaceTokens(in: lastIndex ..< i, with: sortedModifiers) + } + } +} diff --git a/Sources/Rules/NoExplicitOwnership.swift b/Sources/Rules/NoExplicitOwnership.swift new file mode 100644 index 00000000..656a3f9c --- /dev/null +++ b/Sources/Rules/NoExplicitOwnership.swift @@ -0,0 +1,47 @@ +// +// NoExplicitOwnership.swift +// SwiftFormat +// +// Created by Cal Stephens on 8/27/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let noExplicitOwnership = FormatRule( + help: "Don't use explicit ownership modifiers (borrowing / consuming).", + disabledByDefault: true + ) { formatter in + formatter.forEachToken { keywordIndex, token in + guard [.identifier("borrowing"), .identifier("consuming")].contains(token), + let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: keywordIndex) + else { return } + + // Use of `borrowing` and `consuming` as ownership modifiers + // immediately precede a valid type, or the `func` keyword. + // You could also simply use these names as a property, + // like `let borrowing = foo` or `func myFunc(borrowing foo: Foo)`. + // As a simple heuristic to detect the difference, attempt to parse the + // following tokens as a type, and require that it doesn't start with lower-case letter. + let isValidOwnershipModifier: Bool + if formatter.tokens[nextTokenIndex] == .keyword("func") { + isValidOwnershipModifier = true + } + + else if let type = formatter.parseType(at: nextTokenIndex), + type.name.first?.isLowercase == false + { + isValidOwnershipModifier = true + } + + else { + isValidOwnershipModifier = false + } + + if isValidOwnershipModifier { + formatter.removeTokens(in: keywordIndex ..< nextTokenIndex) + } + } + } +} diff --git a/Sources/Rules/NumberFormatting.swift b/Sources/Rules/NumberFormatting.swift new file mode 100644 index 00000000..9cfefb6d --- /dev/null +++ b/Sources/Rules/NumberFormatting.swift @@ -0,0 +1,105 @@ +// +// NumberFormatting.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/17/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Standardize formatting of numeric literals + static let numberFormatting = FormatRule( + help: """ + Use consistent grouping for numeric literals. Groups will be separated by `_` + delimiters to improve readability. For each numeric type you can specify a group + size (the number of digits in each group) and a threshold (the minimum number of + digits in a number before grouping is applied). + """, + options: ["decimalgrouping", "binarygrouping", "octalgrouping", "hexgrouping", + "fractiongrouping", "exponentgrouping", "hexliteralcase", "exponentcase"] + ) { formatter in + func applyGrouping(_ grouping: Grouping, to number: inout String) { + switch grouping { + case .none, .group: + number = number.replacingOccurrences(of: "_", with: "") + case .ignore: + return + } + guard case let .group(group, threshold) = grouping, group > 0, number.count >= threshold else { + return + } + var output = Substring() + var index = number.endIndex + var count = 0 + repeat { + index = number.index(before: index) + if count > 0, count % group == 0 { + output.insert("_", at: output.startIndex) + } + count += 1 + output.insert(number[index], at: output.startIndex) + } while index != number.startIndex + number = String(output) + } + formatter.forEachToken { i, token in + guard case let .number(number, type) = token else { + return + } + let grouping: Grouping + let prefix: String, exponentSeparator: String, parts: [String] + switch type { + case .integer, .decimal: + grouping = formatter.options.decimalGrouping + prefix = "" + exponentSeparator = formatter.options.uppercaseExponent ? "E" : "e" + parts = number.components(separatedBy: CharacterSet(charactersIn: ".eE")) + case .binary: + grouping = formatter.options.binaryGrouping + prefix = "0b" + exponentSeparator = "" + parts = [String(number[prefix.endIndex...])] + case .octal: + grouping = formatter.options.octalGrouping + prefix = "0o" + exponentSeparator = "" + parts = [String(number[prefix.endIndex...])] + case .hex: + grouping = formatter.options.hexGrouping + prefix = "0x" + exponentSeparator = formatter.options.uppercaseExponent ? "P" : "p" + parts = number[prefix.endIndex...].components(separatedBy: CharacterSet(charactersIn: ".pP")).map { + formatter.options.uppercaseHex ? $0.uppercased() : $0.lowercased() + } + } + var main = parts[0], fraction = "", exponent = "" + switch parts.count { + case 2 where number.contains("."): + fraction = parts[1] + case 2: + exponent = parts[1] + case 3: + fraction = parts[1] + exponent = parts[2] + default: + break + } + applyGrouping(grouping, to: &main) + if formatter.options.fractionGrouping { + applyGrouping(grouping, to: &fraction) + } + if formatter.options.exponentGrouping { + applyGrouping(grouping, to: &exponent) + } + var result = prefix + main + if !fraction.isEmpty { + result += "." + fraction + } + if !exponent.isEmpty { + result += exponentSeparator + exponent + } + formatter.replaceToken(at: i, with: .number(result, type)) + } + } +} diff --git a/Sources/Rules/OpaqueGenericParameters.swift b/Sources/Rules/OpaqueGenericParameters.swift new file mode 100644 index 00000000..71d499d0 --- /dev/null +++ b/Sources/Rules/OpaqueGenericParameters.swift @@ -0,0 +1,296 @@ +// +// OpaqueGenericParameters.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/5/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let opaqueGenericParameters = FormatRule( + help: """ + Use opaque generic parameters (`some Protocol`) instead of generic parameters + with constraints (`T where T: Protocol`, etc) where equivalent. Also supports + primary associated types for common standard library types, so definitions like + `T where T: Collection, T.Element == Foo` are updated to `some Collection`. + """, + options: ["someany"] + ) { formatter in + formatter.forEach(.keyword) { keywordIndex, keyword in + guard // Opaque generic parameter syntax is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + // Apply this rule to any function-like declaration + [.keyword("func"), .keyword("init"), .keyword("subscript")].contains(keyword), + // Validate that this is a generic method using angle bracket syntax, + // and find the indices for all of the key tokens + let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), + let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), + let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), + let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + genericSignatureStartIndex < paramListStartIndex, + genericSignatureEndIndex < paramListStartIndex, + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), + let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) + else { return } + + var genericTypes = [Formatter.GenericType]() + + // Parse the generics in the angle brackets (e.g. ``) + formatter.parseGenericTypes( + from: genericSignatureStartIndex, + to: genericSignatureEndIndex, + into: &genericTypes + ) + + // Parse additional conformances and constraints after the `where` keyword if present + // (e.g. `where Foo: Fooable, Foo.Bar: Barable, Foo.Baaz == Baazable`) + var whereTokenIndex: Int? + if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), + whereIndex < openBraceIndex + { + whereTokenIndex = whereIndex + formatter.parseGenericTypes(from: whereIndex, to: openBraceIndex, into: &genericTypes) + } + + // Parse the return type if present + var arrowTokenIndex: Int? + var returnTypeTokens: [Token]? + if let arrowIndex = formatter.index(of: .operator("->", .infix), after: paramListEndIndex), + arrowIndex < openBraceIndex, arrowIndex < whereTokenIndex ?? openBraceIndex + { + arrowTokenIndex = arrowIndex + let returnTypeRange = (arrowIndex + 1) ..< (whereTokenIndex ?? openBraceIndex) + returnTypeTokens = Array(formatter.tokens[returnTypeRange]) + } + + // Parse thrown error type if present + var errorTypeTokens: [Token]? + if let throwsIndex = formatter.index(of: .keyword("throws"), after: paramListEndIndex), + throwsIndex < arrowTokenIndex ?? whereTokenIndex ?? openBraceIndex, + let openParenIndex = formatter.index(of: .nonSpace, after: throwsIndex, if: { + $0 == .startOfScope("(") + }), + let closeParenIndex = formatter.endOfScope(at: openParenIndex) + { + let errorTypeRange = (openParenIndex + 1) ..< closeParenIndex + errorTypeTokens = Array(formatter.tokens[errorTypeRange]) + } + + let genericParameterListRange = (genericSignatureStartIndex + 1) ..< genericSignatureEndIndex + let genericParameterListTokens = formatter.tokens[genericParameterListRange] + + let parameterListRange = (paramListStartIndex + 1) ..< paramListEndIndex + let parameterListTokens = formatter.tokens[parameterListRange] + + let bodyRange = (openBraceIndex + 1) ..< closeBraceIndex + let bodyTokens = formatter.tokens[bodyRange] + + for genericType in genericTypes { + // If the generic type doesn't occur in the generic parameter list (<...>), + // then we inherited it from the generic context and can't replace the type + // with an opaque parameter. + if !genericParameterListTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + continue + } + + // We can only remove the generic type if it appears exactly once in the parameter list. + // - If the generic type occurs _multiple_ times in the parameter list, + // it isn't eligible to be removed. For example `(T, T) where T: Foo` + // requires the two params to be the same underlying type, but + // `(some Foo, some Foo)` does not. + // - If the generic type occurs _zero_ times in the parameter list + // then removing the generic parameter would also remove any + // potentially-important constraints (for example, if the type isn't + // used in the function parameters / body and is only constrained relative + // to generic types in the parent type scope). If this generic parameter + // is truly unused and redundant then the compiler would emit an error. + let countInParameterList = parameterListTokens.filter { $0.string == genericType.name }.count + if countInParameterList != 1 { + genericType.eligibleToRemove = false + continue + } + + // If the generic type occurs in the body of the function, then it can't be removed + if bodyTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + continue + } + + // If the generic type is referenced in any attributes, then it can't be removed + let startOfModifiers = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) + let modifierTokens = formatter.tokens[startOfModifiers ..< keywordIndex] + if modifierTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + continue + } + + // If the generic type is used in a constraint of any other generic type, then the type + // can't be removed without breaking that other type + let otherGenericTypes = genericTypes.filter { $0.name != genericType.name } + let otherTypeConformances = otherGenericTypes.flatMap { $0.conformances } + for otherTypeConformance in otherTypeConformances { + let conformanceTokens = formatter.tokens[otherTypeConformance.sourceRange] + if conformanceTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + } + } + + // In some weird cases you can also have a generic constraint that references a generic + // type from the parent context with the same name. We can't change these, since it + // can cause the build to break + for conformance in genericType.conformances { + if tokenize(conformance.name).contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + } + } + + // A generic used as a return type is different from an opaque result type (SE-244). + // For example in `-> T where T: Fooable`, the generic type is caller-specified, + // but with `-> some Fooable` the generic type is specified by the function implementation. + // Because those represent different concepts, we can't convert between them, + // so have to mark the generic type as ineligible if it appears in the return type. + if let returnTypeTokens = returnTypeTokens, + returnTypeTokens.contains(where: { $0.string == genericType.name }) + { + genericType.eligibleToRemove = false + continue + } + + // https://github.com/nicklockwood/SwiftFormat/issues/1845 + if let errorTypeTokens = errorTypeTokens, + errorTypeTokens.contains(.identifier(genericType.name)) + { + genericType.eligibleToRemove = false + continue + } + + // If the method that generates the opaque parameter syntax doesn't succeed, + // then this type is ineligible (because it used a generic constraint that + // can't be represented using this syntax). + // TODO: this option probably needs to be captured earlier to support comment directives + if genericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) == nil { + genericType.eligibleToRemove = false + continue + } + + // If the generic type is used as a closure type parameter, it can't be removed or the compiler + // will emit a "'some' cannot appear in parameter position in parameter type " error + for tokenIndex in keywordIndex ... closeBraceIndex { + // Check if this is the start of a closure + if formatter.tokens[tokenIndex] == .startOfScope("("), + tokenIndex != paramListStartIndex, + let endOfScope = formatter.endOfScope(at: tokenIndex), + let tokenAfterParen = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfScope), + [.operator("->", .infix), .keyword("throws"), .identifier("async")].contains(tokenAfterParen), + // Check if the closure type parameters contains this generic type + formatter.tokens[tokenIndex ... endOfScope].contains(where: { $0.string == genericType.name }) + { + genericType.eligibleToRemove = false + } + } + + // Extract the comma-separated list of function parameters, + // so we can check conditions on the individual parameters + let parameterListTokenIndices = (paramListStartIndex + 1) ..< paramListEndIndex + + // Split the parameter list at each comma that's directly within the paren list scope + let parameters = parameterListTokenIndices + .split(whereSeparator: { index in + let token = formatter.tokens[index] + return token == .delimiter(",") + && formatter.endOfScope(at: index) == paramListEndIndex + }) + .map { parameterIndices in + parameterIndices.map { index in + formatter.tokens[index] + } + } + + for parameterTokens in parameters { + // Variadic parameters don't support opaque generic syntax, so we have to check + // if any use cases of this type in the parameter list are variadic + if parameterTokens.contains(.operator("...", .postfix)), + parameterTokens.contains(.identifier(genericType.name)) + { + genericType.eligibleToRemove = false + } + } + } + + let genericsEligibleToRemove = genericTypes.filter { $0.eligibleToRemove } + let sourceRangesToRemove = Set(genericsEligibleToRemove.flatMap { type in + [type.definitionSourceRange] + type.conformances.map { $0.sourceRange } + }) + + // We perform modifications to the function signature in reverse order + // so we don't invalidate any of the indices we've recorded. So first + // we remove components of the where clause. + if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), + whereIndex < openBraceIndex + { + let whereClauseSourceRanges = sourceRangesToRemove.filter { $0.lowerBound > whereIndex } + formatter.removeTokens(in: Array(whereClauseSourceRanges)) + + if let newOpenBraceIndex = formatter.index(of: .startOfScope("{"), after: whereIndex) { + // if where clause is completely empty, we need to remove the where token as well + if formatter.index(of: .nonSpaceOrLinebreak, after: whereIndex) == newOpenBraceIndex { + formatter.removeTokens(in: whereIndex ..< newOpenBraceIndex) + } + // remove trailing comma + else if let commaIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + before: newOpenBraceIndex, if: { $0 == .delimiter(",") } + ) { + formatter.removeToken(at: commaIndex) + if formatter.tokens[commaIndex - 1].isSpace, + formatter.tokens[commaIndex].isSpaceOrLinebreak + { + formatter.removeToken(at: commaIndex - 1) + } + } + } + } + + // Replace all of the uses of generic types that are eligible to remove + // with the corresponding opaque parameter declaration + for index in parameterListRange.reversed() { + if let matchingGenericType = genericsEligibleToRemove.first(where: { $0.name == formatter.tokens[index].string }), + var opaqueParameter = matchingGenericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) + { + // If this instance of the type is followed by a `.` or `?` then we have to wrap the new type in parens + // (e.g. changing `Foo.Type` to `some Any.Type` breaks the build, it needs to be `(some Any).Type`) + if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: index), + [.operator(".", .infix), .operator("?", .postfix)].contains(nextToken) + { + opaqueParameter.insert(.startOfScope("("), at: 0) + opaqueParameter.append(.endOfScope(")")) + } + + formatter.replaceToken(at: index, with: opaqueParameter) + } + } + + // Remove types from the generic parameter list + let genericParameterListSourceRanges = sourceRangesToRemove.filter { $0.lowerBound < genericSignatureEndIndex } + formatter.removeTokens(in: Array(genericParameterListSourceRanges)) + + // If we left a dangling comma at the end of the generic parameter list, we need to clean it up + if let newGenericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + let trailingCommaIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: newGenericSignatureEndIndex), + formatter.tokens[trailingCommaIndex] == .delimiter(",") + { + formatter.removeTokens(in: trailingCommaIndex ..< newGenericSignatureEndIndex) + } + + // If we removed all of the generic types, we also have to remove the angle brackets + if let newGenericSignatureEndIndex = formatter.index(of: .nonSpaceOrLinebreak, after: genericSignatureStartIndex), + formatter.token(at: newGenericSignatureEndIndex) == .endOfScope(">") + { + formatter.removeTokens(in: genericSignatureStartIndex ... newGenericSignatureEndIndex) + } + } + } +} diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift new file mode 100644 index 00000000..814f48ad --- /dev/null +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -0,0 +1,45 @@ +// +// OrganizeDeclarations.swift +// SwiftFormat +// +// Created by Cal Stephens on 8/16/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let organizeDeclarations = FormatRule( + help: "Organize declarations within class, struct, enum, actor, and extension bodies.", + runOnceOnly: true, + disabledByDefault: true, + orderAfter: [.extensionAccessControl, .redundantFileprivate], + options: [ + "categorymark", "markcategories", "beforemarks", + "lifecycle", "organizetypes", "structthreshold", "classthreshold", + "enumthreshold", "extensionlength", "organizationmode", + "visibilityorder", "typeorder", "visibilitymarks", "typemarks", + ], + sharedOptions: ["sortedpatterns", "lineaftermarks"] + ) { formatter in + guard !formatter.options.fragment else { return } + + formatter.mapRecursiveDeclarations { declaration in + switch declaration { + // Organize the body of type declarations + case let .type(kind, open, body, close, originalRange): + let organizedType = formatter.organizeDeclaration((kind, open, body, close)) + return .type( + kind: organizedType.kind, + open: organizedType.open, + body: organizedType.body, + close: organizedType.close, + originalRange: originalRange + ) + + case .conditionalCompilation, .declaration: + return declaration + } + } + } +} diff --git a/Sources/Rules/PreferForLoop.swift b/Sources/Rules/PreferForLoop.swift new file mode 100644 index 00000000..7fc3ff47 --- /dev/null +++ b/Sources/Rules/PreferForLoop.swift @@ -0,0 +1,291 @@ +// +// PreferForLoop.swift +// SwiftFormat +// +// Created by Cal Stephens on 8/12/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let preferForLoop = FormatRule( + help: "Convert functional `forEach` calls to for loops.", + options: ["anonymousforeach", "onelineforeach"] + ) { formatter in + formatter.forEach(.identifier("forEach")) { forEachIndex, _ in + // Make sure this is a function call preceded by a `.` + guard let functionCallDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: forEachIndex), + formatter.tokens[functionCallDotIndex] == .operator(".", .infix), + let indexAfterForEach = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: forEachIndex), + let indexBeforeFunctionCallDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: functionCallDotIndex) + else { return } + + // Parse either `{ ... }` or `({ ... })` + let forEachCallOpenParenIndex: Int? + let closureOpenBraceIndex: Int + let closureCloseBraceIndex: Int + let forEachCallCloseParenIndex: Int? + + switch formatter.tokens[indexAfterForEach] { + case .startOfScope("{"): + guard let endOfClosureScope = formatter.endOfScope(at: indexAfterForEach) else { return } + + forEachCallOpenParenIndex = nil + closureOpenBraceIndex = indexAfterForEach + closureCloseBraceIndex = endOfClosureScope + forEachCallCloseParenIndex = nil + + case .startOfScope("("): + guard let endOfFunctionCall = formatter.endOfScope(at: indexAfterForEach), + let indexAfterOpenParen = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterForEach), + formatter.tokens[indexAfterOpenParen] == .startOfScope("{"), + let endOfClosureScope = formatter.endOfScope(at: indexAfterOpenParen) + else { return } + + forEachCallOpenParenIndex = indexAfterForEach + closureOpenBraceIndex = indexAfterOpenParen + closureCloseBraceIndex = endOfClosureScope + forEachCallCloseParenIndex = endOfFunctionCall + + default: + return + } + + // Abort early for single-line loops + guard !formatter.options.preserveSingleLineForEach || formatter + .tokens[closureOpenBraceIndex ..< closureCloseBraceIndex].contains(where: { $0.isLinebreak }) + else { return } + + // Ignore closures with capture lists for now since they're rare + // in this context and add complexity + guard let firstIndexInClosureBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureOpenBraceIndex), + formatter.tokens[firstIndexInClosureBody] != .startOfScope("[") + else { return } + + // Parse the value that `forEach` is being called on + let forLoopSubjectRange: ClosedRange + var forLoopSubjectIdentifier: String? + + // Parse a functional chain backwards from the `forEach` token + var currentIndex = forEachIndex + + while let previousDotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: currentIndex), + formatter.tokens[previousDotIndex] == .operator(".", .infix), + let tokenBeforeDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: previousDotIndex) + { + guard let startOfChainComponent = formatter.startOfChainComponent(at: tokenBeforeDotIndex, forLoopSubjectIdentifier: &forLoopSubjectIdentifier) else { + // If we parse a dot we expect to parse at least one additional component in the chain. + // Otherwise we'd have a malformed chain that starts with a dot, so abort. + return + } + + currentIndex = startOfChainComponent + } + + guard currentIndex != forEachIndex else { return } + forLoopSubjectRange = currentIndex ... indexBeforeFunctionCallDot + + // If there is a `try` before the `forEach` we cannot know if the subject is async/throwing or the body, + // which makes it impossible to know if we should move it or *remove* it, so we must abort (same for await). + if let tokenIndexBeforeForLoop = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: currentIndex), + var prevToken = formatter.token(at: tokenIndexBeforeForLoop) + { + if prevToken.isUnwrapOperator { + prevToken = formatter.last(.nonSpaceOrComment, before: tokenIndexBeforeForLoop) ?? .space("") + } + if [.keyword("try"), .keyword("await")].contains(prevToken) { + return + } + } + + // If the chain includes linebreaks, don't convert it to a for loop. + // + // In this case converting something like: + // + // placeholderStrings + // .filter { $0.style == .fooBar } + // .map { $0.uppercased() } + // .forEach { print($0) } + // + // to: + // + // for placeholderString in placeholderStrings + // .filter { $0.style == .fooBar } + // .map { $0.uppercased() } { print($0) } + // + // would be a pretty obvious downgrade. + if formatter.tokens[forLoopSubjectRange].contains(where: \.isLinebreak) { + return + } + + /// The names of the argument to the `forEach` closure. + /// e.g. `["foo"]` in `forEach { foo in ... }` + /// or `["foo, bar"]` in `forEach { (foo: Foo, bar: Bar) in ... }` + let forEachValueNames: [String] + let inKeywordIndex: Int? + let isAnonymousClosure: Bool + + if let argumentList = formatter.parseClosureArgumentList(at: closureOpenBraceIndex) { + isAnonymousClosure = false + forEachValueNames = argumentList.argumentNames + inKeywordIndex = argumentList.inKeywordIndex + } else { + isAnonymousClosure = true + inKeywordIndex = nil + + if formatter.options.preserveAnonymousForEach { + return + } + + // We can't introduce an identifier that matches a keyword or already exists in + // the loop body so choose the first eligible option from a set of potential names + var eligibleValueNames = ["item", "element", "value"] + if var identifier = forLoopSubjectIdentifier?.singularized(), !identifier.isSwiftKeyword { + eligibleValueNames = [identifier] + eligibleValueNames + } + + // The chosen name shouldn't already exist in the closure body + guard let chosenValueName = eligibleValueNames.first(where: { name in + !formatter.tokens[closureOpenBraceIndex ... closureCloseBraceIndex].contains(where: { $0.string == name }) + }) else { return } + + forEachValueNames = [chosenValueName] + } + + // Validate that the closure body is eligible to be converted to a for loop + for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { + guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } + + // We can only handle anonymous closures that just use $0, since we don't have good names to + // use for other arguments like $1, $2, etc. If the closure has an anonymous argument + // other than just $0 then we have to ignore it. + if formatter.tokens[closureBodyIndex].string.hasPrefix("$"), + let intValue = Int(formatter.tokens[closureBodyIndex].string.dropFirst()), + intValue != 0 + { + return + } + + // We can convert `return`s to `continue`, but only when `return` is the last token in the scope. + // It's legal to write something like `return print("foo")` in a `forEach` as long as + // you're still returning a `Void` value. Since `continue print("foo")` isn't legal, + // we should just ignore this closure. + if formatter.tokens[closureBodyIndex] == .keyword("return"), + let tokenAfterReturnKeyword = formatter.next(.nonSpaceOrComment, after: closureBodyIndex), + !(tokenAfterReturnKeyword.isLinebreak || tokenAfterReturnKeyword == .endOfScope("}")) + { + return + } + } + + // Start updating the `forEach` call to a `for .. in .. {` loop + for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { + guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } + + // The for loop won't have any `$0` identifiers anymore, so we have to + // update those to the value at the current loop index + if isAnonymousClosure, formatter.tokens[closureBodyIndex].string == "$0" { + formatter.replaceToken(at: closureBodyIndex, with: .identifier(forEachValueNames[0])) + } + + // In a `forEach` closure, `return` continues to the next loop iteration. + // To get the same behavior in a for loop we convert `return`s to `continue`s. + if formatter.tokens[closureBodyIndex] == .keyword("return") { + formatter.replaceToken(at: closureBodyIndex, with: .keyword("continue")) + } + } + + if let forEachCallCloseParenIndex = forEachCallCloseParenIndex { + formatter.removeToken(at: forEachCallCloseParenIndex) + } + + // Construct the new for loop + var newTokens: [Token] = [ + .keyword("for"), + .space(" "), + ] + + let forEachValueNameTokens: [Token] + if forEachValueNames.count == 1 { + newTokens.append(.identifier(forEachValueNames[0])) + } else { + newTokens.append(contentsOf: tokenize("(\(forEachValueNames.joined(separator: ", ")))")) + } + + newTokens.append(contentsOf: [ + .space(" "), + .keyword("in"), + .space(" "), + ]) + + newTokens.append(contentsOf: formatter.tokens[forLoopSubjectRange]) + + newTokens.append(contentsOf: [ + .space(" "), + .startOfScope("{"), + ]) + + formatter.replaceTokens( + in: (forLoopSubjectRange.lowerBound) ... (inKeywordIndex ?? closureOpenBraceIndex), + with: newTokens + ) + } + } +} + +private extension Formatter { + // Returns the start index of the chain component ending at the given index + func startOfChainComponent(at index: Int, forLoopSubjectIdentifier: inout String?) -> Int? { + // The previous item in a dot chain can either be: + // 1. an identifier like `foo.` + // 2. a function call like `foo(...).` + // 3. a subscript like `foo[...]. + // 4. a trailing closure like `map { ... }` + // 5. Some other combination of parens / subscript like `(foo).` + // or even `foo["bar"]()()`. + // And any of these can be preceeded by one of the others + switch tokens[index] { + case let .identifier(identifierName): + // Allowlist certain dot chain elements that should be ignored. + // For example, in `foos.reversed().forEach { ... }` we want + // `forLoopSubjectIdentifier` to be `foos` rather than `reversed`. + let chainElementsToIgnore = Set([ + "reversed", "sorted", "shuffled", "enumerated", "dropFirst", "dropLast", + "map", "flatMap", "compactMap", "filter", "reduce", "lazy", + ]) + + if forLoopSubjectIdentifier == nil || chainElementsToIgnore.contains(forLoopSubjectIdentifier ?? "") { + // Since we have to pick a single identifier to represent the subject of the for loop, + // just use the last identifier in the chain + forLoopSubjectIdentifier = identifierName + } + + return index + + case .endOfScope(")"), .endOfScope("]"): + let closingParenIndex = index + guard let startOfScopeIndex = startOfScope(at: closingParenIndex), + let previousNonSpaceNonCommentIndex = self.index(of: .nonSpaceOrComment, before: startOfScopeIndex) + else { return nil } + + // When we find parens for a function call or braces for a subscript, + // continue parsing at the previous non-space non-comment token. + // - If the previous token is a newline then this isn't a function call + // and we'd stop parsing. `foo ()` is a function call but `foo\n()` isn't. + return startOfChainComponent(at: previousNonSpaceNonCommentIndex, forLoopSubjectIdentifier: &forLoopSubjectIdentifier) ?? startOfScopeIndex + + case .endOfScope("}"): + // Stop parsing if we reach a trailing closure. + // Converting this to a for loop would result in unusual looking syntax like + // `for string in strings.map { $0.uppercased() } { print(string) }` + // which causes a warning to be emitted: "trailing closure in this context is + // confusable with the body of the statement; pass as a parenthesized argument + // to silence this warning". + return nil + + default: + return nil + } + } +} diff --git a/Sources/Rules/PreferKeyPath.swift b/Sources/Rules/PreferKeyPath.swift new file mode 100644 index 00000000..46cf6248 --- /dev/null +++ b/Sources/Rules/PreferKeyPath.swift @@ -0,0 +1,79 @@ +// +// PreferKeyPath.swift +// SwiftFormat +// +// Created by Nick Lockwood on 7/29/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let preferKeyPath = FormatRule( + help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax." + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard formatter.options.swiftVersion >= "5.2", + var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) + else { + return + } + var prevToken = formatter.tokens[prevIndex] + var label: String? + if prevToken == .delimiter(":"), + let labelIndex = formatter.index(of: .nonSpace, before: prevIndex), + case let .identifier(name) = formatter.tokens[labelIndex], + let prevIndex2 = formatter.index(of: .nonSpaceOrLinebreak, before: labelIndex) + { + label = name + prevToken = formatter.tokens[prevIndex2] + prevIndex = prevIndex2 + } + let parenthesized = prevToken == .startOfScope("(") + if parenthesized { + prevToken = formatter.last(.nonSpaceOrLinebreak, before: prevIndex) ?? prevToken + } + guard case let .identifier(name) = prevToken, + ["map", "flatMap", "compactMap", "allSatisfy", "filter", "contains"].contains(name), + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .identifier("$0") + }), + let endIndex = formatter.endOfScope(at: i), + let lastIndex = formatter.index(of: .nonSpaceOrLinebreak, before: endIndex) + else { + return + } + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), + formatter.isLabel(at: nextIndex) + { + return + } + if name == "contains" { + if label != "where" { + return + } + } else if label != nil { + return + } + var replacementTokens: [Token] + if nextIndex == lastIndex { + // TODO: add this when https://bugs.swift.org/browse/SR-12897 is fixed + // replacementTokens = tokenize("\\.self") + return + } else { + let tokens = formatter.tokens[nextIndex + 1 ... lastIndex] + guard tokens.allSatisfy({ $0.isSpace || $0.isIdentifier || $0.isOperator(".") }) else { + return + } + replacementTokens = [.operator("\\", .prefix)] + tokens + } + if let label = label { + replacementTokens = [.identifier(label), .delimiter(":"), .space(" ")] + replacementTokens + } + if !parenthesized { + replacementTokens = [.startOfScope("(")] + replacementTokens + [.endOfScope(")")] + } + formatter.replaceTokens(in: prevIndex + 1 ... endIndex, with: replacementTokens) + } + } +} diff --git a/Sources/Rules/PropertyType.swift b/Sources/Rules/PropertyType.swift new file mode 100644 index 00000000..7966a47b --- /dev/null +++ b/Sources/Rules/PropertyType.swift @@ -0,0 +1,208 @@ +// +// PropertyType.swift +// SwiftFormat +// +// Created by Cal Stephens on 3/29/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let propertyType = FormatRule( + help: "Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`).", + disabledByDefault: true, + orderAfter: [.redundantType], + options: ["inferredtypes", "preservesymbols"], + sharedOptions: ["redundanttype"] + ) { formatter in + formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in + // Preserve all properties in conditional statements like `if let foo = Bar() { ... }` + guard !formatter.isConditionalStatement(at: equalsIndex) else { return } + + // Determine whether the type should use the inferred syntax (`let foo = Foo()`) + // of the explicit syntax (`let foo: Foo = .init()`). + let useInferredType: Bool + switch formatter.options.redundantType { + case .inferred: + useInferredType = true + + case .explicit: + useInferredType = false + + case .inferLocalsOnly: + switch formatter.declarationScope(at: equalsIndex) { + case .global, .type: + useInferredType = false + case .local: + useInferredType = true + } + } + + guard let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), + ["var", "let"].contains(formatter.tokens[introducerIndex].string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + let rhsExpressionRange = property.value?.expressionRange + else { return } + + let rhsStartIndex = rhsExpressionRange.lowerBound + + if useInferredType { + guard let type = property.type else { return } + let typeTokens = formatter.tokens[type.range] + + // Preserve the existing formatting if the LHS type is optional. + // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` + // is invalid if `.foo` is defined on `Foo` but not `Foo?`. + guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } + + // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). + // - The `extension MyProtocol where Self == MyType { ... }` syntax + // creates static members where `let foo: any MyProtocol = .myType` + // is valid, but `let foo = (any MyProtocol).myType` isn't. + guard typeTokens.first?.string != "any" else { return } + + // Preserve the existing formatting if the RHS expression has a top-level infix operator. + // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to + // `let value = ClosedRange.zero ... 10`. + if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in + token.isOperator(ofType: .infix) && token != .operator(".", .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { + return + } + + // Preserve the formatting as-is if the type is manually excluded + if formatter.options.preserveSymbols.contains(type.name) { + return + } + + // If the RHS starts with a leading dot, then we know its accessing some static member on this type. + if formatter.tokens[rhsStartIndex].isOperator(".") { + // Preserve the formatting as-is if the identifier is manually excluded + if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), + formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) + { return } + + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) + + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: rhsStartIndex) + } + + // If the RHS is an if/switch expression, check that each branch starts with a leading dot + else if formatter.options.inferredTypesInConditionalExpressions, + ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), + let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) + { + var hasInvalidConditionalBranch = false + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { + hasInvalidConditionalBranch = true + return + } + + if !formatter.tokens[firstTokenInBranch].isOperator(".") { + hasInvalidConditionalBranch = true + } + + // Preserve the formatting as-is if the identifier is manually excluded + if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), + formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) + { + hasInvalidConditionalBranch = true + } + } + + guard !hasInvalidConditionalBranch else { return } + + // Insert a copy of the type on the RHS before the dot in each branch + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } + + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) + + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: dotIndex) + } + } + + else { + return + } + + // Remove the colon and explicit type before the equals token + formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) + } + + // If using explicit types, convert properties to the format `let foo: Foo = .init()`. + else { + guard // When parsing the type, exclude lowercase identifiers so `foo` isn't parsed as a type, + // and so `Foo.init` is parsed as `Foo` instead of `Foo.init`. + let rhsType = formatter.parseType(at: rhsStartIndex, excludeLowercaseIdentifiers: true), + property.type == nil, + let indexAfterIdentifier = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: property.identifierIndex), + formatter.tokens[indexAfterIdentifier] != .delimiter(":"), + let indexAfterType = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsType.range.upperBound), + [.operator(".", .infix), .startOfScope("(")].contains(formatter.tokens[indexAfterType]), + !rhsType.name.contains(".") + else { return } + + // Preserve the existing formatting if the RHS expression has a top-level operator. + // - `let foo = Foo.foo.bar` would not be valid to convert to `let foo: Foo = .foo.bar`. + let operatorSearchIndex = formatter.tokens[indexAfterType].isStartOfScope ? (indexAfterType - 1) : indexAfterType + if let nextInfixOperatorIndex = formatter.index(after: operatorSearchIndex, where: { token in + token.isOperator(ofType: .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { + return + } + + // Preserve any types that have been manually excluded. + // Preserve any `Void` types and tuples, since they're special and don't support things like `.init` + guard !(formatter.options.preserveSymbols + ["Void"]).contains(rhsType.name), + !rhsType.name.hasPrefix("(") + else { return } + + // A type name followed by a `(` is an implicit `.init(`. Insert a `.init` + // so that the init call stays valid after we move the type to the LHS. + if formatter.tokens[indexAfterType] == .startOfScope("(") { + // Preserve the existing format if `init` is manually excluded + if formatter.options.preserveSymbols.contains("init") { + return + } + + formatter.insert([.operator(".", .prefix), .identifier("init")], at: indexAfterType) + } + + // If the type name is followed by an infix `.` operator, convert it to a prefix operator. + else if formatter.tokens[indexAfterType] == .operator(".", .infix) { + // Exclude types with dots followed by a member access. + // - For example with something like `Color.Theme.themeColor`, we don't know + // if the property is `static var themeColor: Color` or `static var themeColor: Color.Theme`. + // - This isn't a problem with something like `Color.Theme()`, which we can reasonably assume + // is an initializer + if rhsType.name.contains(".") { return } + + // Preserve the formatting as-is if the identifier is manually excluded. + // Don't convert `let foo = Foo.self` to `let foo: Foo = .self`, since `.self` returns the metatype + let symbolsToExclude = formatter.options.preserveSymbols + ["self"] + if let indexAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterType), + symbolsToExclude.contains(formatter.tokens[indexAfterDot].string) + { return } + + formatter.replaceToken(at: indexAfterType, with: .operator(".", .prefix)) + } + + // Move the type name to the LHS of the property, followed by a colon + let typeTokens = formatter.tokens[rhsType.range] + formatter.removeTokens(in: rhsType.range) + formatter.insert([.delimiter(":"), .space(" ")] + typeTokens, at: property.identifierIndex + 1) + } + } + } +} diff --git a/Sources/Rules/RedundantBackticks.swift b/Sources/Rules/RedundantBackticks.swift new file mode 100644 index 00000000..9af5a80c --- /dev/null +++ b/Sources/Rules/RedundantBackticks.swift @@ -0,0 +1,23 @@ +// +// RedundantBackticks.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/7/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant backticks around non-keywords, or in places where keywords don't need escaping + static let redundantBackticks = FormatRule( + help: "Remove redundant backticks around identifiers." + ) { formatter in + formatter.forEach(.identifier) { i, token in + guard token.string.first == "`", !formatter.backticksRequired(at: i) else { + return + } + formatter.replaceToken(at: i, with: .identifier(token.unescaped())) + } + } +} diff --git a/Sources/Rules/RedundantBreak.swift b/Sources/Rules/RedundantBreak.swift new file mode 100644 index 00000000..288134ed --- /dev/null +++ b/Sources/Rules/RedundantBreak.swift @@ -0,0 +1,31 @@ +// +// RedundantBreak.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/23/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant `break` keyword from switch cases + static let redundantBreak = FormatRule( + help: "Remove redundant `break` in switch case." + ) { formatter in + formatter.forEach(.keyword("break")) { i, _ in + guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .startOfScope(":"), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isEndOfScope == true, + var startIndex = formatter.index(of: .nonSpace, before: i), + let endIndex = formatter.index(of: .nonSpace, after: i), + formatter.currentScope(at: i) == .startOfScope(":") + else { + return + } + if !formatter.tokens[startIndex].isLinebreak || !formatter.tokens[endIndex].isLinebreak { + startIndex += 1 + } + formatter.removeTokens(in: startIndex ..< endIndex) + } + } +} diff --git a/Sources/Rules/RedundantClosure.swift b/Sources/Rules/RedundantClosure.swift new file mode 100644 index 00000000..577455af --- /dev/null +++ b/Sources/Rules/RedundantClosure.swift @@ -0,0 +1,193 @@ +// +// RedundantClosure.swift +// SwiftFormat +// +// Created by Cal Stephens on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantClosure = FormatRule( + help: """ + Removes redundant closures bodies, containing a single statement, + which are called immediately. + """, + disabledByDefault: false, + orderAfter: [.redundantReturn] + ) { formatter in + formatter.forEach(.startOfScope("{")) { closureStartIndex, _ in + var startIndex = closureStartIndex + if formatter.isStartOfClosure(at: closureStartIndex), + var closureEndIndex = formatter.endOfScope(at: closureStartIndex), + // Closures that are called immediately are redundant + // (as long as there's exactly one statement inside them) + var closureCallOpenParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureEndIndex), + var closureCallCloseParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureCallOpenParenIndex), + formatter.token(at: closureCallOpenParenIndex) == .startOfScope("("), + formatter.token(at: closureCallCloseParenIndex) == .endOfScope(")"), + // Make sure to exclude closures that are completely empty, + // because removing them could break the build. + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex) != closureEndIndex + { + /// Whether or not this closure has a single, simple expression in its body. + /// These closures can always be simplified / removed regardless of the context. + let hasSingleSimpleExpression = formatter.blockBodyHasSingleStatement( + atStartOfScope: closureStartIndex, + includingConditionalStatements: false, + includingReturnStatements: true + ) + + /// Whether or not this closure has a single if/switch expression in its body. + /// Since if/switch expressions are only valid in the `return` position or as an `=` assignment, + /// these closures can only sometimes be simplified / removed. + let hasSingleConditionalExpression = !hasSingleSimpleExpression && + formatter.blockBodyHasSingleStatement( + atStartOfScope: closureStartIndex, + includingConditionalStatements: true, + includingReturnStatements: true, + includingReturnInConditionalStatements: false + ) + + guard hasSingleSimpleExpression || hasSingleConditionalExpression else { + return + } + + // This rule also doesn't support closures with an `in` token. + // - We can't just remove this, because it could have important type information. + // For example, `let double = { () -> Double in 100 }()` and `let double = 100` have different types. + // - We could theoretically support more sophisticated checks / transforms here, + // but this seems like an edge case so we choose not to handle it. + for inIndex in closureStartIndex ... closureEndIndex + where formatter.token(at: inIndex) == .keyword("in") + { + if !formatter.indexIsWithinNestedClosure(inIndex, startOfScopeIndex: closureStartIndex) { + return + } + } + + // If the closure calls a single function, which throws or returns `Never`, + // then removing the closure will cause a compilation failure. + // - We maintain a list of known functions that return `Never`. + // We could expand this to be user-provided if necessary. + for i in closureStartIndex ... closureEndIndex { + switch formatter.tokens[i] { + case .identifier("fatalError"), .identifier("preconditionFailure"), .keyword("throw"): + if !formatter.indexIsWithinNestedClosure(i, startOfScopeIndex: closureStartIndex) { + return + } + default: + break + } + } + + // If closure is preceded by try and/or await then remove those too + if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { + $0 == .keyword("await") + }) { + startIndex = prevIndex + } + if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { + $0 == .keyword("try") + }) { + startIndex = prevIndex + } + + // Since if/switch expressions are only valid in the `return` position or as an `=` assignment, + // these closures can only sometimes be simplified / removed. + if hasSingleConditionalExpression { + // Find the `{` start of scope or `=` and verify that the entire following expression consists of just this closure. + var startOfScopeContainingClosure = formatter.startOfScope(at: startIndex) + var assignmentBeforeClosure = formatter.index(of: .operator("=", .infix), before: startIndex) + + if let assignmentBeforeClosure = assignmentBeforeClosure, formatter.isConditionalStatement(at: assignmentBeforeClosure) { + // Not valid to use conditional expression directly in condition body + return + } + + let potentialStartOfExpressionContainingClosure: Int? + switch (startOfScopeContainingClosure, assignmentBeforeClosure) { + case (nil, nil): + potentialStartOfExpressionContainingClosure = nil + case (.some(let startOfScope), nil): + guard formatter.tokens[startOfScope] == .startOfScope("{") else { return } + potentialStartOfExpressionContainingClosure = startOfScope + case (nil, let .some(assignmentBeforeClosure)): + potentialStartOfExpressionContainingClosure = assignmentBeforeClosure + case let (.some(startOfScope), .some(assignmentBeforeClosure)): + potentialStartOfExpressionContainingClosure = max(startOfScope, assignmentBeforeClosure) + } + + if let potentialStartOfExpressionContainingClosure = potentialStartOfExpressionContainingClosure { + guard var startOfExpressionIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: potentialStartOfExpressionContainingClosure) + else { return } + + // Skip over any return token that may be present + if formatter.tokens[startOfExpressionIndex] == .keyword("return"), + let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfExpressionIndex) + { + startOfExpressionIndex = nextTokenIndex + } + + // Parse the expression and require that entire expression is simply just this closure. + guard let expressionRange = formatter.parseExpressionRange(startingAt: startOfExpressionIndex), + expressionRange == startIndex ... closureCallCloseParenIndex + else { return } + } + } + + // If the closure is a property with an explicit `Void` type, + // we can't remove the closure since the build would break + // if the method is `@discardableResult` + // https://github.com/nicklockwood/SwiftFormat/issues/1236 + if let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex), + formatter.token(at: equalsIndex) == .operator("=", .infix), + let colonIndex = formatter.index(of: .delimiter(":"), before: equalsIndex), + let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), + formatter.endOfVoidType(at: nextIndex) != nil + { + return + } + + // First we remove the spaces and linebreaks between the { } and the remainder of the closure body + // - This requires a bit of bookkeeping, but makes sure we don't remove any + // whitespace characters outside of the closure itself + while formatter.token(at: closureStartIndex + 1)?.isSpaceOrLinebreak == true { + formatter.removeToken(at: closureStartIndex + 1) + + closureCallOpenParenIndex -= 1 + closureCallCloseParenIndex -= 1 + closureEndIndex -= 1 + } + + while formatter.token(at: closureEndIndex - 1)?.isSpaceOrLinebreak == true { + formatter.removeToken(at: closureEndIndex - 1) + + closureCallOpenParenIndex -= 1 + closureCallCloseParenIndex -= 1 + closureEndIndex -= 1 + } + + // remove the trailing }() tokens, working backwards to not invalidate any indices + formatter.removeToken(at: closureCallCloseParenIndex) + formatter.removeToken(at: closureCallOpenParenIndex) + formatter.removeToken(at: closureEndIndex) + + // Remove the initial return token, and any trailing space, if present. + if let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex), + formatter.token(at: returnIndex)?.string == "return" + { + while formatter.token(at: returnIndex + 1)?.isSpaceOrLinebreak == true { + formatter.removeToken(at: returnIndex + 1) + } + + formatter.removeToken(at: returnIndex) + } + + // Finally, remove then open `{` token + formatter.removeTokens(in: startIndex ... closureStartIndex) + } + } + } +} diff --git a/Sources/Rules/RedundantExtensionACL.swift b/Sources/Rules/RedundantExtensionACL.swift new file mode 100644 index 00000000..e3ae22fb --- /dev/null +++ b/Sources/Rules/RedundantExtensionACL.swift @@ -0,0 +1,35 @@ +// +// RedundantExtensionACL.swift +// SwiftFormat +// +// Created by Nick Lockwood on 2/3/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant access control level modifiers in extensions + static let redundantExtensionACL = FormatRule( + help: "Remove redundant access control modifiers." + ) { formatter in + formatter.forEach(.keyword("extension")) { i, _ in + var acl = "" + guard formatter.modifiersForDeclaration(at: i, contains: { + acl = $1 + return _FormatRules.aclModifiers.contains(acl) + }), let startIndex = formatter.index(of: .startOfScope("{"), after: i), + var endIndex = formatter.index(of: .endOfScope("}"), after: startIndex) else { + return + } + if acl == "private" { acl = "fileprivate" } + while let aclIndex = formatter.lastIndex(of: .keyword(acl), in: startIndex + 1 ..< endIndex) { + formatter.removeToken(at: aclIndex) + if formatter.token(at: aclIndex)?.isSpace == true { + formatter.removeToken(at: aclIndex) + } + endIndex = aclIndex + } + } + } +} diff --git a/Sources/Rules/RedundantFileprivate.swift b/Sources/Rules/RedundantFileprivate.swift new file mode 100644 index 00000000..ec5c2ed2 --- /dev/null +++ b/Sources/Rules/RedundantFileprivate.swift @@ -0,0 +1,203 @@ +// +// RedundantFileprivate.swift +// SwiftFormat +// +// Created by Nick Lockwood on 2/3/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace `fileprivate` with `private` where possible + static let redundantFileprivate = FormatRule( + help: "Prefer `private` over `fileprivate` where equivalent." + ) { formatter in + guard !formatter.options.fragment else { return } + + var hasUnreplacedFileprivates = false + formatter.forEach(.keyword("fileprivate")) { i, _ in + // check if definition is at file-scope + if formatter.index(of: .startOfScope, before: i) == nil { + formatter.replaceToken(at: i, with: .keyword("private")) + } else { + hasUnreplacedFileprivates = true + } + } + guard hasUnreplacedFileprivates else { + return + } + let importRanges = formatter.parseImports() + var fileJustContainsOneType: Bool? + func ifCodeInRange(_ range: CountableRange) -> Bool { + var index = range.lowerBound + while index < range.upperBound, let nextIndex = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: index ..< range.upperBound) + { + guard let importRange = importRanges.first(where: { + $0.contains(where: { $0.range.contains(nextIndex) }) + }) else { + return true + } + index = importRange.last!.range.upperBound + 1 + } + return false + } + func isTypeInitialized(_ name: String, in range: CountableRange) -> Bool { + for i in range { + switch formatter.tokens[i] { + case .identifier(name): + guard let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) else { + break + } + switch formatter.tokens[nextIndex] { + case .operator(".", .infix): + if formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("init") { + return true + } + case .startOfScope("("): + return true + case .startOfScope("{"): + if formatter.isStartOfClosure(at: nextIndex) { + return true + } + default: + break + } + case .identifier("init"): + // TODO: this will return true if *any* type is initialized using type inference. + // Is there a way to narrow this down a bit? + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator(".", .prefix) { + return true + } + default: + break + } + } + return false + } + // TODO: improve this logic to handle shadowing + func areMembers(_ names: [String], of type: String, + referencedIn range: CountableRange) -> Bool + { + var i = range.lowerBound + while i < range.upperBound { + switch formatter.tokens[i] { + case .keyword("struct"), .keyword("extension"), .keyword("enum"), .keyword("actor"), + .keyword("class") where formatter.declarationType(at: i) == "class": + guard let startIndex = formatter.index(of: .startOfScope("{"), after: i), + let endIndex = formatter.endOfScope(at: startIndex) + else { + break + } + guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + formatter.tokens[nameIndex] != .identifier(type) + else { + i = endIndex + break + } + for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] + where names.contains(name) + { + return true + } + i = endIndex + case let .identifier(name) where names.contains(name): + if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .operator(".", .infix) + }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: dotIndex) + != .identifier("self") + { + return true + } + default: + break + } + i += 1 + } + return false + } + func isInitOverridden(for type: String, in range: CountableRange) -> Bool { + for i in range { + if case .keyword("init") = formatter.tokens[i], + let scopeStart = formatter.index(of: .startOfScope("{"), after: i), + formatter.index(of: .identifier("super"), after: scopeStart) != nil, + let scopeIndex = formatter.index(of: .startOfScope("{"), before: i), + let colonIndex = formatter.index(of: .delimiter(":"), before: scopeIndex), + formatter.next( + .nonSpaceOrCommentOrLinebreak, + in: colonIndex + 1 ..< scopeIndex + ) == .identifier(type) + { + return true + } + } + return false + } + formatter.forEach(.keyword("fileprivate")) { i, _ in + // Check if definition is a member of a file-scope type + guard formatter.options.swiftVersion >= "4", + let scopeIndex = formatter.index(of: .startOfScope, before: i, if: { + $0 == .startOfScope("{") + }), let typeIndex = formatter.index(of: .keyword, before: scopeIndex, if: { + ["class", "actor", "struct", "enum", "extension"].contains($0.string) + }), let nameIndex = formatter.index(of: .identifier, in: typeIndex ..< scopeIndex), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: nameIndex)?.isOperator(".") == false, + case let .identifier(typeName) = formatter.tokens[nameIndex], + let endIndex = formatter.index(of: .endOfScope, after: scopeIndex), + formatter.currentScope(at: typeIndex) == nil + else { + return + } + // Get member type + guard let keywordIndex = formatter.index(of: .keyword, in: i + 1 ..< endIndex), + let memberType = formatter.declarationType(at: keywordIndex), + // TODO: check if member types are exposed in the interface, otherwise convert them too + ["let", "var", "func", "init"].contains(memberType) + else { + return + } + // Check that type doesn't (potentially) conform to a protocol + // TODO: use a whitelist of known protocols to make this check less blunt + guard !formatter.tokens[typeIndex ..< scopeIndex].contains(.delimiter(":")) else { + return + } + // Check for code outside of main type definition + let startIndex = formatter.startOfModifiers(at: typeIndex, includingAttributes: true) + if fileJustContainsOneType == nil { + fileJustContainsOneType = !ifCodeInRange(0 ..< startIndex) && + !ifCodeInRange(endIndex + 1 ..< formatter.tokens.count) + } + if fileJustContainsOneType == true { + formatter.replaceToken(at: i, with: .keyword("private")) + return + } + // Check if type name is initialized outside type, and if so don't + // change any fileprivate members in case we break memberwise initializer + // TODO: check if struct contains an overridden init; if so we can skip this check + if formatter.tokens[typeIndex] == .keyword("struct"), + isTypeInitialized(typeName, in: 0 ..< startIndex) || + isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count) + { + return + } + // Check if member is referenced outside type + if memberType == "init" { + // Make initializer private if it's not called anywhere + if !isTypeInitialized(typeName, in: 0 ..< startIndex), + !isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count), + !isInitOverridden(for: typeName, in: 0 ..< startIndex), + !isInitOverridden(for: typeName, in: endIndex + 1 ..< formatter.tokens.count) + { + formatter.replaceToken(at: i, with: .keyword("private")) + } + } else if let _names = formatter.namesInDeclaration(at: keywordIndex), + case let names = _names + _names.map({ "$\($0)" }), + !areMembers(names, of: typeName, referencedIn: 0 ..< startIndex), + !areMembers(names, of: typeName, referencedIn: endIndex + 1 ..< formatter.tokens.count) + { + formatter.replaceToken(at: i, with: .keyword("private")) + } + } + } +} diff --git a/Sources/Rules/RedundantGet.swift b/Sources/Rules/RedundantGet.swift new file mode 100644 index 00000000..217c14f4 --- /dev/null +++ b/Sources/Rules/RedundantGet.swift @@ -0,0 +1,34 @@ +// +// RedundantGet.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/15/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant `get {}` clause inside read-only computed property + static let redundantGet = FormatRule( + help: "Remove unneeded `get` clause inside computed properties." + ) { formatter in + formatter.forEach(.identifier("get")) { i, _ in + if formatter.isAccessorKeyword(at: i, checkKeyword: false), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .startOfScope("{") + }), let openIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .startOfScope("{") + }), + let closeIndex = formatter.index(of: .endOfScope("}"), after: openIndex), + let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closeIndex, if: { + $0 == .endOfScope("}") + }) + { + formatter.removeTokens(in: closeIndex ..< nextIndex) + formatter.removeTokens(in: prevIndex + 1 ... openIndex) + // TODO: fix-up indenting of lines in between removed braces + } + } + } +} diff --git a/Sources/Rules/RedundantInit.swift b/Sources/Rules/RedundantInit.swift new file mode 100644 index 00000000..c1552365 --- /dev/null +++ b/Sources/Rules/RedundantInit.swift @@ -0,0 +1,68 @@ +// +// RedundantInit.swift +// SwiftFormat +// +// Created by Alejandro Martínez on 6/19/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Strip redundant `.init` from type instantiations + static let redundantInit = FormatRule( + help: "Remove explicit `init` if not required.", + orderAfter: [.propertyType] + ) { formatter in + formatter.forEach(.identifier("init")) { initIndex, _ in + guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: initIndex, if: { + $0.isOperator(".") + }), let openParenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: initIndex, if: { + $0 == .startOfScope("(") + }), let closeParenIndex = formatter.index(of: .endOfScope(")"), after: openParenIndex), + formatter.last(.nonSpaceOrCommentOrLinebreak, before: closeParenIndex) != .delimiter(":"), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex), + let prevToken = formatter.token(at: prevIndex), + formatter.isValidEndOfType(at: prevIndex), + // Find and parse the type that comes before the .init call + let startOfTypeIndex = Array(0 ..< dotIndex).reversed().last(where: { typeIndex in + guard let type = formatter.parseType(at: typeIndex) else { return false } + return (formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: type.range.upperBound) == dotIndex + // Since `Foo.init` is potentially a valid type, the `.init` may be parsed as part of the type name + || type.range.upperBound == initIndex) + // If this is actually a method call like `type(of: foo).init()`, the token before the "type" + // (which in this case looks like a tuple) will be an identifier. + && !(formatter.last(.nonSpaceOrComment, before: typeIndex)?.isIdentifier ?? false) + }), + let type = formatter.parseType(at: startOfTypeIndex), + // Filter out values that start with a lowercase letter. + // This covers edge cases like `super.init()`, where the `init` is not redundant. + let firstChar = type.name.components(separatedBy: ".").last?.first, + firstChar != "$", + String(firstChar).uppercased() == String(firstChar) + else { return } + + let lineStart = formatter.startOfLine(at: prevIndex, excludingIndent: true) + if [.startOfScope("#if"), .keyword("#elseif")].contains(formatter.tokens[lineStart]) { + return + } + var j = dotIndex + while let prevIndex = formatter.index( + of: prevToken, before: j + ) ?? formatter.index( + of: .startOfScope, before: j + ) { + j = prevIndex + if prevToken == formatter.tokens[prevIndex], + let prevPrevToken = formatter.last( + .nonSpaceOrCommentOrLinebreak, before: prevIndex + ), [.keyword("let"), .keyword("var")].contains(prevPrevToken) + { + return + } + } + formatter.removeTokens(in: initIndex + 1 ..< openParenIndex) + formatter.removeTokens(in: dotIndex ... initIndex) + } + } +} diff --git a/Sources/Rules/RedundantInternal.swift b/Sources/Rules/RedundantInternal.swift new file mode 100644 index 00000000..2a72dad1 --- /dev/null +++ b/Sources/Rules/RedundantInternal.swift @@ -0,0 +1,42 @@ +// +// RedundantInternal.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantInternal = FormatRule( + help: "Remove redundant internal access control." + ) { formatter in + formatter.forEach(.keyword("internal")) { internalKeywordIndex, _ in + // Don't remove import acl + if formatter.next(.nonSpaceOrComment, after: internalKeywordIndex) == .keyword("import") { + return + } + + // If we're inside an extension, then `internal` is only redundant if the extension itself is `internal`. + if let startOfScope = formatter.startOfScope(at: internalKeywordIndex), + let typeKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScope, excluding: ["where"]), + formatter.tokens[typeKeywordIndex] == .keyword("extension"), + // In the language grammar, the ACL level always directly precedes the + // `extension` keyword if present. + let previousToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeKeywordIndex), + ["public", "package", "internal", "private", "fileprivate"].contains(previousToken.string), + previousToken.string != "internal" + { + // The extension has an explicit ACL other than `internal`, so is not internal. + // We can't remove the `internal` keyword since the declaration would change + // to the ACL of the extension. + return + } + + guard formatter.token(at: internalKeywordIndex + 1)?.isSpace == true else { return } + + formatter.removeTokens(in: internalKeywordIndex ... (internalKeywordIndex + 1)) + } + } +} diff --git a/Sources/Rules/RedundantLet.swift b/Sources/Rules/RedundantLet.swift new file mode 100644 index 00000000..e30203c4 --- /dev/null +++ b/Sources/Rules/RedundantLet.swift @@ -0,0 +1,42 @@ +// +// RedundantLet.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/14/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant let/var for unnamed variables + static let redundantLet = FormatRule( + help: "Remove redundant `let`/`var` from ignored variables." + ) { formatter in + formatter.forEach(.identifier("_")) { i, _ in + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":"), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + [.keyword("let"), .keyword("var")].contains($0) + }), + let nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, after: prevIndex) + else { + return + } + if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: prevIndex) { + switch prevToken { + case .keyword("if"), .keyword("guard"), .keyword("while"), .identifier("async"), + .keyword where prevToken.isAttribute, + .delimiter(",") where formatter.currentScope(at: i) != .startOfScope("("): + return + default: + break + } + } + // Crude check for Result Builder + if formatter.isInResultBuilder(at: i) { + return + } + formatter.removeTokens(in: prevIndex ..< nextNonSpaceIndex) + } + } +} diff --git a/Sources/Rules/RedundantLetError.swift b/Sources/Rules/RedundantLetError.swift new file mode 100644 index 00000000..15a21746 --- /dev/null +++ b/Sources/Rules/RedundantLetError.swift @@ -0,0 +1,28 @@ +// +// RedundantLetError.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/16/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant `let error` from `catch` statements + static let redundantLetError = FormatRule( + help: "Remove redundant `let error` from `catch` clause." + ) { formatter in + formatter.forEach(.keyword("catch")) { i, _ in + if let letIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .keyword("let") + }), let errorIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: letIndex, if: { + $0 == .identifier("error") + }), let scopeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: errorIndex, if: { + $0 == .startOfScope("{") + }) { + formatter.removeTokens(in: letIndex ..< scopeIndex) + } + } + } +} diff --git a/Sources/Rules/RedundantNilInit.swift b/Sources/Rules/RedundantNilInit.swift new file mode 100644 index 00000000..5088ab81 --- /dev/null +++ b/Sources/Rules/RedundantNilInit.swift @@ -0,0 +1,76 @@ +// +// RedundantNilInit.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/5/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove or insert redundant `= nil` initialization for Optional properties + static let redundantNilInit = FormatRule( + help: "Remove/insert redundant `nil` default value (Optional vars are nil by default).", + options: ["nilinit"] + ) { formatter in + func search(from index: Int, isStoredProperty: Bool) { + if let optionalIndex = formatter.index(of: .unwrapOperator, after: index) { + if formatter.index(of: .endOfStatement, in: index + 1 ..< optionalIndex) != nil { + return + } + let previousToken = formatter.tokens[optionalIndex - 1] + if !previousToken.isSpaceOrCommentOrLinebreak && previousToken != .keyword("as") { + let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: optionalIndex, if: { + $0 == .operator("=", .infix) + }) + switch formatter.options.nilInit { + case .remove: + if let equalsIndex = equalsIndex, let nilIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { + $0 == .identifier("nil") + }) { + formatter.removeTokens(in: optionalIndex + 1 ... nilIndex) + } + case .insert: + if isStoredProperty && equalsIndex == nil { + let tokens: [Token] = [.space(" "), .operator("=", .infix), .space(" "), .identifier("nil")] + formatter.insert(tokens, at: optionalIndex + 1) + } + } + } + search(from: optionalIndex, isStoredProperty: isStoredProperty) + } + } + + // Check modifiers don't include `lazy` + formatter.forEach(.keyword("var")) { i, _ in + if formatter.modifiersForDeclaration(at: i, contains: { + $1 == "lazy" || ($1 != "@objc" && $1.hasPrefix("@")) + }) || formatter.isInResultBuilder(at: i) { + return // Can't remove the init + } + // Check this isn't a Codable + if let scopeIndex = formatter.index(of: .startOfScope("{"), before: i) { + var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: scopeIndex) + loop: while let index = prevIndex { + switch formatter.tokens[index] { + case .identifier("Codable"), .identifier("Decodable"): + return // Can't safely remove the default value + case .keyword("struct") where formatter.options.swiftVersion < "5.2": + if formatter.index(of: .keyword("init"), after: scopeIndex) == nil { + return // Can't safely remove the default value + } + break loop + case .keyword: + break loop + default: + break + } + prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) + } + } + // Find the nil + search(from: i, isStoredProperty: formatter.isStoredProperty(atIntroducerIndex: i)) + } + } +} diff --git a/Sources/Rules/RedundantObjc.swift b/Sources/Rules/RedundantObjc.swift new file mode 100644 index 00000000..22929243 --- /dev/null +++ b/Sources/Rules/RedundantObjc.swift @@ -0,0 +1,87 @@ +// +// RedundantObjc.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/30/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant @objc annotation + static let redundantObjc = FormatRule( + help: "Remove redundant `@objc` annotations." + ) { formatter in + let objcAttributes = [ + "@IBOutlet", "@IBAction", "@IBSegueAction", + "@IBDesignable", "@IBInspectable", "@GKInspectable", + "@NSManaged", + ] + + formatter.forEach(.keyword("@objc")) { i, _ in + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .startOfScope("(") else { + return + } + var index = i + loop: while var nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index) { + switch formatter.tokens[nextIndex] { + case .keyword("class"), .keyword("actor"), .keyword("enum"), + // Not actually allowed currently, but: future-proofing! + .keyword("protocol"), .keyword("struct"): + return + case .keyword("private"), .keyword("fileprivate"): + if formatter.next(.nonSpaceOrComment, after: nextIndex) == .startOfScope("(") { + break + } + // Can't safely remove objc from private members + return + case let token where token.isAttribute: + if let startIndex = formatter.index(of: .startOfScope("("), after: nextIndex), + let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) + { + nextIndex = endIndex + } + case let token: + guard token.isModifierKeyword else { + break loop + } + } + index = nextIndex + } + func removeAttribute() { + formatter.removeToken(at: i) + if formatter.token(at: i)?.isSpace == true { + formatter.removeToken(at: i) + } else if formatter.token(at: i - 1)?.isSpace == true { + formatter.removeToken(at: i - 1) + } + } + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i, if: { + $0.isAttribute && objcAttributes.contains($0.string) + }) != nil || formatter.next(.nonSpaceOrCommentOrLinebreak, after: i, if: { + $0.isAttribute && objcAttributes.contains($0.string) + }) != nil { + removeAttribute() + return + } + guard let scopeStart = formatter.index(of: .startOfScope("{"), before: i), + let keywordIndex = formatter.index(of: .keyword, before: scopeStart) + else { + return + } + switch formatter.tokens[keywordIndex] { + case .keyword("class"), .keyword("actor"): + if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objcMembers") { + removeAttribute() + } + case .keyword("extension"): + if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objc") { + removeAttribute() + } + default: + break + } + } + } +} diff --git a/Sources/Rules/RedundantOptionalBinding.swift b/Sources/Rules/RedundantOptionalBinding.swift new file mode 100644 index 00000000..7a7680d1 --- /dev/null +++ b/Sources/Rules/RedundantOptionalBinding.swift @@ -0,0 +1,43 @@ +// +// RedundantOptionalBinding.swift +// SwiftFormat +// +// Created by Cal Stephens on 8/1/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantOptionalBinding = FormatRule( + help: "Remove redundant identifiers in optional binding conditions.", + // We can convert `if let foo = self.foo` to just `if let foo`, + // but only if `redundantSelf` can first remove the `self.`. + orderAfter: [.redundantSelf] + ) { formatter in + formatter.forEachToken { i, token in + // `if let foo` conditions were added in Swift 5.7 (SE-0345) + if formatter.options.swiftVersion >= "5.7", + + [.keyword("let"), .keyword("var")].contains(token), + formatter.isConditionalStatement(at: i), + + let identiferIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + let identifier = formatter.token(at: identiferIndex), + + let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: identiferIndex, if: { + $0 == .operator("=", .infix) + }), + + let nextIdentifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex, if: { + $0 == identifier + }), + + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIdentifierIndex), + [.startOfScope("{"), .delimiter(","), .keyword("else")].contains(nextToken) + { + formatter.removeTokens(in: identiferIndex + 1 ... nextIdentifierIndex) + } + } + } +} diff --git a/Sources/Rules/RedundantParens.swift b/Sources/Rules/RedundantParens.swift new file mode 100644 index 00000000..34e73e0a --- /dev/null +++ b/Sources/Rules/RedundantParens.swift @@ -0,0 +1,228 @@ +// +// RedundantParens.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/2/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant parens around the arguments for loops, if statements, closures, etc. + static let redundantParens = FormatRule( + help: "Remove redundant parentheses." + ) { formatter in + func nestedParens(in range: ClosedRange) -> ClosedRange? { + guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: range.lowerBound, if: { + $0 == .startOfScope("(") + }), let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: range.upperBound, if: { + $0 == .endOfScope(")") + }), formatter.index(of: .endOfScope(")"), after: startIndex) == endIndex else { + return nil + } + return startIndex ... endIndex + } + + // TODO: unify with conditionals logic in trailingClosures + let conditionals = Set(["in", "while", "if", "case", "switch", "where", "for", "guard"]) + + formatter.forEach(.startOfScope("(")) { i, _ in + guard var closingIndex = formatter.index(of: .endOfScope(")"), after: i), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .keyword("repeat") + else { + return + } + var innerParens = nestedParens(in: i ... closingIndex) + while let range = innerParens, nestedParens(in: range) != nil { + // TODO: this could be a lot more efficient if we kept track of the + // removed token indices instead of recalculating paren positions every time + formatter.removeParen(at: range.upperBound) + formatter.removeParen(at: range.lowerBound) + closingIndex = formatter.index(of: .endOfScope(")"), after: i)! + innerParens = nestedParens(in: i ... closingIndex) + } + var isClosure = false + let previousIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) ?? -1 + let prevToken = formatter.token(at: previousIndex) ?? .space("") + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") + switch nextToken { + case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), + .identifier("async"), .keyword("in"): + if prevToken != .keyword("throws"), + formatter.index(before: i, where: { + [.endOfScope(")"), .operator("->", .infix), .keyword("for")].contains($0) + }) == nil, + let scopeIndex = formatter.startOfScope(at: i) + { + isClosure = formatter.isStartOfClosure(at: scopeIndex) && formatter.isInClosureArguments(at: i) + } + if !isClosure, nextToken != .keyword("in") { + return // It's a closure type, function declaration or for loop + } + case .operator: + if case let .operator(inner, _)? = formatter.last(.nonSpace, before: closingIndex), + !["?", "!"].contains(inner) + { + return + } + default: + break + } + switch prevToken { + case .stringBody, .operator("?", .postfix), .operator("!", .postfix), .operator("->", .infix): + return + case .identifier: // TODO: are trailing closures allowed in other cases? + // Parens before closure + guard closingIndex == formatter.index(of: .nonSpace, after: i), + let openingIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closingIndex, if: { + $0 == .startOfScope("{") + }), + formatter.isStartOfClosure(at: openingIndex) + else { + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + case _ where isClosure: + if formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == closingIndex || + formatter.index(of: .delimiter(":"), in: i + 1 ..< closingIndex) != nil || + formatter.tokens[i + 1 ..< closingIndex].contains(.identifier("self")) + { + return + } + if let index = formatter.tokens[i + 1 ..< closingIndex].firstIndex(of: .identifier("_")), + formatter.next(.nonSpaceOrComment, after: index)?.isIdentifier == true + { + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + case let .keyword(name) where !conditionals.contains(name) && !["let", "var", "return"].contains(name): + return + case .endOfScope("}"), .endOfScope(")"), .endOfScope("]"), .endOfScope(">"): + if formatter.tokens[previousIndex + 1 ..< i].contains(where: { $0.isLinebreak }) { + fallthrough + } + return // Probably a method invocation + case .delimiter(","), .endOfScope, .keyword: + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") + guard formatter.index(of: .endOfScope("}"), before: closingIndex) == nil, + ![.endOfScope("}"), .endOfScope(">")].contains(prevToken) || + ![.startOfScope("{"), .delimiter(",")].contains(nextToken) + else { + return + } + let string = prevToken.string + if ![.startOfScope("{"), .delimiter(","), .startOfScope(":")].contains(nextToken), + !(string == "for" && nextToken == .keyword("in")), + !(string == "guard" && nextToken == .keyword("else")) + { + // TODO: this is confusing - refactor to move fallthrough to end of case + fallthrough + } + if formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< closingIndex) == nil || + formatter.index(of: .delimiter(","), in: i + 1 ..< closingIndex) != nil + { + // Might be a tuple, so we won't remove the parens + // TODO: improve the logic here so we don't misidentify function calls as tuples + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + case .operator(_, .infix): + guard let nextIndex = formatter.index(of: .nonSpaceOrComment, after: i, if: { + $0 == .startOfScope("{") + }), let lastIndex = formatter.index(of: .endOfScope("}"), after: nextIndex), + formatter.index(of: .nonSpaceOrComment, before: closingIndex) == lastIndex else { + fallthrough + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + default: + if let range = innerParens { + formatter.removeParen(at: range.upperBound) + formatter.removeParen(at: range.lowerBound) + closingIndex = formatter.index(of: .endOfScope(")"), after: i)! + innerParens = nil + } + if prevToken == .startOfScope("("), + formatter.last(.nonSpaceOrComment, before: previousIndex) == .identifier("Selector") + { + return + } + if case .operator = formatter.tokens[closingIndex - 1], + case .operator(_, .infix)? = formatter.token(at: closingIndex + 1) + { + return + } + let nextNonLinebreak = formatter.next(.nonSpaceOrComment, after: closingIndex) + if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + case .operator = formatter.tokens[index] + { + if nextToken.isOperator(".") || (index == i + 1 && + formatter.token(at: i - 1)?.isSpaceOrCommentOrLinebreak == false) + { + return + } + switch nextNonLinebreak { + case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: + return + default: + break + } + } + guard formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) != closingIndex, + formatter.index(in: i + 1 ..< closingIndex, where: { + switch $0 { + case .operator(_, .infix), .identifier("any"), .identifier("some"), .identifier("each"), + .keyword("as"), .keyword("is"), .keyword("try"), .keyword("await"): + switch prevToken { + // TODO: add option to always strip parens in this case (or only for boolean operators?) + case .operator("=", .infix) where $0 == .operator("->", .infix): + break + case .operator(_, .prefix), .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + break + } + switch nextToken { + case .operator(_, .postfix), .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + break + } + switch nextNonLinebreak { + case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: + return true + default: + return false + } + case .operator(_, .postfix): + switch prevToken { + case .operator(_, .prefix), .keyword("as"), .keyword("is"): + return true + default: + return false + } + case .delimiter(","), .delimiter(":"), .delimiter(";"), + .operator(_, .none), .startOfScope("{"): + return true + default: + return false + } + }) == nil, + formatter.index(in: i + 1 ..< closingIndex, where: { $0.isUnwrapOperator }) ?? closingIndex >= + formatter.index(of: .nonSpace, before: closingIndex) ?? closingIndex - 1 + else { + return + } + if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == .keyword("#file") { + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + } + } + } +} diff --git a/Sources/Rules/RedundantPattern.swift b/Sources/Rules/RedundantPattern.swift new file mode 100644 index 00000000..345986bc --- /dev/null +++ b/Sources/Rules/RedundantPattern.swift @@ -0,0 +1,72 @@ +// +// RedundantPattern.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/14/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant pattern in case statements + static let redundantPattern = FormatRule( + help: "Remove redundant pattern matching parameter syntax." + ) { formatter in + func redundantBindings(in range: Range) -> Bool { + var isEmpty = true + for token in formatter.tokens[range.lowerBound ..< range.upperBound] { + switch token { + case .identifier("_"): + isEmpty = false + case .space, .linebreak, .delimiter(","), .keyword("let"), .keyword("var"): + break + default: + return false + } + } + return !isEmpty + } + + formatter.forEach(.startOfScope("(")) { i, _ in + let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) + if let prevIndex = prevIndex, let prevToken = formatter.token(at: prevIndex), + [.keyword("case"), .endOfScope("case")].contains(prevToken) + { + // Not safe to remove + return + } + guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex), + [.startOfScope(":"), .operator("=", .infix)].contains(nextToken), + redundantBindings(in: i + 1 ..< endIndex) + else { + return + } + formatter.removeTokens(in: i ... endIndex) + if let prevIndex = prevIndex, formatter.tokens[prevIndex].isIdentifier, + formatter.last(.nonSpaceOrComment, before: prevIndex)?.string == "." + { + if let endOfScopeIndex = formatter.index( + before: prevIndex, + where: { tkn in tkn == .endOfScope("case") || tkn == .keyword("case") } + ), + let varOrLetIndex = formatter.index(after: endOfScopeIndex, where: { tkn in + tkn == .keyword("let") || tkn == .keyword("var") + }), + let operatorIndex = formatter.index(of: .operator, before: prevIndex), + varOrLetIndex < operatorIndex + { + formatter.removeTokens(in: varOrLetIndex ..< operatorIndex) + } + return + } + + // Was an assignment + formatter.insert(.identifier("_"), at: i) + if formatter.token(at: i - 1).map({ $0.isSpaceOrLinebreak }) != true { + formatter.insert(.space(" "), at: i) + } + } + } +} diff --git a/Sources/Rules/RedundantProperty.swift b/Sources/Rules/RedundantProperty.swift new file mode 100644 index 00000000..390560b5 --- /dev/null +++ b/Sources/Rules/RedundantProperty.swift @@ -0,0 +1,52 @@ +// +// RedundantProperty.swift +// SwiftFormat +// +// Created by Cal Stephens on 6/9/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantProperty = FormatRule( + help: "Simplifies redundant property definitions that are immediately returned.", + disabledByDefault: true, + orderAfter: [.propertyType] + ) { formatter in + formatter.forEach(.keyword) { introducerIndex, introducerToken in + // Find properties like `let identifier = value` followed by `return identifier` + guard ["let", "var"].contains(introducerToken.string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + let (assignmentIndex, expressionRange) = property.value, + let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: expressionRange.upperBound), + formatter.tokens[returnIndex] == .keyword("return"), + let returnedValueIndex = formatter.index(of: .nonSpaceOrComment, after: returnIndex), + let returnedExpression = formatter.parseExpressionRange(startingAt: returnedValueIndex, allowConditionalExpressions: true), + formatter.tokens[returnedExpression] == [.identifier(property.identifier)] + else { return } + + let returnRange = formatter.startOfLine(at: returnIndex) ... formatter.endOfLine(at: returnedExpression.upperBound) + let propertyRange = introducerIndex ... expressionRange.upperBound + + guard !propertyRange.overlaps(returnRange) else { return } + + // Remove the line with the `return identifier` statement. + formatter.removeTokens(in: returnRange) + + // If there's nothing but whitespace between the end of the expression + // and the return statement, we can remove all of it. But if there's a comment, + // we should preserve it. + let rangeBetweenExpressionAndReturn = (expressionRange.upperBound + 1) ..< (returnRange.lowerBound - 1) + if formatter.tokens[rangeBetweenExpressionAndReturn].allSatisfy(\.isSpaceOrLinebreak) { + formatter.removeTokens(in: rangeBetweenExpressionAndReturn) + } + + // Replace the `let identifier = value` with `return value` + formatter.replaceTokens( + in: introducerIndex ..< expressionRange.lowerBound, + with: [.keyword("return"), .space(" ")] + ) + } + } +} diff --git a/Sources/Rules/RedundantRawValues.swift b/Sources/Rules/RedundantRawValues.swift new file mode 100644 index 00000000..aece965b --- /dev/null +++ b/Sources/Rules/RedundantRawValues.swift @@ -0,0 +1,50 @@ +// +// RedundantRawValues.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant raw string values for case statements + static let redundantRawValues = FormatRule( + help: "Remove redundant raw string values for enum cases." + ) { formatter in + formatter.forEach(.keyword("enum")) { i, _ in + guard let nameIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, after: i, if: { $0.isIdentifier } + ), let colonIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { $0 == .delimiter(":") } + ), formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) == .identifier("String"), + let braceIndex = formatter.index(of: .startOfScope("{"), after: colonIndex) else { + return + } + var lastIndex = formatter.index(of: .keyword("case"), after: braceIndex) + while var index = lastIndex { + guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + $0.isIdentifier + }) else { break } + if let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: nameIndex, if: { + $0 == .operator("=", .infix) + }), let quoteIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { + $0 == .startOfScope("\"") + }), formatter.token(at: quoteIndex + 2) == .endOfScope("\"") { + if formatter.tokens[nameIndex].unescaped() == formatter.token(at: quoteIndex + 1)?.string { + formatter.removeTokens(in: nameIndex + 1 ... quoteIndex + 2) + index = nameIndex + } else { + index = quoteIndex + 2 + } + } else { + index = nameIndex + } + lastIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + $0 == .delimiter(",") + }) ?? formatter.index(of: .keyword("case"), after: index) + } + } + } +} diff --git a/Sources/Rules/RedundantReturn.swift b/Sources/Rules/RedundantReturn.swift new file mode 100644 index 00000000..a8fa6f8e --- /dev/null +++ b/Sources/Rules/RedundantReturn.swift @@ -0,0 +1,232 @@ +// +// RedundantReturn.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/7/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant return keyword + static let redundantReturn = FormatRule( + help: "Remove unneeded `return` keyword." + ) { formatter in + // indices of returns that are safe to remove + var returnIndices = [Int]() + + // Also handle redundant void returns in void functions, which can always be removed. + // - The following code is the original implementation of the `redundantReturn` rule + // and is partially redundant with the below code so could be simplified in the future. + formatter.forEach(.keyword("return")) { i, _ in + guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) else { + return + } + defer { + // Check return wasn't removed already + if formatter.token(at: i) == .keyword("return") { + returnIndices.append(i) + } + } + switch formatter.tokens[startIndex] { + case .keyword("in"): + break + case .startOfScope("{"): + guard var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex) else { + break + } + if formatter.options.swiftVersion < "5.1", formatter.isAccessorKeyword(at: prevIndex) { + return + } + if formatter.tokens[prevIndex] == .endOfScope(")"), + let j = formatter.index(of: .startOfScope("("), before: prevIndex) + { + prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: j) ?? j + if formatter.tokens[prevIndex] == .operator("?", .postfix) { + prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) ?? prevIndex + } + let prevToken = formatter.tokens[prevIndex] + guard prevToken.isIdentifier || prevToken == .keyword("init") else { + return + } + } + let prevToken = formatter.tokens[prevIndex] + guard ![.delimiter(":"), .startOfScope("(")].contains(prevToken), + var prevKeywordIndex = formatter.indexOfLastSignificantKeyword( + at: startIndex, excluding: ["where"] + ) + else { + break + } + switch formatter.tokens[prevKeywordIndex].string { + case "let", "var": + guard formatter.options.swiftVersion >= "5.1" || prevToken == .operator("=", .infix) || + formatter.lastIndex(of: .operator("=", .infix), in: prevKeywordIndex + 1 ..< prevIndex) != nil, + !formatter.isConditionalStatement(at: prevKeywordIndex) + else { + return + } + case "func", "throws", "rethrows", "init", "subscript": + if formatter.options.swiftVersion < "5.1", + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .endOfScope("}") + { + return + } + default: + return + } + default: + guard let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .endOfScope("}") + }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { + return + } + if !formatter.isStartOfClosure(at: startIndex), !["func", "throws", "rethrows"] + .contains(formatter.lastSignificantKeyword(at: startIndex, excluding: ["where"]) ?? "") + { + return + } + } + // Don't remove return if it's followed by more code + guard let endIndex = formatter.endOfScope(at: i), + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == endIndex + else { + return + } + if formatter.index(of: .nonSpaceOrLinebreak, after: i) == endIndex, + let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) + { + formatter.removeTokens(in: startIndex + 1 ... i) + return + } + formatter.removeToken(at: i) + if var nextIndex = formatter.index(of: .nonSpace, after: i - 1, if: { $0.isLinebreak }) { + if let i = formatter.index(of: .nonSpaceOrLinebreak, after: nextIndex) { + nextIndex = i - 1 + } + formatter.removeTokens(in: i ... nextIndex) + } else if formatter.token(at: i)?.isSpace == true { + formatter.removeToken(at: i) + } + } + + // Explicit returns are redundant in closures, functions, etc with a single statement body + formatter.forEach(.startOfScope("{")) { startOfScopeIndex, _ in + // Closures always supported implicit returns, but other types of scopes + // only support implicit return in Swift 5.1+ (SE-0255) + let isClosure = formatter.isStartOfClosure(at: startOfScopeIndex) + if formatter.options.swiftVersion < "5.1", !isClosure { + return + } + + // Make sure this is a type of scope that supports implicit returns + let lastKeyword = isClosure ? "" : formatter.lastSignificantKeyword( + at: startOfScopeIndex, + excluding: ["throws", "where"] + ) + if !isClosure, formatter.isConditionalStatement(at: startOfScopeIndex, excluding: ["where"]) || + ["do", "else", "catch"].contains(lastKeyword) + { + return + } + + // Only strip return from conditional block if conditionalAssignment rule is enabled + var stripConditionalReturn = formatter.options.enabledRules.contains("conditionalAssignment") + + // Don't strip return if type is opaque + // (https://github.com/nicklockwood/SwiftFormat/issues/1819) + if stripConditionalReturn, + lastKeyword == "func", + let arrowIndex = formatter.index(of: .operator("->", .infix), before: startOfScopeIndex), + formatter.tokens[arrowIndex ..< startOfScopeIndex].contains(.identifier("some")) + { + stripConditionalReturn = false + } + + // Make sure the body only has a single statement + guard formatter.blockBodyHasSingleStatement( + atStartOfScope: startOfScopeIndex, + includingConditionalStatements: true, + includingReturnStatements: true, + includingReturnInConditionalStatements: stripConditionalReturn + ) else { + return + } + + // Make sure we aren't in a failable `init?`, where explicit return is required unless it's the only statement + if !isClosure, let lastSignificantKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScopeIndex), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex) != .keyword("return"), + formatter.tokens[lastSignificantKeywordIndex] == .keyword("init"), + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lastSignificantKeywordIndex), + nextToken == .operator("?", .postfix) + { + return + } + + // Find all of the return keywords to remove before we remove any of them, + // so we can apply additional validation first. + var returnKeywordRangesToRemove = [Range]() + var hasReturnThatCantBeRemoved = false + + /// Finds the return keywords to remove and stores them in `returnKeywordRangesToRemove` + func removeReturn(atStartOfScope startOfScopeIndex: Int) { + // If this scope is a single-statement if or switch statement then we have to recursively + // remove the return from each branch of the if statement + let startOfBody = formatter.startOfBody(atStartOfScope: startOfScopeIndex) + + if let firstTokenInBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), + let conditionalBranches = formatter.conditionalBranches(at: firstTokenInBody) + { + for branch in conditionalBranches.reversed() { + // In Swift 5.9, there's a bug that prevents you from writing an + // if or switch expression using an `as?` on one of the branches: + // https://github.com/apple/swift/issues/68764 + // + // if condition { + // foo as? String + // } else { + // "bar" + // } + // + if formatter.conditionalBranchHasUnsupportedCastOperator( + startOfScopeIndex: branch.startOfBranch) + { + hasReturnThatCantBeRemoved = true + return + } + + removeReturn(atStartOfScope: branch.startOfBranch) + } + } + + // Otherwise this is a simple case with a single return at the start of the scope + else if let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex), + let returnIndex = formatter.index(of: .keyword("return"), after: startOfScopeIndex), + returnIndices.contains(returnIndex), + returnIndex < endOfScopeIndex, + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: returnIndex), + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: returnIndex)! < endOfScopeIndex + { + let range = returnIndex ..< nextIndex + for (i, index) in returnIndices.enumerated().reversed() { + if range.contains(index) { + returnIndices.remove(at: i) + } else if index > returnIndex { + returnIndices[i] -= range.count + } + } + returnKeywordRangesToRemove.append(range) + } + } + + removeReturn(atStartOfScope: startOfScopeIndex) + + guard !hasReturnThatCantBeRemoved else { return } + + for returnKeywordRangeToRemove in returnKeywordRangesToRemove.sorted(by: { $0.startIndex > $1.startIndex }) { + formatter.removeTokens(in: returnKeywordRangeToRemove) + } + } + } +} diff --git a/Sources/Rules/RedundantSelf.swift b/Sources/Rules/RedundantSelf.swift new file mode 100644 index 00000000..83e66e1e --- /dev/null +++ b/Sources/Rules/RedundantSelf.swift @@ -0,0 +1,21 @@ +// +// RedundantSelf.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/13/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Insert or remove redundant self keyword + static let redundantSelf = FormatRule( + help: "Insert/remove explicit `self` where applicable.", + options: ["self", "selfrequired"] + ) { formatter in + _ = formatter.options.selfRequired + _ = formatter.options.explicitSelf + formatter.addOrRemoveSelf(static: false) + } +} diff --git a/Sources/Rules/RedundantStaticSelf.swift b/Sources/Rules/RedundantStaticSelf.swift new file mode 100644 index 00000000..6e9e0063 --- /dev/null +++ b/Sources/Rules/RedundantStaticSelf.swift @@ -0,0 +1,18 @@ +// +// RedundantStaticSelf.swift +// SwiftFormat +// +// Created by Šimon Javora on 4/29/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant Self keyword + static let redundantStaticSelf = FormatRule( + help: "Remove explicit `Self` where applicable." + ) { formatter in + formatter.addOrRemoveSelf(static: true) + } +} diff --git a/Sources/Rules/RedundantType.swift b/Sources/Rules/RedundantType.swift new file mode 100644 index 00000000..13eb4bfc --- /dev/null +++ b/Sources/Rules/RedundantType.swift @@ -0,0 +1,190 @@ +// +// RedundantType.swift +// SwiftFormat +// +// Created by Facundo Menzella on 8/20/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Removes explicit type declarations from initialization declarations + static let redundantType = FormatRule( + help: "Remove redundant type from variable declarations.", + options: ["redundanttype"] + ) { formatter in + formatter.forEach(.operator("=", .infix)) { i, _ in + guard let keyword = formatter.lastSignificantKeyword(at: i), + ["var", "let"].contains(keyword) + else { + return + } + + let equalsIndex = i + guard let colonIndex = formatter.index(before: i, where: { + [.delimiter(":"), .operator("=", .infix)].contains($0) + }), formatter.tokens[colonIndex] == .delimiter(":"), + let typeEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) + else { return } + + // Compares whether or not two types are equivalent + func compare(typeStartingAfter j: Int, withTypeStartingAfter i: Int) + -> (matches: Bool, i: Int, j: Int, wasValue: Bool) + { + var i = i, j = j, wasValue = false + + while let typeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + typeIndex <= typeEndIndex, + let valueIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: j) + { + let typeToken = formatter.tokens[typeIndex] + let valueToken = formatter.tokens[valueIndex] + if !wasValue { + switch valueToken { + case _ where valueToken.isStringDelimiter, .number, + .identifier("true"), .identifier("false"): + if formatter.options.redundantType == .explicit { + // We never remove the value in this case, so exit early + return (false, i, j, wasValue) + } + wasValue = true + default: + break + } + } + guard typeToken == formatter.typeToken(forValueToken: valueToken) else { + return (false, i, j, wasValue) + } + // Avoid introducing "inferred to have type 'Void'" warning + if formatter.options.redundantType == .inferred, typeToken == .identifier("Void") || + typeToken == .endOfScope(")") && formatter.tokens[i] == .startOfScope("(") + { + return (false, i, j, wasValue) + } + i = typeIndex + j = valueIndex + if formatter.tokens[j].isStringDelimiter, let next = formatter.endOfScope(at: j) { + j = next + } + } + guard i == typeEndIndex else { + return (false, i, j, wasValue) + } + + // Check for ternary + if let endOfExpression = formatter.endOfExpression(at: j, upTo: [.operator("?", .infix)]), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfExpression) == .operator("?", .infix) + { + return (false, i, j, wasValue) + } + + return (true, i, j, wasValue) + } + + // The implementation of RedundantType uses inferred or explicit, + // potentially depending on the context. + let isInferred: Bool + let declarationKeywordIndex: Int? + switch formatter.options.redundantType { + case .inferred: + isInferred = true + declarationKeywordIndex = nil + case .explicit: + isInferred = false + declarationKeywordIndex = formatter.declarationIndexAndScope(at: equalsIndex).index + case .inferLocalsOnly: + let (index, scope) = formatter.declarationIndexAndScope(at: equalsIndex) + switch scope { + case .global, .type: + isInferred = false + declarationKeywordIndex = index + case .local: + isInferred = true + declarationKeywordIndex = nil + } + } + + // Explicit type can't be safely removed from @Model classes + // https://github.com/nicklockwood/SwiftFormat/issues/1649 + if !isInferred, + let declarationKeywordIndex = declarationKeywordIndex, + formatter.modifiersForDeclaration(at: declarationKeywordIndex, contains: "@Model") + { + return + } + + // Removes a type already processed by `compare(typeStartingAfter:withTypeStartingAfter:)` + func removeType(after indexBeforeStartOfType: Int, i: Int, j: Int, wasValue: Bool) { + if isInferred { + formatter.removeTokens(in: colonIndex ... typeEndIndex) + if formatter.tokens[colonIndex - 1].isSpace { + formatter.removeToken(at: colonIndex - 1) + } + } else if !wasValue, let valueStartIndex = formatter + .index(of: .nonSpaceOrCommentOrLinebreak, after: indexBeforeStartOfType), + !formatter.isConditionalStatement(at: i), + let endIndex = formatter.endOfExpression(at: j, upTo: []), + endIndex > j + { + let allowChains = formatter.options.swiftVersion >= "5.4" + if formatter.next(.nonSpaceOrComment, after: j) == .startOfScope("(") { + if allowChains || formatter.index( + of: .operator(".", .infix), + in: j ..< endIndex + ) == nil { + formatter.replaceTokens(in: valueStartIndex ... j, with: [ + .operator(".", .infix), .identifier("init"), + ]) + } + } else if let nextIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + after: j, + if: { $0 == .operator(".", .infix) } + ), allowChains || formatter.index( + of: .operator(".", .infix), + in: (nextIndex + 1) ..< endIndex + ) == nil { + formatter.removeTokens(in: valueStartIndex ... j) + } + } + } + + // In Swift 5.9+ (SE-0380) we need to handle if / switch expressions by checking each branch + if formatter.options.swiftVersion >= "5.9", + let tokenAfterEquals = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex), + let conditionalBranches = formatter.conditionalBranches(at: tokenAfterEquals), + formatter.allRecursiveConditionalBranches( + in: conditionalBranches, + satisfy: { branch in + compare(typeStartingAfter: branch.startOfBranch, withTypeStartingAfter: colonIndex).matches + } + ) + { + if isInferred { + formatter.removeTokens(in: colonIndex ... typeEndIndex) + if formatter.tokens[colonIndex - 1].isSpace { + formatter.removeToken(at: colonIndex - 1) + } + } else { + formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in + let (_, i, j, wasValue) = compare( + typeStartingAfter: branch.startOfBranch, + withTypeStartingAfter: colonIndex + ) + + removeType(after: branch.startOfBranch, i: i, j: j, wasValue: wasValue) + } + } + } + + // Otherwise this is just a simple assignment expression where the RHS is a single value + else { + let (matches, i, j, wasValue) = compare(typeStartingAfter: equalsIndex, withTypeStartingAfter: colonIndex) + if matches { + removeType(after: equalsIndex, i: i, j: j, wasValue: wasValue) + } + } + } + } +} diff --git a/Sources/Rules/RedundantTypedThrows.swift b/Sources/Rules/RedundantTypedThrows.swift new file mode 100644 index 00000000..f7e3da1b --- /dev/null +++ b/Sources/Rules/RedundantTypedThrows.swift @@ -0,0 +1,41 @@ +// +// RedundantTypedThrows.swift +// SwiftFormat +// +// Created by Miguel Jimenez on 6/8/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantTypedThrows = FormatRule( + help: "Converts `throws(any Error)` to `throws`, and converts `throws(Never)` to non-throwing.") + { formatter in + formatter.forEach(.keyword("throws")) { throwsIndex, _ in + guard // Typed throws was added in Swift 6.0: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md + formatter.options.swiftVersion >= "6.0", + let startOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: throwsIndex), + formatter.tokens[startOfScope] == .startOfScope("("), + let endOfScope = formatter.endOfScope(at: startOfScope) + else { return } + + let throwsTypeRange = (startOfScope + 1) ..< endOfScope + let throwsType: String = formatter.tokens[throwsTypeRange].map { $0.string }.joined() + + if throwsType == "Never" { + if formatter.tokens[endOfScope + 1].isSpace { + formatter.removeTokens(in: throwsIndex ... endOfScope + 1) + } else { + formatter.removeTokens(in: throwsIndex ... endOfScope) + } + } + + // We don't remove `(Error)` because we can't guarantee it will reference the `Swift.Error` protocol + // (it's relatively common to define a custom error like `enum Error: Swift.Error { ... }`). + if throwsType == "any Error" || throwsType == "any Swift.Error" || throwsType == "Swift.Error" { + formatter.removeTokens(in: startOfScope ... endOfScope) + } + } + } +} diff --git a/Sources/Rules/RedundantVoidReturnType.swift b/Sources/Rules/RedundantVoidReturnType.swift new file mode 100644 index 00000000..5a6abd71 --- /dev/null +++ b/Sources/Rules/RedundantVoidReturnType.swift @@ -0,0 +1,58 @@ +// +// RedundantVoidReturnType.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/3/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant void return values for function and closure declarations + static let redundantVoidReturnType = FormatRule( + help: "Remove explicit `Void` return type.", + options: ["closurevoid"] + ) { formatter in + formatter.forEach(.operator("->", .infix)) { i, _ in + guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + let endIndex = formatter.endOfVoidType(at: startIndex) + else { + return + } + + // If this is the explicit return type of a closure, it should + // always be safe to remove + if formatter.options.closureVoidReturn == .remove, + formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .keyword("in") + { + formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) + return + } + + guard let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) else { return } + + let isInProtocol = nextToken == .endOfScope("}") || (nextToken.isKeywordOrAttribute && nextToken != .keyword("in")) + + // After a `Void` we could see the start of a function's body, or if the function is inside a protocol declaration + // we can find a keyword related to other declarations or the end scope of the protocol definition. + guard nextToken == .startOfScope("{") || isInProtocol else { return } + + guard let prevIndex = formatter.index(of: .endOfScope(")"), before: i), + let parenIndex = formatter.index(of: .startOfScope("("), before: prevIndex), + let startToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: parenIndex), + startToken.isIdentifier || [.startOfScope("{"), .endOfScope("]")].contains(startToken) + else { + return + } + + let startRemoveIndex: Int + if isInProtocol, formatter.token(at: i - 1)?.isSpace == true { + startRemoveIndex = i - 1 + } else { + startRemoveIndex = i + } + formatter.removeTokens(in: startRemoveIndex ..< formatter.index(of: .nonSpace, after: endIndex)!) + } + } +} diff --git a/Sources/Rules/Semicolons.swift b/Sources/Rules/Semicolons.swift new file mode 100644 index 00000000..b8785d92 --- /dev/null +++ b/Sources/Rules/Semicolons.swift @@ -0,0 +1,52 @@ +// +// Semicolons.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/24/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove semicolons, except where doing so would change the meaning of the code + static let semicolons = FormatRule( + help: "Remove semicolons.", + options: ["semicolons"], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.delimiter(";")) { i, _ in + if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { + let prevTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) + let prevToken = prevTokenIndex.map { formatter.tokens[$0] } + if prevToken == nil || nextToken == .endOfScope("}") { + // Safe to remove + formatter.removeToken(at: i) + } else if prevToken == .keyword("return") || ( + formatter.options.swiftVersion < "3" && + // Might be a traditional for loop (not supported in Swift 3 and above) + formatter.currentScope(at: i) == .startOfScope("(") + ) { + // Not safe to remove or replace + } else if case .identifier? = prevToken, formatter.last( + .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex! + ) == .keyword("var") { + // Not safe to remove or replace + } else if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { + // Safe to remove + formatter.removeToken(at: i) + } else if !formatter.options.allowInlineSemicolons { + // Replace with a linebreak + if formatter.token(at: i + 1)?.isSpace == true { + formatter.removeToken(at: i + 1) + } + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) + formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) + } + } else { + // Safe to remove + formatter.removeToken(at: i) + } + } + } +} diff --git a/Sources/Rules/SortDeclarations.swift b/Sources/Rules/SortDeclarations.swift new file mode 100644 index 00000000..7d064367 --- /dev/null +++ b/Sources/Rules/SortDeclarations.swift @@ -0,0 +1,155 @@ +// +// SortDeclarations.swift +// SwiftFormat +// +// Created by Cal Stephens on 11/22/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let sortDeclarations = FormatRule( + help: """ + Sorts the body of declarations with // swiftformat:sort + and declarations between // swiftformat:sort:begin and + // swiftformat:sort:end comments. + """, + options: ["sortedpatterns"], + sharedOptions: ["organizetypes"] + ) { formatter in + formatter.forEachToken( + where: { + $0.isCommentBody && $0.string.contains("swiftformat:sort") + || $0.isDeclarationTypeKeyword(including: Array(Token.swiftTypeKeywords)) + } + ) { index, token in + + let rangeToSort: ClosedRange + let numberOfLeadingLinebreaks: Int + + // For `:sort:begin`, directives, we sort the declarations + // between the `:begin` and and `:end` comments + let shouldBePartiallySorted = token.string.contains("swiftformat:sort:begin") + + let identifier = formatter.next(.identifier, after: index) + let shouldBeSortedByNamePattern = formatter.options.alphabeticallySortedDeclarationPatterns.contains { + identifier?.string.contains($0) ?? false + } + let shouldBeSortedByMarkComment = token.isCommentBody && !token.string.contains(":sort:") + // For `:sort` directives and types with matching name pattern, we sort the declarations + // between the open and close brace of the following type + let shouldBeFullySorted = shouldBeSortedByNamePattern || shouldBeSortedByMarkComment + + if shouldBePartiallySorted { + guard let endCommentIndex = formatter.tokens[index...].firstIndex(where: { + $0.isComment && $0.string.contains("swiftformat:sort:end") + }), + let sortRangeStart = formatter.index(of: .nonSpaceOrComment, after: index), + let firstRangeToken = formatter.index(of: .nonLinebreak, after: sortRangeStart), + let lastRangeToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: endCommentIndex - 2) + else { return } + + rangeToSort = sortRangeStart ... lastRangeToken + numberOfLeadingLinebreaks = firstRangeToken - sortRangeStart + } else if shouldBeFullySorted { + guard let typeOpenBrace = formatter.index(of: .startOfScope("{"), after: index), + let typeCloseBrace = formatter.endOfScope(at: typeOpenBrace), + let firstTypeBodyToken = formatter.index(of: .nonLinebreak, after: typeOpenBrace), + let lastTypeBodyToken = formatter.index(of: .nonLinebreak, before: typeCloseBrace), + let declarationKeyword = formatter.lastSignificantKeyword(at: typeOpenBrace), + lastTypeBodyToken > typeOpenBrace + else { return } + + // Sorting the body of a type conflicts with the `organizeDeclarations` + // keyword if enabled for this type of declaration. In that case, + // defer to the sorting implementation in `organizeDeclarations`. + if formatter.options.enabledRules.contains(FormatRule.organizeDeclarations.name), + formatter.options.organizeTypes.contains(declarationKeyword) + { + return + } + + rangeToSort = firstTypeBodyToken ... lastTypeBodyToken + // We don't include any leading linebreaks in the range to sort, + // since `firstTypeBodyToken` is the first `nonLinebreak` in the body + numberOfLeadingLinebreaks = 0 + } else { + return + } + + var declarations = Formatter(Array(formatter.tokens[rangeToSort])) + .parseDeclarations() + .enumerated() + .sorted(by: { lhs, rhs -> Bool in + let (lhsIndex, lhsDeclaration) = lhs + let (rhsIndex, rhsDeclaration) = rhs + + // Primarily sort by name, to alphabetize + if let lhsName = lhsDeclaration.name, + let rhsName = rhsDeclaration.name, + lhsName != rhsName + { + return lhsName.localizedCompare(rhsName) == .orderedAscending + } + + // Otherwise preserve the existing order + else { + return lhsIndex < rhsIndex + } + + }) + .map { $0.element } + + // Make sure there's at least one newline between each declaration + for i in 0 ..< max(0, declarations.count - 1) { + let declaration = declarations[i] + let nextDeclaration = declarations[i + 1] + + if declaration.tokens.last?.isLinebreak == false, + nextDeclaration.tokens.first?.isLinebreak == false + { + declarations[i + 1] = formatter.mapOpeningTokens(in: nextDeclaration) { openTokens in + let openFormatter = Formatter(openTokens) + openFormatter.insertLinebreak(at: 0) + return openFormatter.tokens + } + } + } + + var sortedFormatter = Formatter(declarations.flatMap { $0.tokens }) + + // Make sure the type has the same number of leading line breaks + // as it did before sorting + if let currentLeadingLinebreakCount = sortedFormatter.tokens.firstIndex(where: { !$0.isLinebreak }) { + if numberOfLeadingLinebreaks != currentLeadingLinebreakCount { + sortedFormatter.removeTokens(in: 0 ..< currentLeadingLinebreakCount) + + for _ in 0 ..< numberOfLeadingLinebreaks { + sortedFormatter.insertLinebreak(at: 0) + } + } + + } else { + for _ in 0 ..< numberOfLeadingLinebreaks { + sortedFormatter.insertLinebreak(at: 0) + } + } + + // There are always expected to be zero trailing line breaks, + // so we remove any trailing line breaks + // (this is because `typeBodyRange` specifically ends before the first + // trailing linebreak) + while sortedFormatter.tokens.last?.isLinebreak == true { + sortedFormatter.removeLastToken() + } + + if Array(formatter.tokens[rangeToSort]) != sortedFormatter.tokens { + formatter.replaceTokens( + in: rangeToSort, + with: sortedFormatter.tokens + ) + } + } + } +} diff --git a/Sources/Rules/SortImports.swift b/Sources/Rules/SortImports.swift new file mode 100644 index 00000000..a62ece51 --- /dev/null +++ b/Sources/Rules/SortImports.swift @@ -0,0 +1,54 @@ +// +// SortImports.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/13/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Sort import statements + static let sortImports = FormatRule( + help: "Sort import statements alphabetically.", + options: ["importgrouping"], + sharedOptions: ["linebreaks"] + ) { formatter in + func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] { + if case .alpha = formatter.options.importGrouping { + return ranges.sorted(by: <) + } else if case .length = formatter.options.importGrouping { + return ranges.sorted { $0.module.count < $1.module.count } + } + // Group @testable imports at the top or bottom + // TODO: need more general solution for handling other import attributes + return ranges.sorted { + // If both have a @testable keyword, or neither has one, just sort alphabetically + guard $0.isTestable != $1.isTestable else { + return $0 < $1 + } + return formatter.options.importGrouping == .testableFirst ? $0.isTestable : $1.isTestable + } + } + + for var importRanges in formatter.parseImports().reversed() { + guard importRanges.count > 1 else { continue } + let range: Range = importRanges.first!.range.lowerBound ..< importRanges.last!.range.upperBound + let sortedRanges = sortRanges(importRanges) + var insertedLinebreak = false + var sortedTokens = sortedRanges.flatMap { inputRange -> [Token] in + var tokens = Array(formatter.tokens[inputRange.range]) + if tokens.first?.isLinebreak == false { + insertedLinebreak = true + tokens.insert(formatter.linebreakToken(for: tokens.startIndex), at: tokens.startIndex) + } + return tokens + } + if insertedLinebreak { + sortedTokens.removeFirst() + } + formatter.replaceTokens(in: range, with: sortedTokens) + } + } +} diff --git a/Sources/Rules/SortSwitchCases.swift b/Sources/Rules/SortSwitchCases.swift new file mode 100644 index 00000000..d9ce65b6 --- /dev/null +++ b/Sources/Rules/SortSwitchCases.swift @@ -0,0 +1,92 @@ +// +// SortSwitchCases.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/13/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Sorts switch cases alphabetically + static let sortSwitchCases = FormatRule( + help: "Sort switch cases alphabetically.", + disabledByDefault: true + ) { formatter in + formatter.parseSwitchCaseRanges() + .reversed() // don't mess with indexes + .forEach { switchCaseRanges in + guard switchCaseRanges.count > 1, // nothing to sort + let firstCaseIndex = switchCaseRanges.first?.beforeDelimiterRange.lowerBound else { return } + + let indentCounts = switchCaseRanges.map { formatter.currentIndentForLine(at: $0.beforeDelimiterRange.lowerBound).count } + let maxIndentCount = indentCounts.max() ?? 0 + + func sortableValue(for token: Token) -> String? { + switch token { + case let .identifier(name): + return name + case let .stringBody(body): + return body + case let .number(value, .hex): + return Int(value.dropFirst(2), radix: 16) + .map(String.init) ?? value + case let .number(value, .octal): + return Int(value.dropFirst(2), radix: 8) + .map(String.init) ?? value + case let .number(value, .binary): + return Int(value.dropFirst(2), radix: 2) + .map(String.init) ?? value + case let .number(value, _): + return value + default: + return nil + } + } + + let sorted = switchCaseRanges.sorted { case1, case2 -> Bool in + let lhs = formatter.tokens[case1.beforeDelimiterRange] + .compactMap(sortableValue) + let rhs = formatter.tokens[case2.beforeDelimiterRange] + .compactMap(sortableValue) + for (lhs, rhs) in zip(lhs, rhs) { + switch lhs.localizedStandardCompare(rhs) { + case .orderedAscending: + return true + case .orderedDescending: + return false + case .orderedSame: + continue + } + } + return lhs.count < rhs.count + } + + let sortedTokens = sorted.map { formatter.tokens[$0.beforeDelimiterRange] } + let sortedComments = sorted.map { formatter.tokens[$0.afterDelimiterRange] } + + // ignore if there's a where keyword and it is not in the last place. + let firstWhereIndex = sortedTokens.firstIndex(where: { slice in slice.contains(.keyword("where")) }) + guard firstWhereIndex == nil || firstWhereIndex == sortedTokens.count - 1 else { return } + + for switchCase in switchCaseRanges.enumerated().reversed() { + let newTokens = Array(sortedTokens[switchCase.offset]) + var newComments = Array(sortedComments[switchCase.offset]) + let oldComments = formatter.tokens[switchCaseRanges[switchCase.offset].afterDelimiterRange] + + if newComments.last?.isLinebreak == oldComments.last?.isLinebreak { + formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) + } else if newComments.count > 1, + newComments.last?.isLinebreak == true, oldComments.last?.isLinebreak == false + { + // indent the new content + newComments.append(.space(String(repeating: " ", count: maxIndentCount))) + formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) + } + + formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].beforeDelimiterRange, with: newTokens) + } + } + } +} diff --git a/Sources/Rules/SortTypealiases.swift b/Sources/Rules/SortTypealiases.swift new file mode 100644 index 00000000..77c67819 --- /dev/null +++ b/Sources/Rules/SortTypealiases.swift @@ -0,0 +1,135 @@ +// +// SortTypealiases.swift +// SwiftFormat +// +// Created by Cal Stephens on 5/6/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let sortTypealiases = FormatRule( + help: "Sort protocol composition typealiases alphabetically." + ) { formatter in + formatter.forEach(.keyword("typealias")) { typealiasIndex, _ in + guard let (equalsIndex, andTokenIndices, endIndex) = formatter.parseProtocolCompositionTypealias(at: typealiasIndex), + let typealiasNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) + else { + return + } + + var seenTypes = Set() + + // Split the typealias into individual elements. + // Any comments on their own line are grouped with the following element. + let delimiters = [equalsIndex] + andTokenIndices + var parsedElements: [(startIndex: Int, delimiterIndex: Int, endIndex: Int, type: String, allTokens: [Token], isDuplicate: Bool)] = [] + + for delimiter in delimiters.indices { + let endOfPreviousElement = parsedElements.last?.endIndex ?? typealiasNameIndex + let elementStartIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfPreviousElement) ?? delimiters[delimiter] + + // Start with the end index just being the end of the type name + var elementEndIndex: Int + let nextElementIsOnSameLine: Bool + if delimiter == delimiters.indices.last { + elementEndIndex = endIndex + nextElementIsOnSameLine = false + } else { + let nextDelimiterIndex = delimiters[delimiter + 1] + elementEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: nextDelimiterIndex) ?? (nextDelimiterIndex - 1) + + let endOfLine = formatter.endOfLine(at: elementEndIndex) + nextElementIsOnSameLine = formatter.endOfLine(at: nextDelimiterIndex) == endOfLine + } + + // Handle comments in multiline typealiases + if !nextElementIsOnSameLine { + // Any comments on the same line as the type name should be considered part of this element. + // Any comments after the linebreak are consisidered part of the next element. + // To do that we just extend this element to the end of the current line. + elementEndIndex = formatter.endOfLine(at: elementEndIndex) - 1 + } + + let tokens = Array(formatter.tokens[elementStartIndex ... elementEndIndex]) + let typeName = tokens + .filter { !$0.isSpaceOrCommentOrLinebreak && !$0.isOperator } + .map { $0.string }.joined() + + // While we're here, also filter out any duplicates. + // Since we're sorting, duplicates would sit right next to each other + // which makes them especially obvious. + let isDuplicate = seenTypes.contains(typeName) + seenTypes.insert(typeName) + + parsedElements.append(( + startIndex: elementStartIndex, + delimiterIndex: delimiters[delimiter], + endIndex: elementEndIndex, + type: typeName, + allTokens: tokens, + isDuplicate: isDuplicate + )) + } + + // Sort each element by type name + var sortedElements = parsedElements.sorted(by: { lhsElement, rhsElement in + lhsElement.type.lexicographicallyPrecedes(rhsElement.type) + }) + + // Don't modify the file if the typealias is already sorted + if parsedElements.map(\.startIndex) == sortedElements.map(\.startIndex) { + return + } + + let firstNonDuplicateIndex = sortedElements.firstIndex(where: { !$0.isDuplicate }) + + for elementIndex in sortedElements.indices { + // Revalidate all of the delimiters after sorting + // (the first delimiter should be `=` and all others should be `&` + let delimiterIndexInTokens = sortedElements[elementIndex].delimiterIndex - sortedElements[elementIndex].startIndex + + if elementIndex == firstNonDuplicateIndex { + sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("=", .infix) + } else { + sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("&", .infix) + } + + // Make sure there's always a linebreak after any comments, to prevent + // them from accidentially commenting out following elements of the typealias + if elementIndex != sortedElements.indices.last, + sortedElements[elementIndex].allTokens.last?.isComment == true, + let nextToken = formatter.nextToken(after: parsedElements[elementIndex].endIndex), + !nextToken.isLinebreak + { + sortedElements[elementIndex].allTokens.append(.linebreak("\n", 0)) + } + + // If this element starts with a comment, that's because the comment + // was originally on a line all by itself. To preserve this, make sure + // there's a linebreak before the comment. + if elementIndex != sortedElements.indices.first, + sortedElements[elementIndex].allTokens.first?.isComment == true, + let previousToken = formatter.lastToken(before: parsedElements[elementIndex].startIndex, where: { !$0.isSpace }), + !previousToken.isLinebreak + { + sortedElements[elementIndex].allTokens.insert(.linebreak("\n", 0), at: 0) + } + } + + // Replace each index in the parsed list with the corresponding index in the sorted list, + // working backwards to not invalidate any existing indices + for (originalElement, newElement) in zip(parsedElements, sortedElements).reversed() { + if newElement.isDuplicate, let tokenBeforeElement = formatter.index(of: .nonSpaceOrLinebreak, before: originalElement.startIndex) { + formatter.removeTokens(in: (tokenBeforeElement + 1) ... originalElement.endIndex) + } else { + formatter.replaceTokens( + in: originalElement.startIndex ... originalElement.endIndex, + with: newElement.allTokens + ) + } + } + } + } +} diff --git a/Sources/Rules/SortedImports.swift b/Sources/Rules/SortedImports.swift new file mode 100644 index 00000000..673cd224 --- /dev/null +++ b/Sources/Rules/SortedImports.swift @@ -0,0 +1,23 @@ +// +// SortedImports.swift +// SwiftFormat +// +// Created by Pablo Carcelén on 11/22/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Deprecated + static let sortedImports = FormatRule( + help: "Sort import statements alphabetically.", + deprecationMessage: "Use sortImports instead.", + options: ["importgrouping"], + sharedOptions: ["linebreaks"] + ) { formatter in + _ = formatter.options.importGrouping + _ = formatter.options.linebreak + FormatRule.sortImports.apply(with: formatter) + } +} diff --git a/Sources/Rules/SortedSwitchCases.swift b/Sources/Rules/SortedSwitchCases.swift new file mode 100644 index 00000000..b4c56ff5 --- /dev/null +++ b/Sources/Rules/SortedSwitchCases.swift @@ -0,0 +1,19 @@ +// +// SortedSwitchCases.swift +// SwiftFormat +// +// Created by Facundo Menzella on 9/22/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Deprecated + static let sortedSwitchCases = FormatRule( + help: "Sort switch cases alphabetically.", + deprecationMessage: "Use sortSwitchCases instead." + ) { formatter in + FormatRule.sortSwitchCases.apply(with: formatter) + } +} diff --git a/Sources/Rules/SpaceAroundBraces.swift b/Sources/Rules/SpaceAroundBraces.swift new file mode 100644 index 00000000..2ae15df5 --- /dev/null +++ b/Sources/Rules/SpaceAroundBraces.swift @@ -0,0 +1,39 @@ +// +// SpaceAroundBraces.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that there is space between an opening brace and the preceding + /// identifier, and between a closing brace and the following identifier. + static let spaceAroundBraces = FormatRule( + help: "Add or remove space around curly braces." + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + if let prevToken = formatter.token(at: i - 1) { + switch prevToken { + case .space, .linebreak, .operator(_, .prefix), .operator(_, .infix), + .startOfScope where !prevToken.isStringDelimiter: + break + default: + formatter.insert(.space(" "), at: i) + } + } + } + formatter.forEach(.endOfScope("}")) { i, _ in + if let nextToken = formatter.token(at: i + 1) { + switch nextToken { + case .identifier, .keyword: + formatter.insert(.space(" "), at: i + 1) + default: + break + } + } + } + } +} diff --git a/Sources/Rules/SpaceAroundBrackets.swift b/Sources/Rules/SpaceAroundBrackets.swift new file mode 100644 index 00000000..a5a9bd86 --- /dev/null +++ b/Sources/Rules/SpaceAroundBrackets.swift @@ -0,0 +1,72 @@ +// +// SpaceAroundBrackets.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement the following rules with respect to the spacing around square brackets: + /// * There is no space between an opening bracket and the preceding identifier, + /// unless the identifier is one of the specified keywords + /// * There is no space between an opening bracket and the preceding closing brace + /// * There is no space between an opening bracket and the preceding closing square bracket + /// * There is space between a closing bracket and following identifier + /// * There is space between a closing bracket and following opening brace + static let spaceAroundBrackets = FormatRule( + help: "Add or remove space around square brackets." + ) { formatter in + formatter.forEach(.startOfScope("[")) { i, _ in + let index = i - 1 + guard let prevToken = formatter.token(at: index) else { + return + } + switch prevToken { + case .keyword, + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + formatter.insert(.space(" "), at: i) + case .space: + let index = i - 2 + if let token = formatter.token(at: index) { + switch token { + case .identifier("as"), .identifier("is"), // not treated as keywords inside macro + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + break + case .identifier, .number, .endOfScope("]"), .endOfScope("}"), .endOfScope(")"): + formatter.removeToken(at: i - 1) + default: + break + } + } + default: + break + } + } + formatter.forEach(.endOfScope("]")) { i, _ in + guard let nextToken = formatter.token(at: i + 1) else { + return + } + switch nextToken { + case .identifier, .keyword, .startOfScope("{"), + .startOfScope("(") where formatter.isInClosureArguments(at: i): + formatter.insert(.space(" "), at: i + 1) + case .space: + switch formatter.token(at: i + 2) { + case .startOfScope("(")? where !formatter.isInClosureArguments(at: i + 2), .startOfScope("[")?: + formatter.removeToken(at: i + 1) + default: + break + } + default: + break + } + } + } +} diff --git a/Sources/Rules/SpaceAroundComments.swift b/Sources/Rules/SpaceAroundComments.swift new file mode 100644 index 00000000..2314bb21 --- /dev/null +++ b/Sources/Rules/SpaceAroundComments.swift @@ -0,0 +1,46 @@ +// +// SpaceAroundComments.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/31/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Add space around comments, except at the start or end of a line + static let spaceAroundComments = FormatRule( + help: "Add space before and/or after comments." + ) { formatter in + formatter.forEach(.startOfScope("//")) { i, _ in + if let prevToken = formatter.token(at: i - 1), !prevToken.isSpaceOrLinebreak { + formatter.insert(.space(" "), at: i) + } + } + formatter.forEach(.endOfScope("*/")) { i, _ in + guard let startIndex = formatter.index(of: .startOfScope("/*"), before: i), + case let .commentBody(commentStart)? = formatter.next(.nonSpaceOrLinebreak, after: startIndex), + case let .commentBody(commentEnd)? = formatter.last(.nonSpaceOrLinebreak, before: i), + !commentStart.hasPrefix("@"), !commentEnd.hasSuffix("@") + else { + return + } + if let nextToken = formatter.token(at: i + 1) { + if !nextToken.isSpaceOrLinebreak { + if nextToken != .delimiter(",") { + formatter.insert(.space(" "), at: i + 1) + } + } else if formatter.next(.nonSpace, after: i + 1) == .delimiter(",") { + formatter.removeToken(at: i + 1) + } + } + if let prevToken = formatter.token(at: startIndex - 1), !prevToken.isSpaceOrLinebreak { + if case let .commentBody(text) = prevToken, text.last?.unicodeScalars.last?.isSpace == true { + return + } + formatter.insert(.space(" "), at: startIndex) + } + } + } +} diff --git a/Sources/Rules/SpaceAroundGenerics.swift b/Sources/Rules/SpaceAroundGenerics.swift new file mode 100644 index 00000000..92de68c9 --- /dev/null +++ b/Sources/Rules/SpaceAroundGenerics.swift @@ -0,0 +1,24 @@ +// +// SpaceAroundGenerics.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure there is no space between an opening chevron and the preceding identifier + static let spaceAroundGenerics = FormatRule( + help: "Remove space around angle brackets." + ) { formatter in + formatter.forEach(.startOfScope("<")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isIdentifierOrKeyword == true + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/SpaceAroundOperators.swift b/Sources/Rules/SpaceAroundOperators.swift new file mode 100644 index 00000000..49e79289 --- /dev/null +++ b/Sources/Rules/SpaceAroundOperators.swift @@ -0,0 +1,142 @@ +// +// SpaceAroundOperators.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement the following rules with respect to the spacing around operators: + /// * Infix operators are separated from their operands by a space on either + /// side. Does not affect prefix/postfix operators, as required by syntax. + /// * Delimiters, such as commas and colons, are consistently followed by a + /// single space, unless it appears at the end of a line, and is not + /// preceded by a space, unless it appears at the beginning of a line. + static let spaceAroundOperators = FormatRule( + help: "Add or remove space around operators or delimiters.", + options: ["operatorfunc", "nospaceoperators", "ranges", "typedelimiter"] + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .operator(_, .none): + switch formatter.token(at: i + 1) { + case nil, .linebreak?, .endOfScope?, .operator?, .delimiter?, + .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: + break + case .space?: + switch formatter.next(.nonSpaceOrLinebreak, after: i) { + case nil, .linebreak?, .endOfScope?, .delimiter?, + .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: + formatter.removeToken(at: i + 1) + default: + break + } + default: + formatter.insert(.space(" "), at: i + 1) + } + case .operator("?", .postfix), .operator("!", .postfix): + if let prevToken = formatter.token(at: i - 1), + formatter.token(at: i + 1)?.isSpaceOrLinebreak == false, + [.keyword("as"), .keyword("try")].contains(prevToken) + { + formatter.insert(.space(" "), at: i + 1) + } + case .operator(".", _): + if formatter.token(at: i + 1)?.isSpace == true { + formatter.removeToken(at: i + 1) + } + guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { + formatter.removeTokens(in: 0 ..< i) + break + } + let spaceRequired: Bool + switch formatter.tokens[prevIndex] { + case .operator(_, .infix), .startOfScope: + return + case let token where token.isUnwrapOperator: + if let prevToken = formatter.last(.nonSpace, before: prevIndex), + [.keyword("as"), .keyword("try")].contains(prevToken) + { + spaceRequired = true + } else { + spaceRequired = false + } + case .operator(_, .prefix): + spaceRequired = false + case let token: + spaceRequired = !token.isAttribute && !token.isLvalue + } + if formatter.token(at: i - 1)?.isSpaceOrLinebreak == true { + if !spaceRequired { + formatter.removeToken(at: i - 1) + } + } else if spaceRequired { + formatter.insertSpace(" ", at: i) + } + case .operator("?", .infix): + break // Spacing around ternary ? is not optional + case let .operator(name, .infix) where formatter.options.noSpaceOperators.contains(name) || + (!formatter.options.spaceAroundRangeOperators && token.isRangeOperator): + if formatter.token(at: i + 1)?.isSpace == true, + formatter.token(at: i - 1)?.isSpace == true, + let nextToken = formatter.next(.nonSpace, after: i), + !nextToken.isCommentOrLinebreak, !nextToken.isOperator, + let prevToken = formatter.last(.nonSpace, before: i), + !prevToken.isCommentOrLinebreak, !prevToken.isOperator || prevToken.isUnwrapOperator + { + formatter.removeToken(at: i + 1) + formatter.removeToken(at: i - 1) + } + case .operator(_, .infix): + if formatter.token(at: i + 1)?.isSpaceOrLinebreak == false { + formatter.insert(.space(" "), at: i + 1) + } + if formatter.token(at: i - 1)?.isSpaceOrLinebreak == false { + formatter.insert(.space(" "), at: i) + } + case .operator(_, .prefix): + if let prevIndex = formatter.index(of: .nonSpace, before: i, if: { + [.startOfScope("["), .startOfScope("("), .startOfScope("<")].contains($0) + }) { + formatter.removeTokens(in: prevIndex + 1 ..< i) + } else if let prevToken = formatter.token(at: i - 1), + !prevToken.isSpaceOrLinebreak, !prevToken.isOperator + { + formatter.insert(.space(" "), at: i) + } + case .delimiter(":"): + // TODO: make this check more robust, and remove redundant space + if formatter.token(at: i + 1)?.isIdentifier == true, + formatter.token(at: i + 2) == .delimiter(":") + { + // It's a selector + break + } + fallthrough + case .operator(_, .postfix), .delimiter(","), .delimiter(";"), .startOfScope(":"): + switch formatter.token(at: i + 1) { + case nil, .space?, .linebreak?, .endOfScope?, .operator?, .delimiter?: + break + default: + // Ensure there is a space after the token + formatter.insert(.space(" "), at: i + 1) + } + + let spaceBeforeToken = formatter.token(at: i - 1)?.isSpace == true + && formatter.token(at: i - 2)?.isLinebreak == false + + if spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaceAfter { + // Remove space before the token + formatter.removeToken(at: i - 1) + } else if !spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaced { + formatter.insertSpace(" ", at: i) + } + default: + break + } + } + } +} diff --git a/Sources/Rules/SpaceAroundParens.swift b/Sources/Rules/SpaceAroundParens.swift new file mode 100644 index 00000000..e9a449b2 --- /dev/null +++ b/Sources/Rules/SpaceAroundParens.swift @@ -0,0 +1,111 @@ +// +// SpaceAroundParens.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement the following rules with respect to the spacing around parens: + /// * There is no space between an opening paren and the preceding identifier, + /// unless the identifier is one of the specified keywords + /// * There is no space between an opening paren and the preceding closing brace + /// * There is no space between an opening paren and the preceding closing square bracket + /// * There is space between a closing paren and following identifier + /// * There is space between a closing paren and following opening brace + /// * There is no space between a closing paren and following opening square bracket + static let spaceAroundParens = FormatRule( + help: "Add or remove space around parentheses." + ) { formatter in + func spaceAfter(_ keywordOrAttribute: String, index: Int) -> Bool { + switch keywordOrAttribute { + case "@autoclosure": + if formatter.options.swiftVersion < "3", + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: index), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("escaping") + { + assert(formatter.tokens[nextIndex] == .startOfScope("(")) + return false + } + return true + case "@escaping", "@noescape", "@Sendable": + return true + case _ where keywordOrAttribute.hasPrefix("@"): + if let i = formatter.index(of: .startOfScope("("), after: index) { + return formatter.isParameterList(at: i) + } + return false + case "private", "fileprivate", "internal", + "init", "subscript", "throws": + return false + case "await": + return formatter.options.swiftVersion >= "5.5" || + formatter.options.swiftVersion == .undefined + default: + return keywordOrAttribute.first.map { !"@#".contains($0) } ?? true + } + } + + formatter.forEach(.startOfScope("(")) { i, _ in + let index = i - 1 + guard let prevToken = formatter.token(at: index) else { + return + } + switch prevToken { + case let .keyword(string) where spaceAfter(string, index: index): + fallthrough + case .endOfScope("]") where formatter.isInClosureArguments(at: index), + .endOfScope(")") where formatter.isAttribute(at: index), + .identifier("some") where formatter.isTypePosition(at: index), + .identifier("any") where formatter.isTypePosition(at: index), + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("isolated") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + formatter.insert(.space(" "), at: i) + case .space: + let index = i - 2 + guard let token = formatter.token(at: index) else { + return + } + switch token { + case .identifier("some") where formatter.isTypePosition(at: index), + .identifier("any") where formatter.isTypePosition(at: index), + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("isolated") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + break + case let .keyword(string) where !spaceAfter(string, index: index): + fallthrough + case .number, .identifier: + fallthrough + case .endOfScope("}"), .endOfScope(">"), + .endOfScope("]") where !formatter.isInClosureArguments(at: index), + .endOfScope(")") where !formatter.isAttribute(at: index): + formatter.removeToken(at: i - 1) + default: + break + } + default: + break + } + } + formatter.forEach(.endOfScope(")")) { i, _ in + guard let nextToken = formatter.token(at: i + 1) else { + return + } + switch nextToken { + case .identifier, .keyword, .startOfScope("{"): + formatter.insert(.space(" "), at: i + 1) + case .space where formatter.token(at: i + 2) == .startOfScope("["): + formatter.removeToken(at: i + 1) + default: + break + } + } + } +} diff --git a/Sources/Rules/SpaceInsideBraces.swift b/Sources/Rules/SpaceInsideBraces.swift new file mode 100644 index 00000000..8394c095 --- /dev/null +++ b/Sources/Rules/SpaceInsideBraces.swift @@ -0,0 +1,35 @@ +// +// SpaceInsideBraces.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that there is space immediately inside braces + static let spaceInsideBraces = FormatRule( + help: "Add space inside curly braces." + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + if let nextToken = formatter.token(at: i + 1) { + if !nextToken.isSpaceOrLinebreak, + ![.endOfScope("}"), .startOfScope("{")].contains(nextToken) + { + formatter.insert(.space(" "), at: i + 1) + } + } + } + formatter.forEach(.endOfScope("}")) { i, _ in + if let prevToken = formatter.token(at: i - 1) { + if !prevToken.isSpaceOrLinebreak, + ![.endOfScope("}"), .startOfScope("{")].contains(prevToken) + { + formatter.insert(.space(" "), at: i) + } + } + } + } +} diff --git a/Sources/Rules/SpaceInsideBrackets.swift b/Sources/Rules/SpaceInsideBrackets.swift new file mode 100644 index 00000000..a768fb39 --- /dev/null +++ b/Sources/Rules/SpaceInsideBrackets.swift @@ -0,0 +1,31 @@ +// +// SpaceInsideBrackets.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove space immediately inside square brackets + static let spaceInsideBrackets = FormatRule( + help: "Remove space inside square brackets." + ) { formatter in + formatter.forEach(.startOfScope("[")) { i, _ in + if formatter.token(at: i + 1)?.isSpace == true, + formatter.token(at: i + 2)?.isComment == false + { + formatter.removeToken(at: i + 1) + } + } + formatter.forEach(.endOfScope("]")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isCommentOrLinebreak == false + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/SpaceInsideComments.swift b/Sources/Rules/SpaceInsideComments.swift new file mode 100644 index 00000000..03d16140 --- /dev/null +++ b/Sources/Rules/SpaceInsideComments.swift @@ -0,0 +1,56 @@ +// +// SpaceInsideComments.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/31/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Add space inside comments, taking care not to mangle headerdoc or + /// carefully preformatted comments, such as star boxes, etc. + static let spaceInsideComments = FormatRule( + help: "Add leading and/or trailing space inside comments." + ) { formatter in + formatter.forEach(.startOfScope("//")) { i, _ in + guard case let .commentBody(string)? = formatter.token(at: i + 1), + let first = string.first else { return } + if "/!:".contains(first) { + let nextIndex = string.index(after: string.startIndex) + if nextIndex < string.endIndex, case let next = string[nextIndex], !" \t/".contains(next) { + let string = String(string.first!) + " " + String(string.dropFirst()) + formatter.replaceToken(at: i + 1, with: .commentBody(string)) + } + } else if !" \t".contains(first), !string.hasPrefix("===") { // Special-case check for swift stdlib codebase + formatter.insert(.space(" "), at: i + 1) + } + } + formatter.forEach(.startOfScope("/*")) { i, _ in + guard case let .commentBody(string)? = formatter.token(at: i + 1), + !string.hasPrefix("---"), !string.hasPrefix("@"), !string.hasSuffix("---"), !string.hasSuffix("@") + else { + return + } + if let first = string.first, "*!:".contains(first) { + let nextIndex = string.index(after: string.startIndex) + if nextIndex < string.endIndex, case let next = string[nextIndex], + !" /t".contains(next), !string.hasPrefix("**"), !string.hasPrefix("*/") + { + let string = String(string.first!) + " " + String(string.dropFirst()) + formatter.replaceToken(at: i + 1, with: .commentBody(string)) + } + } else { + formatter.insert(.space(" "), at: i + 1) + } + if let i = formatter.index(of: .endOfScope("*/"), after: i), let prevToken = formatter.token(at: i - 1) { + if !prevToken.isSpaceOrLinebreak, !prevToken.string.hasSuffix("*"), + !prevToken.string.trimmingCharacters(in: .whitespaces).isEmpty + { + formatter.insert(.space(" "), at: i) + } + } + } + } +} diff --git a/Sources/Rules/SpaceInsideGenerics.swift b/Sources/Rules/SpaceInsideGenerics.swift new file mode 100644 index 00000000..c21d19b0 --- /dev/null +++ b/Sources/Rules/SpaceInsideGenerics.swift @@ -0,0 +1,29 @@ +// +// SpaceInsideGenerics.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove space immediately inside chevrons + static let spaceInsideGenerics = FormatRule( + help: "Remove space inside angle brackets." + ) { formatter in + formatter.forEach(.startOfScope("<")) { i, _ in + if formatter.token(at: i + 1)?.isSpace == true { + formatter.removeToken(at: i + 1) + } + } + formatter.forEach(.endOfScope(">")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isLinebreak == false + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/SpaceInsideParens.swift b/Sources/Rules/SpaceInsideParens.swift new file mode 100644 index 00000000..c557340e --- /dev/null +++ b/Sources/Rules/SpaceInsideParens.swift @@ -0,0 +1,31 @@ +// +// SpaceInsideParens.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove space immediately inside parens + static let spaceInsideParens = FormatRule( + help: "Remove space inside parentheses." + ) { formatter in + formatter.forEach(.startOfScope("(")) { i, _ in + if formatter.token(at: i + 1)?.isSpace == true, + formatter.token(at: i + 2)?.isComment == false + { + formatter.removeToken(at: i + 1) + } + } + formatter.forEach(.endOfScope(")")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isCommentOrLinebreak == false + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/Specifiers.swift b/Sources/Rules/Specifiers.swift new file mode 100644 index 00000000..13f2a75f --- /dev/null +++ b/Sources/Rules/Specifiers.swift @@ -0,0 +1,21 @@ +// +// Specifiers.swift +// SwiftFormat +// +// Created by Nick Lockwood on 9/6/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Deprecated + static let specifiers = FormatRule( + help: "Use consistent ordering for member modifiers.", + deprecationMessage: "Use modifierOrder instead.", + options: ["modifierorder"] + ) { formatter in + _ = formatter.options.modifierOrder + FormatRule.modifierOrder.apply(with: formatter) + } +} diff --git a/Sources/Rules/StrongOutlets.swift b/Sources/Rules/StrongOutlets.swift new file mode 100644 index 00000000..c3210ce6 --- /dev/null +++ b/Sources/Rules/StrongOutlets.swift @@ -0,0 +1,35 @@ +// +// StrongOutlets.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/24/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Strip unnecessary `weak` from @IBOutlet properties (except delegates and datasources) + static let strongOutlets = FormatRule( + help: "Remove `weak` modifier from `@IBOutlet` properties." + ) { formatter in + formatter.forEach(.keyword("@IBOutlet")) { i, _ in + guard let varIndex = formatter.index(of: .keyword("var"), after: i), + let weakIndex = (i ..< varIndex).first(where: { formatter.tokens[$0] == .identifier("weak") }), + case let .identifier(name)? = formatter.next(.identifier, after: varIndex) + else { + return + } + let lowercased = name.lowercased() + if lowercased.hasSuffix("delegate") || lowercased.hasSuffix("datasource") { + return + } + if formatter.tokens[weakIndex + 1].isSpace { + formatter.removeToken(at: weakIndex + 1) + } else if formatter.tokens[weakIndex - 1].isSpace { + formatter.removeToken(at: weakIndex - 1) + } + formatter.removeToken(at: weakIndex) + } + } +} diff --git a/Sources/Rules/StrongifiedSelf.swift b/Sources/Rules/StrongifiedSelf.swift new file mode 100644 index 00000000..0ca11d5a --- /dev/null +++ b/Sources/Rules/StrongifiedSelf.swift @@ -0,0 +1,28 @@ +// +// StrongifiedSelf.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/24/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Removed backticks from `self` when strongifying + static let strongifiedSelf = FormatRule( + help: "Remove backticks around `self` in Optional unwrap expressions." + ) { formatter in + formatter.forEach(.identifier("`self`")) { i, _ in + guard formatter.options.swiftVersion >= "4.2", + let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .operator("=", .infix) + }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self"), + formatter.isConditionalStatement(at: i) + else { + return + } + formatter.replaceToken(at: i, with: .identifier("self")) + } + } +} diff --git a/Sources/Rules/Todos.swift b/Sources/Rules/Todos.swift new file mode 100644 index 00000000..79c490fe --- /dev/null +++ b/Sources/Rules/Todos.swift @@ -0,0 +1,69 @@ +// +// Todos.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/23/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that TODO, MARK and FIXME comments are followed by a : as required + static let todos = FormatRule( + help: "Use correct formatting for `TODO:`, `MARK:` or `FIXME:` comments." + ) { formatter in + formatter.forEachToken { i, token in + guard case var .commentBody(string) = token else { + return + } + var removedSpace = false + if string.hasPrefix("/"), let scopeStart = formatter.index(of: .startOfScope, before: i, if: { + $0 == .startOfScope("//") + }) { + if let prevLinebreak = formatter.index(of: .linebreak, before: scopeStart), + case .commentBody? = formatter.last(.nonSpace, before: prevLinebreak) + { + return + } + if let nextLinebreak = formatter.index(of: .linebreak, after: i), + case .startOfScope("//")? = formatter.next(.nonSpace, after: nextLinebreak) + { + return + } + removedSpace = true + string = string.replacingOccurrences(of: "^/(\\s+)", with: "", options: .regularExpression) + } + for pair in [ + "todo:": "TODO:", + "todo :": "TODO:", + "fixme:": "FIXME:", + "fixme :": "FIXME:", + "mark:": "MARK:", + "mark :": "MARK:", + "mark-": "MARK: -", + "mark -": "MARK: -", + ] where string.lowercased().hasPrefix(pair.0) { + string = pair.1 + string.dropFirst(pair.0.count) + } + guard let tag = ["TODO", "MARK", "FIXME"].first(where: { string.hasPrefix($0) }) else { + return + } + var suffix = String(string[tag.endIndex ..< string.endIndex]) + if let first = suffix.unicodeScalars.first, !" :".unicodeScalars.contains(first) { + // If not followed by a space or :, don't mess with it as it may be a custom format + return + } + while let first = suffix.unicodeScalars.first, " :".unicodeScalars.contains(first) { + suffix = String(suffix.dropFirst()) + } + if tag == "MARK", suffix.hasPrefix("-"), suffix != "-", !suffix.hasPrefix("- ") { + suffix = "- " + suffix.dropFirst() + } + formatter.replaceToken(at: i, with: .commentBody(tag + ":" + (suffix.isEmpty ? "" : " \(suffix)"))) + if removedSpace { + formatter.insertSpace(" ", at: i) + } + } + } +} diff --git a/Sources/Rules/TrailingClosures.swift b/Sources/Rules/TrailingClosures.swift new file mode 100644 index 00000000..fa3bd7f9 --- /dev/null +++ b/Sources/Rules/TrailingClosures.swift @@ -0,0 +1,68 @@ +// +// TrailingClosures.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/17/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Convert closure arguments to trailing closure syntax where possible + static let trailingClosures = FormatRule( + help: "Use trailing closure syntax where applicable.", + options: ["trailingclosures", "nevertrailing"] + ) { formatter in + let useTrailing = Set([ + "async", "asyncAfter", "sync", "autoreleasepool", + ] + formatter.options.trailingClosures) + + let nonTrailing = Set([ + "performBatchUpdates", + "expect", // Special case to support autoclosure arguments in the Nimble framework + ] + formatter.options.neverTrailing) + + formatter.forEach(.startOfScope("(")) { i, _ in + guard let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + case let .identifier(name) = prevToken, // TODO: are trailing closures allowed in other cases? + !nonTrailing.contains(name), !formatter.isConditionalStatement(at: i) + else { + return + } + guard let closingIndex = formatter.index(of: .endOfScope(")"), after: i), let closingBraceIndex = + formatter.index(of: .nonSpaceOrComment, before: closingIndex, if: { $0 == .endOfScope("}") }), + let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: closingBraceIndex), + formatter.index(of: .endOfScope("}"), before: openingBraceIndex) == nil + else { + return + } + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) != .startOfScope("{"), + var startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: openingBraceIndex) + else { + return + } + switch formatter.tokens[startIndex] { + case .delimiter(","), .startOfScope("("): + break + case .delimiter(":"): + guard useTrailing.contains(name) else { + return + } + if let commaIndex = formatter.index(of: .delimiter(","), before: openingBraceIndex) { + startIndex = commaIndex + } else if formatter.index(of: .startOfScope("("), before: openingBraceIndex) == i { + startIndex = i + } else { + return + } + default: + return + } + let wasParen = (startIndex == i) + formatter.removeParen(at: closingIndex) + formatter.replaceTokens(in: startIndex ..< openingBraceIndex, with: + wasParen ? [.space(" ")] : [.endOfScope(")"), .space(" ")]) + } + } +} diff --git a/Sources/Rules/TrailingCommas.swift b/Sources/Rules/TrailingCommas.swift new file mode 100644 index 00000000..82f89f78 --- /dev/null +++ b/Sources/Rules/TrailingCommas.swift @@ -0,0 +1,55 @@ +// +// TrailingCommas.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that the last item in a multi-line array literal is followed by a comma. + /// This is useful for preventing noise in commits when items are added to end of array. + static let trailingCommas = FormatRule( + help: "Add or remove trailing comma from the last item in a collection literal.", + options: ["commas"] + ) { formatter in + formatter.forEach(.endOfScope("]")) { i, _ in + guard let prevTokenIndex = formatter.index(of: .nonSpaceOrComment, before: i), + let scopeType = formatter.scopeType(at: i) + else { + return + } + switch scopeType { + case .array, .dictionary: + switch formatter.tokens[prevTokenIndex] { + case .linebreak: + guard let prevTokenIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex + 1 + ) else { + break + } + switch formatter.tokens[prevTokenIndex] { + case .startOfScope("["), .delimiter(":"): + break // do nothing + case .delimiter(","): + if !formatter.options.trailingCommas { + formatter.removeToken(at: prevTokenIndex) + } + default: + if formatter.options.trailingCommas { + formatter.insert(.delimiter(","), at: prevTokenIndex + 1) + } + } + case .delimiter(","): + formatter.removeToken(at: prevTokenIndex) + default: + break + } + default: + return + } + } + } +} diff --git a/Sources/Rules/TrailingSpace.swift b/Sources/Rules/TrailingSpace.swift new file mode 100644 index 00000000..75b0941a --- /dev/null +++ b/Sources/Rules/TrailingSpace.swift @@ -0,0 +1,27 @@ +// +// TrailingSpace.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/24/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove trailing space from the end of lines, as it has no semantic + /// meaning and leads to noise in commits. + static let trailingSpace = FormatRule( + help: "Remove trailing space at end of a line.", + orderAfter: [.wrap, .wrapArguments], + options: ["trimwhitespace"] + ) { formatter in + formatter.forEach(.space) { i, _ in + if formatter.token(at: i + 1)?.isLinebreak ?? true, + formatter.options.truncateBlankLines || formatter.token(at: i - 1)?.isLinebreak == false + { + formatter.removeToken(at: i) + } + } + } +} diff --git a/Sources/Rules/TypeSugar.swift b/Sources/Rules/TypeSugar.swift new file mode 100644 index 00000000..fc4c1b3d --- /dev/null +++ b/Sources/Rules/TypeSugar.swift @@ -0,0 +1,105 @@ +// +// TypeSugar.swift +// SwiftFormat +// +// Created by Nick Lockwood on 2/2/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace Array, Dictionary and Optional with [T], [T: U] and T? + static let typeSugar = FormatRule( + help: "Prefer shorthand syntax for Arrays, Dictionaries and Optionals.", + options: ["shortoptionals"] + ) { formatter in + formatter.forEach(.startOfScope("<")) { i, _ in + guard let typeIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), + case let .identifier(identifier) = formatter.tokens[typeIndex], + let endIndex = formatter.index(of: .endOfScope(">"), after: i), + let typeStart = formatter.index(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex), + let typeEnd = formatter.lastIndex(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex) + else { + return + } + let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex, if: { + $0.isOperator(".") + }) + if let dotIndex = dotIndex, formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: dotIndex, if: { + ![.identifier("self"), .identifier("Type")].contains($0) + }) != nil, identifier != "Optional" { + return + } + // Workaround for https://bugs.swift.org/browse/SR-12856 + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) != .delimiter(":") || + formatter.currentScope(at: i) == .startOfScope("[") + { + var startIndex = i + if formatter.tokens[typeIndex] == .identifier("Dictionary") { + startIndex = formatter.index(of: .delimiter(","), in: i + 1 ..< endIndex) ?? startIndex + } + if let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startIndex, if: { + $0 == .startOfScope("(") + }), let underscoreIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { + $0 == .identifier("_") + }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: underscoreIndex)?.isIdentifier == true { + return + } + } + if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) { + switch prevToken { + case .keyword("struct"), .keyword("class"), .keyword("actor"), + .keyword("enum"), .keyword("protocol"), .keyword("typealias"): + return + default: + break + } + } + switch formatter.tokens[typeIndex] { + case .identifier("Array"): + formatter.replaceTokens(in: typeIndex ... endIndex, with: + [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) + case .identifier("Dictionary"): + guard let commaIndex = formatter.index(of: .delimiter(","), in: typeStart ..< typeEnd) else { + return + } + formatter.replaceToken(at: commaIndex, with: .delimiter(":")) + formatter.replaceTokens(in: typeIndex ... endIndex, with: + [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) + case .identifier("Optional"): + if formatter.options.shortOptionals == .exceptProperties, + let lastKeyword = formatter.lastSignificantKeyword(at: i), + ["var", "let"].contains(lastKeyword) + { + return + } + if formatter.lastSignificantKeyword(at: i) == "case" || + formatter.last(.endOfScope, before: i) == .endOfScope("case") + { + // https://bugs.swift.org/browse/SR-13838 + return + } + var typeTokens = formatter.tokens[typeStart ... typeEnd] + if [.operator("&", .infix), .operator("->", .infix), + .identifier("some"), .identifier("any")].contains(where: typeTokens.contains) + { + typeTokens.insert(.startOfScope("("), at: typeTokens.startIndex) + typeTokens.append(.endOfScope(")")) + } + typeTokens.append(.operator("?", .postfix)) + formatter.replaceTokens(in: typeIndex ... endIndex, with: typeTokens) + default: + return + } + // Drop leading Swift. namespace + if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: typeIndex, if: { + $0.isOperator(".") + }), let swiftTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex, if: { + $0 == .identifier("Swift") + }) { + formatter.removeTokens(in: swiftTokenIndex ..< typeIndex) + } + } + } +} diff --git a/Sources/Rules/UnusedArguments.swift b/Sources/Rules/UnusedArguments.swift new file mode 100644 index 00000000..a2562690 --- /dev/null +++ b/Sources/Rules/UnusedArguments.swift @@ -0,0 +1,315 @@ +// +// UnusedArguments.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/3/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace unused arguments with an underscore + static let unusedArguments = FormatRule( + help: "Mark unused function arguments with `_`.", + options: ["stripunusedargs"] + ) { formatter in + guard !formatter.options.fragment else { return } + + func removeUsed(from argNames: inout [String], with associatedData: inout [T], + locals: Set = [], in range: CountableRange) + { + var isDeclaration = false + var wasDeclaration = false + var isConditional = false + var isGuard = false + var locals = locals + var tempLocals = Set() + func pushLocals() { + if isDeclaration, isConditional { + for name in tempLocals { + if let index = argNames.firstIndex(of: name), + !locals.contains(name) + { + argNames.remove(at: index) + associatedData.remove(at: index) + } + } + } + wasDeclaration = isDeclaration + isDeclaration = false + locals.formUnion(tempLocals) + tempLocals.removeAll() + } + var i = range.lowerBound + while i < range.upperBound { + if formatter.isStartOfStatement(at: i, treatingCollectionKeysAsStart: false), + // Immediately following an `=` operator, if or switch keywords + // are expressions rather than statements. + formatter.lastToken(before: i, where: { !$0.isSpaceOrCommentOrLinebreak })?.isOperator("=") != true + { + pushLocals() + wasDeclaration = false + } + let token = formatter.tokens[i] + outer: switch token { + case .keyword("guard"): + isGuard = true + case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): + isDeclaration = true + var i = i + while let scopeStart = formatter.index(of: .startOfScope("("), before: i) { + i = scopeStart + } + isConditional = formatter.isConditionalStatement(at: i) + case .identifier: + let name = token.unescaped() + guard let index = argNames.firstIndex(of: name), !locals.contains(name) else { + break + } + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(".") == false, + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":") || + [.startOfScope("("), .startOfScope("[")].contains(formatter.currentScope(at: i) ?? .space("")) + { + if isDeclaration { + switch formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { + case .endOfScope(")")?, .operator("=", .infix)?, + .delimiter(",")? where !isConditional: + tempLocals.insert(name) + break outer + default: + break + } + } + argNames.remove(at: index) + associatedData.remove(at: index) + if argNames.isEmpty { + return + } + } + case .keyword("if"), .keyword("switch"): + guard formatter.isConditionalAssignment(at: i), + let conditinalBranches = formatter.conditionalBranches(at: i), + let endIndex = conditinalBranches.last?.endOfBranch + else { fallthrough } + + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + case .startOfScope("{"): + guard let endIndex = formatter.endOfScope(at: i) else { + return formatter.fatalError("Expected }", at: i) + } + if formatter.isStartOfClosure(at: i) { + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + } else if isGuard { + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + pushLocals() + } else { + let prevLocals = locals + pushLocals() + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + locals = prevLocals + } + + isGuard = false + i = endIndex + case .endOfScope("case"), .endOfScope("default"): + pushLocals() + guard let colonIndex = formatter.index(of: .startOfScope(":"), after: i) else { + return formatter.fatalError("Expected :", at: i) + } + guard let endIndex = formatter.endOfScope(at: colonIndex) else { + return formatter.fatalError("Expected end of case statement", + at: colonIndex) + } + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + i = endIndex - 1 + case .operator("=", .infix), .delimiter(":"), .startOfScope(":"), + .keyword("in"), .keyword("where"): + wasDeclaration = isDeclaration + isDeclaration = false + case .delimiter(","): + if let scope = formatter.currentScope(at: i), [ + .startOfScope("("), .startOfScope("["), .startOfScope("<"), + ].contains(scope) { + break + } + if isConditional { + if isGuard, wasDeclaration { + pushLocals() + } + wasDeclaration = false + } else { + let _wasDeclaration = wasDeclaration + pushLocals() + isDeclaration = _wasDeclaration + } + case .delimiter(";"): + pushLocals() + wasDeclaration = false + default: + break + } + i += 1 + } + } + // Closure arguments + formatter.forEach(.keyword("in")) { i, _ in + var argNames = [String]() + var nameIndexPairs = [(Int, Int)]() + guard let start = formatter.index(of: .startOfScope("{"), before: i) else { + return + } + var index = i - 1 + var argCountStack = [0] + while index > start { + let token = formatter.tokens[index] + switch token { + case .endOfScope("}"): + return + case .endOfScope("]"): + // TODO: handle unused capture list arguments + index = formatter.index(of: .startOfScope("["), before: index) ?? index + case .endOfScope(")"): + argCountStack.append(argNames.count) + case .startOfScope("("): + argCountStack.removeLast() + case .delimiter(","): + argCountStack[argCountStack.count - 1] = argNames.count + case .identifier("async") where + formatter.last(.nonSpaceOrLinebreak, before: index)?.isIdentifier == true: + fallthrough + case .operator("->", .infix), .keyword("throws"): + // Everything after this was part of return value + let count = argCountStack.last ?? 0 + argNames.removeSubrange(count ..< argNames.count) + nameIndexPairs.removeSubrange(count ..< nameIndexPairs.count) + case let .keyword(name) where + !token.isAttribute && !name.hasPrefix("#") && name != "inout": + return + case .identifier: + guard argCountStack.count < 3, + let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), [ + .delimiter(","), .startOfScope("("), .startOfScope("{"), .endOfScope("]"), + ].contains(prevToken), let scopeStart = formatter.index(of: .startOfScope, before: index), + ![.startOfScope("["), .startOfScope("<")].contains(formatter.tokens[scopeStart]) + else { + break + } + let name = token.unescaped() + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index), + let nextToken = formatter.token(at: nextIndex), case .identifier = nextToken, + formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .delimiter(":") + { + let internalName = nextToken.unescaped() + if internalName != "_" { + argNames.append(internalName) + nameIndexPairs.append((index, nextIndex)) + } + } else if name != "_" { + argNames.append(name) + nameIndexPairs.append((index, index)) + } + default: + break + } + index -= 1 + } + guard !argNames.isEmpty, let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: i) else { + return + } + removeUsed(from: &argNames, with: &nameIndexPairs, in: i + 1 ..< bodyEndIndex) + for pair in nameIndexPairs { + if case .identifier("_") = formatter.tokens[pair.0], pair.0 != pair.1 { + formatter.removeToken(at: pair.1) + if formatter.tokens[pair.1 - 1] == .space(" ") { + formatter.removeToken(at: pair.1 - 1) + } + } else { + formatter.replaceToken(at: pair.1, with: .identifier("_")) + } + } + } + // Function arguments + formatter.forEachToken { i, token in + guard formatter.options.stripUnusedArguments != .closureOnly, + case let .keyword(keyword) = token, ["func", "init", "subscript"].contains(keyword), + let startIndex = formatter.index(of: .startOfScope("("), after: i), + let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) else { return } + let isOperator = (keyword == "subscript") || + (keyword == "func" && formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isOperator == true) + var index = startIndex + var argNames = [String]() + var nameIndexPairs = [(Int, Int)]() + while index < endIndex { + guard let externalNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + if case let .identifier(name) = $0 { + return formatter.options.stripUnusedArguments != .unnamedOnly || name == "_" + } + // Probably an empty argument list + return false + }) else { return } + guard let nextIndex = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: externalNameIndex) else { return } + let nextToken = formatter.tokens[nextIndex] + switch nextToken { + case let .identifier(name): + if name != "_" { + argNames.append(nextToken.unescaped()) + nameIndexPairs.append((externalNameIndex, nextIndex)) + } + case .delimiter(":"): + let externalNameToken = formatter.tokens[externalNameIndex] + if case let .identifier(name) = externalNameToken, name != "_" { + argNames.append(externalNameToken.unescaped()) + nameIndexPairs.append((externalNameIndex, externalNameIndex)) + } + default: + return + } + index = formatter.index(of: .delimiter(","), after: index) ?? endIndex + } + guard !argNames.isEmpty, let bodyStartIndex = formatter.index(after: endIndex, where: { + switch $0 { + case .startOfScope("{"): // What we're looking for + return true + case .keyword("throws"), + .keyword("rethrows"), + .identifier("async"), + .keyword("where"), + .keyword("is"): + return false // Keep looking + case .keyword: + return true // Not valid between end of arguments and start of body + default: + return false // Keep looking + } + }), formatter.tokens[bodyStartIndex] == .startOfScope("{"), + let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: bodyStartIndex) else { + return + } + removeUsed(from: &argNames, with: &nameIndexPairs, in: bodyStartIndex + 1 ..< bodyEndIndex) + for pair in nameIndexPairs.reversed() { + if pair.0 == pair.1 { + if isOperator { + formatter.replaceToken(at: pair.0, with: .identifier("_")) + } else { + formatter.insert(.identifier("_"), at: pair.0 + 1) + formatter.insert(.space(" "), at: pair.0 + 1) + } + } else if case .identifier("_") = formatter.tokens[pair.0] { + formatter.removeToken(at: pair.1) + if formatter.tokens[pair.1 - 1] == .space(" ") { + formatter.removeToken(at: pair.1 - 1) + } + } else { + formatter.replaceToken(at: pair.1, with: .identifier("_")) + } + } + } + } +} diff --git a/Sources/Rules/UnusedPrivateDeclaration.swift b/Sources/Rules/UnusedPrivateDeclaration.swift new file mode 100644 index 00000000..dd39b106 --- /dev/null +++ b/Sources/Rules/UnusedPrivateDeclaration.swift @@ -0,0 +1,66 @@ +// +// UnusedPrivateDeclaration.swift +// SwiftFormat +// +// Created by Manny Lopez on 7/17/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove unused private and fileprivate declarations + static let unusedPrivateDeclaration = FormatRule( + help: "Remove unused private and fileprivate declarations.", + disabledByDefault: true, + options: ["preservedecls"] + ) { formatter in + guard !formatter.options.fragment else { return } + + // Only remove unused properties, functions, or typealiases. + // - This rule doesn't currently support removing unused types, + // and it's more difficult to track the usage of other declaration + // types like `init`, `subscript`, `operator`, etc. + let allowlist = ["let", "var", "func", "typealias"] + let disallowedModifiers = ["override", "@objc", "@IBAction", "@IBSegueAction", "@IBOutlet", "@IBDesignable", "@IBInspectable", "@NSManaged", "@GKInspectable"] + + // Collect all of the `private` or `fileprivate` declarations in the file + var privateDeclarations: [Declaration] = [] + formatter.forEachRecursiveDeclaration { declaration in + let declarationModifiers = Set(declaration.modifiers) + let hasDisallowedModifiers = disallowedModifiers.contains(where: { declarationModifiers.contains($0) }) + + guard allowlist.contains(declaration.keyword), + let name = declaration.name, + !formatter.options.preservedPrivateDeclarations.contains(name), + !hasDisallowedModifiers + else { return } + + switch formatter.visibility(of: declaration) { + case .fileprivate, .private: + privateDeclarations.append(declaration) + case .none, .open, .public, .package, .internal: + break + } + } + + // Count the usage of each identifier in the file + var usage: [String: Int] = [:] + formatter.forEach(.identifier) { _, token in + usage[token.string, default: 0] += 1 + } + + // Remove any private or fileprivate declaration whose name only + // appears a single time in the source file + for declaration in privateDeclarations.reversed() { + // Strip backticks from name for a normalized base name for cases like `default` + guard let name = declaration.name?.trimmingCharacters(in: CharacterSet(charactersIn: "`")) else { continue } + // Check for regular usage, common property wrapper prefixes, and protected names + let variants = [name, "_\(name)", "$\(name)", "`\(name)`"] + let count = variants.compactMap { usage[$0] }.reduce(0, +) + if count <= 1 { + formatter.removeTokens(in: declaration.originalRange) + } + } + } +} diff --git a/Sources/Rules/Void.swift b/Sources/Rules/Void.swift new file mode 100644 index 00000000..ea65c15c --- /dev/null +++ b/Sources/Rules/Void.swift @@ -0,0 +1,138 @@ +// +// Void.swift +// SwiftFormat +// +// Created by Nick Lockwood on 10/19/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Normalize the use of void in closure arguments and return values + static let void = FormatRule( + help: "Use `Void` for type declarations and `()` for values.", + options: ["voidtype"] + ) { formatter in + let hasLocalVoid = formatter.hasLocalVoid() + + formatter.forEach(.identifier("Void")) { i, _ in + if let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .endOfScope(")") + }), var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), { + let token = formatter.tokens[prevIndex] + if token == .delimiter(":"), + let prevPrevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex), + formatter.tokens[prevPrevIndex] == .identifier("_"), + let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevPrevIndex), + formatter.tokens[startIndex] == .startOfScope("(") + { + prevIndex = startIndex + return true + } + return token == .startOfScope("(") + }() { + if formatter.isArgumentToken(at: nextIndex) || formatter.last( + .nonSpaceOrLinebreak, + before: prevIndex + )?.isIdentifier == true { + if !formatter.options.useVoid, !hasLocalVoid { + // Convert to parens + formatter.replaceToken(at: i, with: .endOfScope(")")) + formatter.insert(.startOfScope("("), at: i) + } + } else if formatter.options.useVoid { + // Strip parens + formatter.removeTokens(in: i + 1 ... nextIndex) + formatter.removeTokens(in: prevIndex ..< i) + } else { + // Remove Void + formatter.removeTokens(in: prevIndex + 1 ..< nextIndex) + } + } else if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + [.operator(".", .prefix), .operator(".", .infix), + .keyword("typealias")].contains(prevToken) + { + return + } else if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == + .operator(".", .infix) + { + return + } else if formatter.next(.nonSpace, after: i) == .startOfScope("(") { + if !hasLocalVoid { + formatter.removeToken(at: i) + } + } else if !formatter.options.useVoid || formatter.isArgumentToken(at: i), !hasLocalVoid { + // Convert to parens + formatter.replaceToken(at: i, with: [.startOfScope("("), .endOfScope(")")]) + } + } + formatter.forEach(.startOfScope("(")) { i, _ in + guard formatter.options.useVoid else { + return + } + guard let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .endOfScope(")") + }), let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + !formatter.isArgumentToken(at: endIndex) else { + return + } + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator("->", .infix) { + if !hasLocalVoid { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) + } + } else if prevToken == .startOfScope("<") || + (prevToken == .delimiter(",") && formatter.currentScope(at: i) == .startOfScope("<")), + !hasLocalVoid + { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) + } + // TODO: other cases + } + } +} + +private extension Formatter { + func isArgumentToken(at index: Int) -> Bool { + guard let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: index) else { + return false + } + switch nextToken { + case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), .identifier("async"): + return true + case .startOfScope("{"): + if tokens[index] == .endOfScope(")"), + let index = self.index(of: .startOfScope("("), before: index), + let nameIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, before: index, if: { + $0.isIdentifier + }), last(.nonSpaceOrCommentOrLinebreak, before: nameIndex) == .keyword("func") + { + return true + } + return false + case .keyword("in"): + if tokens[index] == .endOfScope(")"), + let index = self.index(of: .startOfScope("("), before: index) + { + return last(.nonSpaceOrCommentOrLinebreak, before: index) == .startOfScope("{") + } + return false + default: + return false + } + } + + func hasLocalVoid() -> Bool { + for (i, token) in tokens.enumerated() where token == .identifier("Void") { + if let prevToken = last(.nonSpaceOrCommentOrLinebreak, before: i) { + switch prevToken { + case .keyword("typealias"), .keyword("struct"), .keyword("class"), .keyword("enum"): + return true + default: + break + } + } + } + return false + } +} diff --git a/Sources/Rules/Wrap.swift b/Sources/Rules/Wrap.swift new file mode 100644 index 00000000..deff74cc --- /dev/null +++ b/Sources/Rules/Wrap.swift @@ -0,0 +1,68 @@ +// +// Wrap.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/17/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrap = FormatRule( + help: "Wrap lines that exceed the specified maximum width.", + options: ["maxwidth", "nowrapoperators", "assetliterals", "wrapternary"], + sharedOptions: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", "indent", + "trimwhitespace", "linebreaks", "tabwidth", "maxwidth", "smarttabs", "wrapreturntype", + "wrapconditions", "wraptypealiases", "wrapternary", "wrapeffects"] + ) { formatter in + let maxWidth = formatter.options.maxWidth + guard maxWidth > 0 else { return } + + // Wrap collections first to avoid conflict + formatter.wrapCollectionsAndArguments(completePartialWrapping: false, + wrapSingleArguments: false) + + // Wrap other line types + var currentIndex = 0 + var indent = "" + var alreadyLinewrapped = false + + func isLinewrapToken(_ token: Token?) -> Bool { + switch token { + case .delimiter?, .operator(_, .infix)?: + return true + default: + return false + } + } + + formatter.forEachToken(onlyWhereEnabled: false) { i, token in + if i < currentIndex { + return + } + if token.isLinebreak { + indent = formatter.currentIndentForLine(at: i + 1) + alreadyLinewrapped = isLinewrapToken(formatter.last(.nonSpaceOrComment, before: i)) + currentIndex = i + 1 + } else if let breakPoint = formatter.indexWhereLineShouldWrapInLine(at: i) { + if !alreadyLinewrapped { + indent += formatter.linewrapIndent(at: breakPoint) + } + alreadyLinewrapped = true + if formatter.isEnabled { + let spaceAdded = formatter.insertSpace(indent, at: breakPoint + 1) + formatter.insertLinebreak(at: breakPoint + 1) + currentIndex = breakPoint + spaceAdded + 2 + } else { + currentIndex = breakPoint + 1 + } + } else { + currentIndex = formatter.endOfLine(at: i) + } + } + + formatter.wrapCollectionsAndArguments(completePartialWrapping: true, + wrapSingleArguments: true) + } +} diff --git a/Sources/Rules/WrapArguments.swift b/Sources/Rules/WrapArguments.swift new file mode 100644 index 00000000..8f8c13cf --- /dev/null +++ b/Sources/Rules/WrapArguments.swift @@ -0,0 +1,24 @@ +// +// WrapArguments.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/23/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Normalize argument wrapping style + static let wrapArguments = FormatRule( + help: "Align wrapped function arguments or collection elements.", + orderAfter: [.wrap], + options: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", + "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects"], + sharedOptions: ["indent", "trimwhitespace", "linebreaks", + "tabwidth", "maxwidth", "smarttabs", "assetliterals", "wrapternary"] + ) { formatter in + formatter.wrapCollectionsAndArguments(completePartialWrapping: true, + wrapSingleArguments: false) + } +} diff --git a/Sources/Rules/WrapAttributes.swift b/Sources/Rules/WrapAttributes.swift new file mode 100644 index 00000000..f7b7b1d6 --- /dev/null +++ b/Sources/Rules/WrapAttributes.swift @@ -0,0 +1,113 @@ +// +// WrapAttributes.swift +// SwiftFormat +// +// Created by Nick Lockwood on 7/26/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapAttributes = FormatRule( + help: "Wrap @attributes onto a separate line, or keep them on the same line.", + options: ["funcattributes", "typeattributes", "varattributes", "storedvarattrs", "computedvarattrs", "complexattrs", "noncomplexattrs"], + sharedOptions: ["linebreaks", "maxwidth"] + ) { formatter in + formatter.forEach(.attribute) { i, _ in + // Ignore sequential attributes + guard let endIndex = formatter.endOfAttribute(at: i), + var keywordIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + after: endIndex, if: { $0.isKeyword || $0.isModifierKeyword } + ) + else { + return + } + + // Skip modifiers + while formatter.isModifier(at: keywordIndex), + let nextIndex = formatter.index(of: .keyword, after: keywordIndex) + { + keywordIndex = nextIndex + } + + // Check which `AttributeMode` option to use + var attributeMode: AttributeMode + switch formatter.tokens[keywordIndex].string { + case "func", "init", "subscript": + attributeMode = formatter.options.funcAttributes + case "class", "actor", "struct", "enum", "protocol", "extension": + attributeMode = formatter.options.typeAttributes + case "var", "let": + let storedOrComputedAttributeMode: AttributeMode + if formatter.isStoredProperty(atIntroducerIndex: keywordIndex) { + storedOrComputedAttributeMode = formatter.options.storedVarAttributes + } else { + storedOrComputedAttributeMode = formatter.options.computedVarAttributes + } + + // If the relevant `storedvarattrs` or `computedvarattrs` option hasn't been configured, + // fall back to the previous (now deprecated) `varattributes` option. + if storedOrComputedAttributeMode == .preserve { + attributeMode = formatter.options.varAttributes + } else { + attributeMode = storedOrComputedAttributeMode + } + default: + return + } + + // If the complexAttributes option is configured, it takes precedence over other options + // if this is a complex attributes with arguments. + let attributeName = formatter.tokens[i].string + let isComplexAttribute = formatter.isComplexAttribute(at: i) + && !formatter.options.complexAttributesExceptions.contains(attributeName) + + if isComplexAttribute, formatter.options.complexAttributes != .preserve { + attributeMode = formatter.options.complexAttributes + } + + // Apply the `AttributeMode` + switch attributeMode { + case .preserve: + return + case .prevLine: + // Make sure there's a newline immediately following the attribute + if let nextIndex = formatter.index(of: .nonSpaceOrComment, after: endIndex), + formatter.token(at: nextIndex)?.isLinebreak != true + { + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) + formatter.insertLinebreak(at: nextIndex) + // Remove any trailing whitespace left on the line with the attributes + if let prevToken = formatter.token(at: nextIndex - 1), prevToken.isSpace { + formatter.removeToken(at: nextIndex - 1) + } + } + case .sameLine: + // Make sure there isn't a newline immediately following the attribute + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), + formatter.tokens[(endIndex + 1) ..< nextIndex].contains(where: { $0.isLinebreak }) + { + // If unwrapping the attribute causes the line to exceed the max width, + // leave it as-is. The existing formatting is likely better than how + // this would be re-unwrapped by the wrap rule. + let startOfLine = formatter.startOfLine(at: i) + let endOfLine = formatter.endOfLine(at: i) + let startOfNextLine = formatter.startOfLine(at: nextIndex, excludingIndent: true) + let endOfNextLine = formatter.endOfLine(at: nextIndex) + let combinedLine = formatter.tokens[startOfLine ... endOfLine].map { $0.string }.joined() + + formatter.tokens[startOfNextLine ..< endOfNextLine].map { $0.string }.joined() + + if formatter.options.maxWidth > 0, combinedLine.count > formatter.options.maxWidth { + return + } + + // Replace the newline with a space so the attribute doesn't + // merge with the next token. + formatter.replaceTokens(in: (endIndex + 1) ..< nextIndex, with: .space(" ")) + } + } + } + } +} diff --git a/Sources/Rules/WrapConditionalBodies.swift b/Sources/Rules/WrapConditionalBodies.swift new file mode 100644 index 00000000..857308ef --- /dev/null +++ b/Sources/Rules/WrapConditionalBodies.swift @@ -0,0 +1,24 @@ +// +// WrapConditionalBodies.swift +// SwiftFormat +// +// Created by Nick Lockwood on 11/6/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapConditionalBodies = FormatRule( + help: "Wrap the bodies of inline conditional statements onto a new line.", + disabledByDefault: true, + sharedOptions: ["linebreaks", "indent"] + ) { formatter in + formatter.forEachToken(where: { [.keyword("if"), .keyword("else")].contains($0) }) { i, _ in + guard let startIndex = formatter.index(of: .startOfScope("{"), after: i) else { + return formatter.fatalError("Expected {", at: i) + } + formatter.wrapStatementBody(at: startIndex) + } + } +} diff --git a/Sources/Rules/WrapEnumCases.swift b/Sources/Rules/WrapEnumCases.swift new file mode 100644 index 00000000..a825b718 --- /dev/null +++ b/Sources/Rules/WrapEnumCases.swift @@ -0,0 +1,70 @@ +// +// WrapEnumCases.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/28/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Formats enum cases declaration into one case per line + static let wrapEnumCases = FormatRule( + help: "Rewrite comma-delimited enum cases to one case per line.", + disabledByDefault: true, + options: ["wrapenumcases"], + sharedOptions: ["linebreaks"] + ) { formatter in + + func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { + guard let firstIndex = caseRangeGroup.first?.value.lowerBound, + let scopeStart = formatter.startOfScope(at: firstIndex), + formatter.tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) + else { + // Don't wrap if first case is on same line as opening `{` + return false + } + return formatter.options.wrapEnumCases == .always || caseRangeGroup.contains(where: { + formatter.tokens[$0.value].contains(where: { + [.startOfScope("("), .operator("=", .infix)].contains($0) + }) + }) + } + + formatter.parseEnumCaseRanges() + .filter(shouldWrapCaseRangeGroup) + .flatMap { $0 } + .filter { $0.endOfCaseRangeToken == .delimiter(",") } + .reversed() + .forEach { enumCase in + guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: enumCase.value.upperBound) else { + return + } + let caseIndex = formatter.lastIndex(of: .keyword("case"), in: 0 ..< enumCase.value.lowerBound) + let indent = formatter.currentIndentForLine(at: caseIndex ?? enumCase.value.lowerBound) + + if formatter.tokens[nextNonSpaceIndex] == .startOfScope("//") { + formatter.removeToken(at: enumCase.value.upperBound) + if formatter.token(at: enumCase.value.upperBound)?.isSpace == true, + formatter.token(at: enumCase.value.upperBound - 1)?.isSpace == true + { + formatter.removeToken(at: enumCase.value.upperBound - 1) + } + nextNonSpaceIndex = formatter.index(of: .linebreak, after: enumCase.value.upperBound) ?? nextNonSpaceIndex + } else { + formatter.removeTokens(in: enumCase.value.upperBound ..< nextNonSpaceIndex) + nextNonSpaceIndex = enumCase.value.upperBound + } + + if !formatter.tokens[nextNonSpaceIndex].isLinebreak { + formatter.insertLinebreak(at: nextNonSpaceIndex) + } + + let offset = indent.isEmpty ? 0 : 1 + formatter.insertSpace(indent, at: nextNonSpaceIndex + 1) + formatter.insert([.keyword("case")], at: nextNonSpaceIndex + 1 + offset) + formatter.insertSpace(" ", at: nextNonSpaceIndex + 2 + offset) + } + } +} diff --git a/Sources/Rules/WrapLoopBodies.swift b/Sources/Rules/WrapLoopBodies.swift new file mode 100644 index 00000000..cbfa8d18 --- /dev/null +++ b/Sources/Rules/WrapLoopBodies.swift @@ -0,0 +1,29 @@ +// +// WrapLoopBodies.swift +// SwiftFormat +// +// Created by Nick Lockwood on 1/3/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapLoopBodies = FormatRule( + help: "Wrap the bodies of inline loop statements onto a new line.", + orderAfter: [.preferForLoop], + sharedOptions: ["linebreaks", "indent"] + ) { formatter in + formatter.forEachToken(where: { [ + .keyword("for"), + .keyword("while"), + .keyword("repeat"), + ].contains($0) }) { i, token in + if let startIndex = formatter.index(of: .startOfScope("{"), after: i) { + formatter.wrapStatementBody(at: startIndex) + } else if token == .keyword("for") { + return formatter.fatalError("Expected {", at: i) + } + } + } +} diff --git a/Sources/Rules/WrapMultilineConditionalAssignment.swift b/Sources/Rules/WrapMultilineConditionalAssignment.swift new file mode 100644 index 00000000..64b39c1e --- /dev/null +++ b/Sources/Rules/WrapMultilineConditionalAssignment.swift @@ -0,0 +1,60 @@ +// +// WrapMultilineConditionalAssignment.swift +// SwiftFormat +// +// Created by Cal Stephens on 11/18/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapMultilineConditionalAssignment = FormatRule( + help: "Wrap multiline conditional assignment expressions after the assignment operator.", + disabledByDefault: true, + orderAfter: [.conditionalAssignment], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.keyword) { startOfCondition, keywordToken in + guard [.keyword("if"), .keyword("switch")].contains(keywordToken), + let assignmentIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfCondition), + formatter.tokens[assignmentIndex] == .operator("=", .infix), + let endOfPropertyDefinition = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex) + else { return } + + // Verify the RHS of the assignment is an if/switch expression + guard let startOfConditionalExpression = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: assignmentIndex), + ["if", "switch"].contains(formatter.tokens[startOfConditionalExpression].string), + let conditionalBranches = formatter.conditionalBranches(at: startOfConditionalExpression), + let lastBranch = conditionalBranches.last + else { return } + + // If the entire expression is on a single line, we leave the formatting as-is + guard !formatter.onSameLine(startOfConditionalExpression, lastBranch.endOfBranch) else { + return + } + + // The `=` should be on the same line as the rest of the property + if !formatter.onSameLine(endOfPropertyDefinition, assignmentIndex), + formatter.last(.nonSpaceOrComment, before: assignmentIndex)?.isLinebreak == true, + let previousToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex), + formatter.onSameLine(endOfPropertyDefinition, previousToken) + { + // Move the assignment operator to follow the previous token. + // Also remove any trailing space after the previous position + // of the assignment operator. + if formatter.tokens[assignmentIndex + 1].isSpaceOrLinebreak { + formatter.removeToken(at: assignmentIndex + 1) + } + + formatter.removeToken(at: assignmentIndex) + formatter.insert([.space(" "), .operator("=", .infix)], at: previousToken + 1) + } + + // And there should be a line break between the `=` and the `if` / `switch` keyword + else if !formatter.tokens[(assignmentIndex + 1) ..< startOfConditionalExpression].contains(where: \.isLinebreak) { + formatter.insertLinebreak(at: startOfConditionalExpression - 1) + } + } + } +} diff --git a/Sources/Rules/WrapMultilineStatementBraces.swift b/Sources/Rules/WrapMultilineStatementBraces.swift new file mode 100644 index 00000000..781e8c1f --- /dev/null +++ b/Sources/Rules/WrapMultilineStatementBraces.swift @@ -0,0 +1,35 @@ +// +// WrapMultilineStatementBraces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/16/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapMultilineStatementBraces = FormatRule( + help: "Wrap the opening brace of multiline statements.", + orderAfter: [.braces, .indent, .wrapArguments], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard formatter.last(.nonSpaceOrComment, before: i)?.isLinebreak == false, + formatter.shouldWrapMultilineStatementBrace(at: i), + let endIndex = formatter.endOfScope(at: i) + else { + return + } + let indent = formatter.currentIndentForLine(at: endIndex) + // Insert linebreak + formatter.insertLinebreak(at: i) + // Align the opening brace with closing brace + formatter.insertSpace(indent, at: i + 1) + // Clean up trailing space on the previous line + if case .space? = formatter.token(at: i - 1) { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/WrapSingleLineComments.swift b/Sources/Rules/WrapSingleLineComments.swift new file mode 100644 index 00000000..2f6f028a --- /dev/null +++ b/Sources/Rules/WrapSingleLineComments.swift @@ -0,0 +1,65 @@ +// +// WrapSingleLineComments.swift +// SwiftFormat +// +// Created by Max Desiatov on 8/11/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Wrap single-line comments that exceed given `FormatOptions.maxWidth` setting. + static let wrapSingleLineComments = FormatRule( + help: "Wrap single line `//` comments that exceed the specified `--maxwidth`.", + sharedOptions: ["maxwidth", "indent", "tabwidth", "assetliterals", "linebreaks"] + ) { formatter in + let delimiterLength = "//".count + var maxWidth = formatter.options.maxWidth + guard maxWidth > 3 else { + return + } + + formatter.forEach(.startOfScope("//")) { i, _ in + let startOfLine = formatter.startOfLine(at: i) + let endOfLine = formatter.endOfLine(at: i) + guard formatter.lineLength(from: startOfLine, upTo: endOfLine) > maxWidth else { + return + } + + guard let startIndex = formatter.index(of: .nonSpace, after: i), + case var .commentBody(comment) = formatter.tokens[startIndex], + !comment.isCommentDirective + else { + return + } + + var words = comment.components(separatedBy: " ") + comment = words.removeFirst() + let commentPrefix = comment == "/" ? "/ " : comment.hasPrefix("/") ? "/" : "" + let prefixLength = formatter.lineLength(upTo: startIndex) + var length = prefixLength + comment.count + while length <= maxWidth, let next = words.first, + length + next.count < maxWidth || + // Don't wrap if next word won't fit on a line by itself anyway + prefixLength + commentPrefix.count + next.count > maxWidth + { + comment += " \(next)" + length += next.count + 1 + words.removeFirst() + } + if words.isEmpty || comment == commentPrefix { + return + } + var prefix = formatter.tokens[i ..< startIndex] + if let token = formatter.token(at: startOfLine), case .space = token { + prefix.insert(token, at: prefix.startIndex) + } + formatter.replaceTokens(in: startIndex ..< endOfLine, with: [ + .commentBody(comment), formatter.linebreakToken(for: startIndex), + ] + prefix + [ + .commentBody(commentPrefix + words.joined(separator: " ")), + ]) + } + } +} diff --git a/Sources/Rules/WrapSwitchCases.swift b/Sources/Rules/WrapSwitchCases.swift new file mode 100644 index 00000000..08f6b667 --- /dev/null +++ b/Sources/Rules/WrapSwitchCases.swift @@ -0,0 +1,36 @@ +// +// WrapSwitchCases.swift +// SwiftFormat +// +// Created by Nick Lockwood on 8/28/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Writes one switch case per line + static let wrapSwitchCases = FormatRule( + help: "Wrap comma-delimited switch cases onto multiple lines.", + disabledByDefault: true, + sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"] + ) { formatter in + formatter.forEach(.endOfScope("case")) { i, _ in + guard var endIndex = formatter.index(of: .startOfScope(":"), after: i) else { return } + let lineStart = formatter.startOfLine(at: i) + let indent = formatter.spaceEquivalentToTokens(from: lineStart, upTo: i + 2) + + var startIndex = i + while let commaIndex = formatter.index(of: .delimiter(","), in: startIndex + 1 ..< endIndex), + let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: commaIndex) + { + if formatter.index(of: .linebreak, in: commaIndex ..< nextIndex) == nil { + formatter.insertLinebreak(at: commaIndex + 1) + let delta = formatter.insertSpace(indent, at: commaIndex + 2) + endIndex += 1 + delta + } + startIndex = commaIndex + } + } + } +} diff --git a/Sources/Rules/YodaConditions.swift b/Sources/Rules/YodaConditions.swift new file mode 100644 index 00000000..603a3dac --- /dev/null +++ b/Sources/Rules/YodaConditions.swift @@ -0,0 +1,139 @@ +// +// YodaConditions.swift +// SwiftFormat +// +// Created by Nick Lockwood on 3/9/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Reorders "yoda conditions" where constant is placed on lhs of a comparison + static let yodaConditions = FormatRule( + help: "Prefer constant values to be on the right-hand-side of expressions.", + options: ["yodaswap"] + ) { formatter in + func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { + var index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: range) + while var i = index { + switch formatter.tokens[i] { + case .startOfScope where isConstant(at: i): + guard let endIndex = formatter.index(of: .endOfScope, after: i) else { + return false + } + i = endIndex + fallthrough + case _ where isConstant(at: i), .delimiter(","), .delimiter(":"): + index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< range.upperBound) + case .identifier: + guard let nextIndex = + formatter.index(of: .nonSpaceOrComment, in: i + 1 ..< range.upperBound), + formatter.tokens[nextIndex] == .delimiter(":") + else { + return false + } + // Identifier is a label + index = nextIndex + default: + return false + } + } + return true + } + func isConstant(at index: Int) -> Bool { + var index = index + while case .operator(_, .postfix) = formatter.tokens[index] { + index -= 1 + } + guard let token = formatter.token(at: index) else { + return false + } + switch token { + case .number, .identifier("true"), .identifier("false"), .identifier("nil"): + return true + case .endOfScope("]"), .endOfScope(")"): + guard let startIndex = formatter.index(of: .startOfScope, before: index), + !formatter.isSubscriptOrFunctionCall(at: startIndex) + else { + return false + } + return valuesInRangeAreConstant(startIndex + 1 ..< index) + case .startOfScope("["), .startOfScope("("): + guard !formatter.isSubscriptOrFunctionCall(at: index), + let endIndex = formatter.index(of: .endOfScope, after: index) + else { + return false + } + return valuesInRangeAreConstant(index + 1 ..< endIndex) + case .startOfScope, .endOfScope: + // TODO: what if string contains interpolation? + return token.isStringDelimiter + case _ where formatter.options.yodaSwap == .literalsOnly: + // Don't treat .members as constant + return false + case .operator(".", .prefix) where formatter.token(at: index + 1)?.isIdentifier == true, + .identifier where formatter.token(at: index - 1) == .operator(".", .prefix) && + formatter.token(at: index - 2) != .operator("\\", .prefix): + return true + default: + return false + } + } + func isOperator(at index: Int?) -> Bool { + guard let index = index else { + return false + } + switch formatter.tokens[index] { + // Discount operators with higher precedence than == + case .operator("=", .infix), + .operator("&&", .infix), .operator("||", .infix), + .operator("?", .infix), .operator(":", .infix): + return false + case .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + return false + } + } + func startOfValue(at index: Int) -> Int? { + var index = index + while case .operator(_, .postfix)? = formatter.token(at: index) { + index -= 1 + } + if case .endOfScope? = formatter.token(at: index) { + guard let i = formatter.index(of: .startOfScope, before: index) else { + return nil + } + index = i + } + while case .operator(_, .prefix)? = formatter.token(at: index - 1) { + index -= 1 + } + return index + } + + formatter.forEachToken { i, token in + guard case let .operator(op, .infix) = token, + let opIndex = ["==", "!=", "<", "<=", ">", ">="].firstIndex(of: op), + let prevIndex = formatter.index(of: .nonSpace, before: i), + isConstant(at: prevIndex), let startIndex = startOfValue(at: prevIndex), + !isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex)), + let nextIndex = formatter.index(of: .nonSpace, after: i), !isConstant(at: nextIndex) || + isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextIndex)), + let endIndex = formatter.endOfExpression(at: nextIndex, upTo: [ + .operator("&&", .infix), .operator("||", .infix), + .operator("?", .infix), .operator(":", .infix), + ]) + else { + return + } + let inverseOp = ["==", "!=", ">", ">=", "<", "<="][opIndex] + let expression = Array(formatter.tokens[nextIndex ... endIndex]) + let constant = Array(formatter.tokens[startIndex ... prevIndex]) + formatter.replaceTokens(in: nextIndex ... endIndex, with: constant) + formatter.replaceToken(at: i, with: .operator(inverseOp, .infix)) + formatter.replaceTokens(in: startIndex ... prevIndex, with: expression) + } + } +} diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index b3ee6646..4e0ac23f 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -180,7 +180,7 @@ public func enumerateFiles(withInputURL inputURL: URL, let fileOptions = options.fileOptions ?? .default if resourceValues.isRegularFile == true { if fileOptions.supportedFileExtensions.contains(inputURL.pathExtension) { - let fileHeaderRuleEnabled = options.rules?.contains(FormatRules.fileHeader.name) ?? false + let fileHeaderRuleEnabled = options.rules?.contains(FormatRule.fileHeader.name) ?? false let shouldGetGitInfo = fileHeaderRuleEnabled && options.formatOptions?.fileHeader.needsGitInfo == true @@ -501,7 +501,7 @@ private func applyRules( inferFormatOptions(sharedOptions, from: tokens, into: &options) // Check if required FileInfo is available - if rules.contains(FormatRules.fileHeader) { + if rules.contains(.fileHeader) { let header = options.fileHeader let fileInfo = options.fileInfo diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index c6821fbc..9cd85242 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -134,7 +134,7 @@ public enum OperatorType { public typealias OriginalLine = Int /// All token types -public enum Token: Equatable { +public enum Token: Hashable { case number(String, NumberType) case linebreak(String, OriginalLine) case startOfScope(String) diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index 5a349faa..d78dfa31 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 01045A9E2119A37F00D2BE3D /* ArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */; }; 01045A9F2119D30D00D2BE3D /* Inference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01045A90211988F100D2BE3D /* Inference.swift */; }; 01045AA0211A1EE300D2BE3D /* Arguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01045A982119979400D2BE3D /* Arguments.swift */; }; - 011676A22707D312001CCDCE /* RulesTests+General.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011676A12707D312001CCDCE /* RulesTests+General.swift */; }; 011A53EB21FFAA4200DD9268 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011A53E921FFAA3A00DD9268 /* VersionTests.swift */; }; 01426E4E23AA29B100E7D871 /* ParsingHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01426E4D23AA29B100E7D871 /* ParsingHelpersTests.swift */; }; 0142C77023C3FB6D005D5832 /* LintFileCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0142C76F23C3FB6D005D5832 /* LintFileCommand.swift */; }; @@ -35,13 +34,13 @@ 018E82751D62E730008CA0F8 /* TokenizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018E82741D62E730008CA0F8 /* TokenizerTests.swift */; }; 01A0EAA81D5DB4CF00A0A8E3 /* SwiftFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01A0EAAF1D5DB4D000A0A8E3 /* SwiftFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01A0EAA41D5DB4CF00A0A8E3 /* SwiftFormat.framework */; }; - 01A0EAB41D5DB4D000A0A8E3 /* RulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */; }; - 01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + 01A0EAB41D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */; }; + 01A0EAC11D5DB4F700A0A8E3 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 01A0EAC21D5DB4F700A0A8E3 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; 01A0EACD1D5DB5F500A0A8E3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EACC1D5DB5F500A0A8E3 /* main.swift */; }; 01A0EAD41D5DC08A00A0A8E3 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; - 01A0EAD51D5DC08A00A0A8E3 /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + 01A0EAD51D5DC08A00A0A8E3 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 01A0EAD61D5DC08A00A0A8E3 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 01A8320724EC7F7600A9D0EB /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; 01A8320824EC7F7700A9D0EB /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; @@ -61,18 +60,8 @@ 01BBD85C21DAA2A700457380 /* Globs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BBD85821DAA2A000457380 /* Globs.swift */; }; 01BBD85E21DAA30700457380 /* GlobsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BBD85D21DAA30700457380 /* GlobsTests.swift */; }; 01BEC5772236E1A700D0DD83 /* MetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */; }; - 01C209AF2502CD3C00E728A2 /* RulesTests+Spacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */; }; - 01C209B12502CEF700E728A2 /* RulesTests+Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B02502CEF700E728A2 /* RulesTests+Linebreaks.swift */; }; - 01C209B32502CF8300E728A2 /* RulesTests+Indentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B22502CF8300E728A2 /* RulesTests+Indentation.swift */; }; - 01C209B52502D27800E728A2 /* RulesTests+Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B42502D27800E728A2 /* RulesTests+Braces.swift */; }; - 01C209B72502D2FD00E728A2 /* RulesTests+Parens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B62502D2FD00E728A2 /* RulesTests+Parens.swift */; }; - 01C209B92502D3C500E728A2 /* RulesTests+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */; }; - 01C209BB2502D62000E728A2 /* RulesTests+Organization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209BA2502D62000E728A2 /* RulesTests+Organization.swift */; }; - 01C209BD2502D71F00E728A2 /* RulesTests+Redundancy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209BC2502D71F00E728A2 /* RulesTests+Redundancy.swift */; }; 01C4D3292BB518D400BDF1AF /* ZRegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */; }; 01D3B28624E9C9C700888DE0 /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; - 01EF830F25616089003F6F2D /* RulesTests+Syntax.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */; }; - 01EFC8B729CF2B5100222029 /* RulesTests+Hoisting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EFC8B629CF2B5100222029 /* RulesTests+Hoisting.swift */; }; 01F17E821E25870700DCD359 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F17E811E25870700DCD359 /* CommandLine.swift */; }; 01F17E831E25870700DCD359 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F17E811E25870700DCD359 /* CommandLine.swift */; }; 01F17E851E258A4900DCD359 /* CommandLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F17E841E258A4900DCD359 /* CommandLineTests.swift */; }; @@ -84,13 +73,564 @@ 08180DD02C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 08180DD12C4EB68000FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; + 2E2BAB8C2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BAB8D2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BAB8E2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BAB8F2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BABFF2C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC002C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC012C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC022C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC032C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC042C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC052C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC062C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC072C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC082C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC092C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC0A2C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC0B2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0C2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0D2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0E2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0F2C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC102C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC112C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC122C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC132C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC142C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC152C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC162C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC172C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC182C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC192C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC1A2C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC1B2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1C2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1D2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1E2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1F2C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC202C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC212C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC222C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC232C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC242C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC252C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC262C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC272C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC282C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC292C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC2A2C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC2B2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2C2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2D2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2E2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2F2C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC302C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC312C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC322C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC332C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC342C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC352C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC362C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC372C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC382C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC392C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC3A2C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC3B2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3C2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3D2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3E2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3F2C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC402C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC412C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC422C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC432C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC442C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC452C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC462C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC472C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC482C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC492C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC4A2C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC4B2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4C2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4D2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4E2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4F2C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC502C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC512C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC522C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC532C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC542C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC552C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC562C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC572C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC582C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC592C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC5A2C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC5B2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5C2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5D2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5E2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5F2C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC602C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC612C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC622C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC632C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC642C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC652C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC662C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC672C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC682C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC692C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC6A2C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC6B2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6C2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6D2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6E2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6F2C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC702C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC712C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC722C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC732C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC742C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC752C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC762C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC772C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC782C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC792C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC7A2C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC7B2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7C2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7D2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7E2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7F2C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC802C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC812C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC822C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC832C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC842C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC852C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC862C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC872C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC882C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC892C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC8A2C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC8B2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8C2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8D2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8E2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8F2C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC902C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC912C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC922C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC932C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC942C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC952C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC962C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC972C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC982C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC992C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC9A2C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC9B2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9C2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9D2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9E2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9F2C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA02C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA12C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA22C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA32C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA42C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA52C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA62C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA72C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACA82C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACA92C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACAA2C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACAB2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAC2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAD2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAE2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAF2C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB02C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB12C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB22C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB32C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB42C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB52C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB62C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB72C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACB82C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACB92C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACBA2C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACBB2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBC2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBD2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBE2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBF2C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC02C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC12C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC22C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC32C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC42C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC52C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC62C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC72C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACC82C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACC92C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACCA2C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACCB2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCC2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCD2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCE2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCF2C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD02C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD12C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD22C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD32C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD42C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD52C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD72C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACD82C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACD92C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACDA2C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACDB2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDC2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDD2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDE2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDF2C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE02C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE12C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE22C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE32C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE42C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE52C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE62C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE72C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACE82C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACE92C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACEA2C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACEB2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACEC2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACED2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACEE2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACEF2C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF02C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF12C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF22C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF32C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF42C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF52C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF62C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF72C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACF82C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACF92C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACFA2C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACFB2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFC2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFD2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFE2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFF2C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD002C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD012C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD022C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD032C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD042C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD052C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD062C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD072C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD082C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD092C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD0A2C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD0B2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0C2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0D2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0E2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0F2C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD102C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD112C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD122C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD132C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD142C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD152C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD162C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD172C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD182C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD192C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD1A2C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD1B2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1C2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1D2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1E2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1F2C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD202C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD212C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD222C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD232C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD242C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD252C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD262C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD272C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD282C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD292C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD2A2C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD2B2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2C2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2D2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2E2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2F2C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD302C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD312C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD322C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD332C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD342C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD352C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD362C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD372C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD382C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD392C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD3A2C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD3B2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3C2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3D2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3E2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3F2C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD402C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD412C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD422C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD432C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD442C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD452C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD462C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD472C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD482C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD492C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD4A2C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD4B2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4C2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4D2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4E2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4F2C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD502C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD512C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD522C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD532C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD542C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD552C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD562C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD572C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD582C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD592C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD5A2C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD5B2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5C2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5D2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5E2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5F2C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD602C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD612C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD622C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD632C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD642C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD652C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD662C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD672C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD682C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD692C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD6A2C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD6B2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6C2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6D2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6E2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6F2C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD702C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD712C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD722C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD732C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD742C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD752C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD762C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD772C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD782C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD792C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD7A2C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD7B2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7C2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7D2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7E2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7F2C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD802C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD812C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD822C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD832C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD842C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD852C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD862C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD872C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD882C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD892C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD8A2C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD8B2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8C2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8D2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8E2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8F2C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD902C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD912C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD922C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD932C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD942C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD952C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD962C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD972C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD982C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD992C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD9A2C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD9B2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9C2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9D2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9E2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9F2C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA02C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA12C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA22C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA32C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA42C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA52C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA62C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA72C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADA82C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADA92C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADAA2C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADAB2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAC2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAD2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAE2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAF2C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB02C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB12C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB22C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB32C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; + 2E2BADB42C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; + 2E2BADB52C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; + 2E2BADB62C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; }; + 2E8DE6F82C57FEB30032BF25 /* RedundantClosureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE68D2C57FEB30032BF25 /* RedundantClosureTests.swift */; }; + 2E8DE6F92C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE68E2C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift */; }; + 2E8DE6FA2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE68F2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift */; }; + 2E8DE6FB2C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6902C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift */; }; + 2E8DE6FC2C57FEB30032BF25 /* WrapArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6912C57FEB30032BF25 /* WrapArgumentsTests.swift */; }; + 2E8DE6FD2C57FEB30032BF25 /* RedundantSelfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6922C57FEB30032BF25 /* RedundantSelfTests.swift */; }; + 2E8DE6FE2C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6932C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift */; }; + 2E8DE6FF2C57FEB30032BF25 /* DuplicateImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */; }; + 2E8DE7002C57FEB30032BF25 /* TodosTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */; }; + 2E8DE7012C57FEB30032BF25 /* FileHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */; }; + 2E8DE7022C57FEB30032BF25 /* EnumNamespacesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */; }; + 2E8DE7032C57FEB30032BF25 /* PreferForLoopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6982C57FEB30032BF25 /* PreferForLoopTests.swift */; }; + 2E8DE7042C57FEB30032BF25 /* ExtensionAccessControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */; }; + 2E8DE7052C57FEB30032BF25 /* TrailingCommasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69A2C57FEB30032BF25 /* TrailingCommasTests.swift */; }; + 2E8DE7062C57FEB30032BF25 /* WrapLoopBodiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69B2C57FEB30032BF25 /* WrapLoopBodiesTests.swift */; }; + 2E8DE7072C57FEB30032BF25 /* StrongifiedSelfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */; }; + 2E8DE7082C57FEB30032BF25 /* RedundantPropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69D2C57FEB30032BF25 /* RedundantPropertyTests.swift */; }; + 2E8DE7092C57FEB30032BF25 /* OpaqueGenericParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69E2C57FEB30032BF25 /* OpaqueGenericParametersTests.swift */; }; + 2E8DE70A2C57FEB30032BF25 /* SpaceInsideBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69F2C57FEB30032BF25 /* SpaceInsideBracesTests.swift */; }; + 2E8DE70B2C57FEB30032BF25 /* ModifierOrderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A02C57FEB30032BF25 /* ModifierOrderTests.swift */; }; + 2E8DE70C2C57FEB30032BF25 /* WrapAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A12C57FEB30032BF25 /* WrapAttributesTests.swift */; }; + 2E8DE70D2C57FEB30032BF25 /* RedundantObjcTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A22C57FEB30032BF25 /* RedundantObjcTests.swift */; }; + 2E8DE70E2C57FEB30032BF25 /* HoistPatternLetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A32C57FEB30032BF25 /* HoistPatternLetTests.swift */; }; + 2E8DE70F2C57FEB30032BF25 /* WrapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A42C57FEB30032BF25 /* WrapTests.swift */; }; + 2E8DE7102C57FEB30032BF25 /* SpaceInsideGenericsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */; }; + 2E8DE7112C57FEB30032BF25 /* RedundantStaticSelfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A62C57FEB30032BF25 /* RedundantStaticSelfTests.swift */; }; + 2E8DE7122C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A72C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift */; }; + 2E8DE7132C57FEB30032BF25 /* RedundantInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A82C57FEB30032BF25 /* RedundantInitTests.swift */; }; + 2E8DE7142C57FEB30032BF25 /* RedundantRawValuesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A92C57FEB30032BF25 /* RedundantRawValuesTests.swift */; }; + 2E8DE7152C57FEB30032BF25 /* SortImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AA2C57FEB30032BF25 /* SortImportsTests.swift */; }; + 2E8DE7162C57FEB30032BF25 /* TrailingSpaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AB2C57FEB30032BF25 /* TrailingSpaceTests.swift */; }; + 2E8DE7172C57FEB30032BF25 /* YodaConditionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AC2C57FEB30032BF25 /* YodaConditionsTests.swift */; }; + 2E8DE7182C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AD2C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift */; }; + 2E8DE7192C57FEB30032BF25 /* UnusedArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AE2C57FEB30032BF25 /* UnusedArgumentsTests.swift */; }; + 2E8DE71A2C57FEB30032BF25 /* GenericExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AF2C57FEB30032BF25 /* GenericExtensionsTests.swift */; }; + 2E8DE71B2C57FEB30032BF25 /* NumberFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B02C57FEB30032BF25 /* NumberFormattingTests.swift */; }; + 2E8DE71C2C57FEB30032BF25 /* RedundantTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B12C57FEB30032BF25 /* RedundantTypeTests.swift */; }; + 2E8DE71D2C57FEB30032BF25 /* TypeSugarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B22C57FEB30032BF25 /* TypeSugarTests.swift */; }; + 2E8DE71E2C57FEB30032BF25 /* SpaceAroundBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B32C57FEB30032BF25 /* SpaceAroundBracesTests.swift */; }; + 2E8DE71F2C57FEB30032BF25 /* SortTypealiasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B42C57FEB30032BF25 /* SortTypealiasesTests.swift */; }; + 2E8DE7202C57FEB30032BF25 /* SortSwitchCasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B52C57FEB30032BF25 /* SortSwitchCasesTests.swift */; }; + 2E8DE7212C57FEB30032BF25 /* EmptyBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */; }; + 2E8DE7222C57FEB30032BF25 /* SortDeclarationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B72C57FEB30032BF25 /* SortDeclarationsTests.swift */; }; + 2E8DE7232C57FEB30032BF25 /* BlockCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B82C57FEB30032BF25 /* BlockCommentsTests.swift */; }; + 2E8DE7242C57FEB30032BF25 /* StrongOutletsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */; }; + 2E8DE7252C57FEB30032BF25 /* BracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BA2C57FEB30032BF25 /* BracesTests.swift */; }; + 2E8DE7262C57FEB30032BF25 /* AnyObjectProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BB2C57FEB30032BF25 /* AnyObjectProtocolTests.swift */; }; + 2E8DE7272C57FEB30032BF25 /* RedundantBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BC2C57FEB30032BF25 /* RedundantBreakTests.swift */; }; + 2E8DE7282C57FEB30032BF25 /* RedundantNilInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BD2C57FEB30032BF25 /* RedundantNilInitTests.swift */; }; + 2E8DE7292C57FEB30032BF25 /* RedundantParensTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BE2C57FEB30032BF25 /* RedundantParensTests.swift */; }; + 2E8DE72A2C57FEB30032BF25 /* PreferKeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BF2C57FEB30032BF25 /* PreferKeyPathTests.swift */; }; + 2E8DE72B2C57FEB30032BF25 /* SpaceAroundParensTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C02C57FEB30032BF25 /* SpaceAroundParensTests.swift */; }; + 2E8DE72C2C57FEB30032BF25 /* RedundantLetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C12C57FEB30032BF25 /* RedundantLetTests.swift */; }; + 2E8DE72D2C57FEB30032BF25 /* VoidTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C22C57FEB30032BF25 /* VoidTests.swift */; }; + 2E8DE72E2C57FEB30032BF25 /* IndentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C32C57FEB30032BF25 /* IndentTests.swift */; }; + 2E8DE72F2C57FEB30032BF25 /* RedundantBackticksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C42C57FEB30032BF25 /* RedundantBackticksTests.swift */; }; + 2E8DE7302C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C52C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift */; }; + 2E8DE7312C57FEB30032BF25 /* HoistAwaitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C62C57FEB30032BF25 /* HoistAwaitTests.swift */; }; + 2E8DE7322C57FEB30032BF25 /* RedundantLetErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C72C57FEB30032BF25 /* RedundantLetErrorTests.swift */; }; + 2E8DE7332C57FEB30032BF25 /* SpaceAroundCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C82C57FEB30032BF25 /* SpaceAroundCommentsTests.swift */; }; + 2E8DE7342C57FEB30032BF25 /* PropertyTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C92C57FEB30032BF25 /* PropertyTypeTests.swift */; }; + 2E8DE7352C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CA2C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift */; }; + 2E8DE7362C57FEB30032BF25 /* SemicolonsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CB2C57FEB30032BF25 /* SemicolonsTests.swift */; }; + 2E8DE7372C57FEB30032BF25 /* RedundantPatternTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CC2C57FEB30032BF25 /* RedundantPatternTests.swift */; }; + 2E8DE7382C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CD2C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift */; }; + 2E8DE7392C57FEB30032BF25 /* RedundantInternalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CE2C57FEB30032BF25 /* RedundantInternalTests.swift */; }; + 2E8DE73A2C57FEB30032BF25 /* IsEmptyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CF2C57FEB30032BF25 /* IsEmptyTests.swift */; }; + 2E8DE73B2C57FEB30032BF25 /* AcronymsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D02C57FEB30032BF25 /* AcronymsTests.swift */; }; + 2E8DE73C2C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */; }; + 2E8DE73D2C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D22C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift */; }; + 2E8DE73E2C57FEB30032BF25 /* WrapEnumCasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D32C57FEB30032BF25 /* WrapEnumCasesTests.swift */; }; + 2E8DE73F2C57FEB30032BF25 /* NoExplicitOwnershipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D42C57FEB30032BF25 /* NoExplicitOwnershipTests.swift */; }; + 2E8DE7402C57FEB30032BF25 /* HoistTryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D52C57FEB30032BF25 /* HoistTryTests.swift */; }; + 2E8DE7412C57FEB30032BF25 /* RedundantOptionalBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D62C57FEB30032BF25 /* RedundantOptionalBindingTests.swift */; }; + 2E8DE7422C57FEB30032BF25 /* ConsecutiveSpacesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */; }; + 2E8DE7432C57FEB30032BF25 /* SpaceAroundBracketsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D82C57FEB30032BF25 /* SpaceAroundBracketsTests.swift */; }; + 2E8DE7442C57FEB30032BF25 /* TrailingClosuresTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D92C57FEB30032BF25 /* TrailingClosuresTests.swift */; }; + 2E8DE7452C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DA2C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift */; }; + 2E8DE7462C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DB2C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift */; }; + 2E8DE7472C57FEB30032BF25 /* ConditionalAssignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DC2C57FEB30032BF25 /* ConditionalAssignmentTests.swift */; }; + 2E8DE7482C57FEB30032BF25 /* RedundantGetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DD2C57FEB30032BF25 /* RedundantGetTests.swift */; }; + 2E8DE7492C57FEB30032BF25 /* HeaderFileNameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DE2C57FEB30032BF25 /* HeaderFileNameTests.swift */; }; + 2E8DE74A2C57FEB30032BF25 /* RedundantExtensionACLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DF2C57FEB30032BF25 /* RedundantExtensionACLTests.swift */; }; + 2E8DE74B2C57FEB30032BF25 /* LeadingDelimitersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */; }; + 2E8DE74C2C57FEB30032BF25 /* WrapConditionalBodiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */; }; + 2E8DE74D2C57FEB30032BF25 /* OrganizeDeclarationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */; }; + 2E8DE74E2C57FEB30032BF25 /* DocCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */; }; + 2E8DE74F2C57FEB30032BF25 /* ElseOnSameLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */; }; + 2E8DE7502C57FEB30032BF25 /* RedundantReturnTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */; }; + 2E8DE7512C57FEB30032BF25 /* LinebreaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */; }; + 2E8DE7522C57FEB30032BF25 /* MarkTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E72C57FEB30032BF25 /* MarkTypesTests.swift */; }; + 2E8DE7532C57FEB30032BF25 /* SpaceInsideParensTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */; }; + 2E8DE7542C57FEB30032BF25 /* AssertionFailuresTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E92C57FEB30032BF25 /* AssertionFailuresTests.swift */; }; + 2E8DE7552C57FEB30032BF25 /* RedundantTypedThrowsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EA2C57FEB30032BF25 /* RedundantTypedThrowsTests.swift */; }; + 2E8DE7562C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EB2C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift */; }; + 2E8DE7572C57FEB30032BF25 /* ApplicationMainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EC2C57FEB30032BF25 /* ApplicationMainTests.swift */; }; + 2E8DE7582C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6ED2C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift */; }; + 2E8DE7592C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EE2C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift */; }; + 2E8DE75A2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */; }; + 2E8DE75B2C57FEB30032BF25 /* AndOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F02C57FEB30032BF25 /* AndOperatorTests.swift */; }; + 2E8DE75C2C57FEB30032BF25 /* WrapSwitchCasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F12C57FEB30032BF25 /* WrapSwitchCasesTests.swift */; }; + 2E8DE75D2C57FEB30032BF25 /* SpaceInsideBracketsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F22C57FEB30032BF25 /* SpaceInsideBracketsTests.swift */; }; + 2E8DE75E2C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */; }; + 2E8DE75F2C57FEB30032BF25 /* BlankLineAfterImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F42C57FEB30032BF25 /* BlankLineAfterImportsTests.swift */; }; + 2E8DE7602C57FEB30032BF25 /* InitCoderUnavailableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */; }; + 2E8DE7612C57FEB30032BF25 /* RedundantFileprivateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */; }; + 2E8DE7622C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; 37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; 9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 9028F7851DA4B435009FE5B4 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; - 9028F7861DA4B435009FE5B4 /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + 9028F7861DA4B435009FE5B4 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 90C4B6CD1DA4B04A009EB000 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */; }; 90C4B6D11DA4B04A009EB000 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90C4B6D01DA4B04A009EB000 /* Assets.xcassets */; }; 90C4B6D41DA4B04A009EB000 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90C4B6D21DA4B04A009EB000 /* Main.storyboard */; }; @@ -119,7 +659,7 @@ E41CB5C52027700100C1BEDE /* FreeTextTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41CB5C42027700100C1BEDE /* FreeTextTableCellView.swift */; }; E43EF47C202FF47C00E523BD /* OptionDescriptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43EF47B202FF47C00E523BD /* OptionDescriptorTests.swift */; }; E4872111201D3B830014845E /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; }; - E4872112201D3B860014845E /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + E4872112201D3B860014845E /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; E4872113201D3B890014845E /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; E4872114201D3B8C0014845E /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; E487211D201D885A0014845E /* RulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E487211C201D885A0014845E /* RulesViewController.swift */; }; @@ -192,7 +732,6 @@ 01045A90211988F100D2BE3D /* Inference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inference.swift; sourceTree = ""; }; 01045A982119979400D2BE3D /* Arguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arguments.swift; sourceTree = ""; }; 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArgumentsTests.swift; sourceTree = ""; }; - 011676A12707D312001CCDCE /* RulesTests+General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+General.swift"; sourceTree = ""; }; 011A53E921FFAA3A00DD9268 /* VersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionTests.swift; sourceTree = ""; }; 01426E4D23AA29B100E7D871 /* ParsingHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsingHelpersTests.swift; sourceTree = ""; }; 0142C76F23C3FB6D005D5832 /* LintFileCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintFileCommand.swift; sourceTree = ""; }; @@ -209,9 +748,9 @@ 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftFormat.h; sourceTree = ""; }; 01A0EAA91D5DB4CF00A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01A0EAAE1D5DB4D000A0A8E3 /* SwiftFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesTests.swift; sourceTree = ""; }; + 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+testFormatting.swift"; sourceTree = ""; }; 01A0EAB51D5DB4D000A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rules.swift; sourceTree = ""; }; + 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatRule.swift; sourceTree = ""; }; 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftFormat.swift; sourceTree = ""; }; 01A0EACA1D5DB5F500A0A8E3 /* swiftformat */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = swiftformat; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -222,24 +761,232 @@ 01BBD85821DAA2A000457380 /* Globs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globs.swift; sourceTree = ""; }; 01BBD85D21DAA30700457380 /* GlobsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobsTests.swift; sourceTree = ""; }; 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataTests.swift; sourceTree = ""; }; - 01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Spacing.swift"; sourceTree = ""; }; - 01C209B02502CEF700E728A2 /* RulesTests+Linebreaks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Linebreaks.swift"; sourceTree = ""; }; - 01C209B22502CF8300E728A2 /* RulesTests+Indentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Indentation.swift"; sourceTree = ""; }; - 01C209B42502D27800E728A2 /* RulesTests+Braces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Braces.swift"; sourceTree = ""; }; - 01C209B62502D2FD00E728A2 /* RulesTests+Parens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Parens.swift"; sourceTree = ""; }; - 01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Wrapping.swift"; sourceTree = ""; }; - 01C209BA2502D62000E728A2 /* RulesTests+Organization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Organization.swift"; sourceTree = ""; }; - 01C209BC2502D71F00E728A2 /* RulesTests+Redundancy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Redundancy.swift"; sourceTree = ""; }; 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZRegressionTests.swift; sourceTree = ""; }; 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingHelpers.swift; sourceTree = ""; }; - 01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Syntax.swift"; sourceTree = ""; }; - 01EFC8B629CF2B5100222029 /* RulesTests+Hoisting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Hoisting.swift"; sourceTree = ""; }; 01F17E811E25870700DCD359 /* CommandLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLine.swift; sourceTree = ""; }; 01F17E841E258A4900DCD359 /* CommandLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLineTests.swift; sourceTree = ""; }; 01F3DF8B1DB9FD3F00454944 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = ""; }; 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InferenceTests.swift; sourceTree = ""; }; 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclarationHelpers.swift; sourceTree = ""; }; + 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleRegistry.generated.swift; sourceTree = ""; }; + 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitCoderUnavailable.swift; sourceTree = ""; }; + 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBreak.swift; sourceTree = ""; }; + 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterSwitchCase.swift; sourceTree = ""; }; + 2E2BAB942C57F6DD00590239 /* Indent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indent.swift; sourceTree = ""; }; + 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineConditionalAssignment.swift; sourceTree = ""; }; + 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveSpaces.swift; sourceTree = ""; }; + 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsistentSwitchCaseSpacing.swift; sourceTree = ""; }; + 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantExtensionACL.swift; sourceTree = ""; }; + 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantOptionalBinding.swift; sourceTree = ""; }; + 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInternal.swift; sourceTree = ""; }; + 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilInit.swift; sourceTree = ""; }; + 2E2BAB9C2C57F6DD00590239 /* Todos.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Todos.swift; sourceTree = ""; }; + 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideParens.swift; sourceTree = ""; }; + 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Semicolons.swift; sourceTree = ""; }; + 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistPatternLet.swift; sourceTree = ""; }; + 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElseOnSameLine.swift; sourceTree = ""; }; + 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuplicateImports.swift; sourceTree = ""; }; + 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantGet.swift; sourceTree = ""; }; + 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundOperators.swift; sourceTree = ""; }; + 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAroundMark.swift; sourceTree = ""; }; + 2E2BABA52C57F6DD00590239 /* SortImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortImports.swift; sourceTree = ""; }; + 2E2BABA62C57F6DD00590239 /* SortedImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedImports.swift; sourceTree = ""; }; + 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenChainedFunctions.swift; sourceTree = ""; }; + 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantFileprivate.swift; sourceTree = ""; }; + 2E2BABA92C57F6DD00590239 /* BlockComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockComments.swift; sourceTree = ""; }; + 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongOutlets.swift; sourceTree = ""; }; + 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreakAtEndOfFile.swift; sourceTree = ""; }; + 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideGenerics.swift; sourceTree = ""; }; + 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssertionFailures.swift; sourceTree = ""; }; + 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyBraces.swift; sourceTree = ""; }; + 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundComments.swift; sourceTree = ""; }; + 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantParens.swift; sourceTree = ""; }; + 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundGenerics.swift; sourceTree = ""; }; + 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linebreaks.swift; sourceTree = ""; }; + 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadingDelimiters.swift; sourceTree = ""; }; + 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideComments.swift; sourceTree = ""; }; + 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLet.swift; sourceTree = ""; }; + 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsBeforeAttributes.swift; sourceTree = ""; }; + 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveBlankLines.swift; sourceTree = ""; }; + 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInit.swift; sourceTree = ""; }; + 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExplicitOwnership.swift; sourceTree = ""; }; + 2E2BABBA2C57F6DD00590239 /* Void.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Void.swift; sourceTree = ""; }; + 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineComments.swift; sourceTree = ""; }; + 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLetError.swift; sourceTree = ""; }; + 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenScopes.swift; sourceTree = ""; }; + 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantClosure.swift; sourceTree = ""; }; + 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizeDeclarations.swift; sourceTree = ""; }; + 2E2BABC02C57F6DD00590239 /* FileHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeader.swift; sourceTree = ""; }; + 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeSugar.swift; sourceTree = ""; }; + 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBrackets.swift; sourceTree = ""; }; + 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFileName.swift; sourceTree = ""; }; + 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsEmpty.swift; sourceTree = ""; }; + 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBrackets.swift; sourceTree = ""; }; + 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtEndOfScope.swift; sourceTree = ""; }; + 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessControl.swift; sourceTree = ""; }; + 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBraces.swift; sourceTree = ""; }; + 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantReturn.swift; sourceTree = ""; }; + 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericExtensions.swift; sourceTree = ""; }; + 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingSpace.swift; sourceTree = ""; }; + 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantObjc.swift; sourceTree = ""; }; + 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalAssignment.swift; sourceTree = ""; }; + 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferForLoop.swift; sourceTree = ""; }; + 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStaticSelf.swift; sourceTree = ""; }; + 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenImports.swift; sourceTree = ""; }; + 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineStatementBraces.swift; sourceTree = ""; }; + 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBraces.swift; sourceTree = ""; }; + 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantPattern.swift; sourceTree = ""; }; + 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationMain.swift; sourceTree = ""; }; + 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantProperty.swift; sourceTree = ""; }; + 2E2BABD62C57F6DD00590239 /* Wrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wrap.swift; sourceTree = ""; }; + 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterImports.swift; sourceTree = ""; }; + 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifierOrder.swift; sourceTree = ""; }; + 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumNamespaces.swift; sourceTree = ""; }; + 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantSelf.swift; sourceTree = ""; }; + 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferKeyPath.swift; sourceTree = ""; }; + 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapEnumCases.swift; sourceTree = ""; }; + 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapAttributes.swift; sourceTree = ""; }; + 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapConditionalBodies.swift; sourceTree = ""; }; + 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSwitchCases.swift; sourceTree = ""; }; + 2E2BABE02C57F6DD00590239 /* Braces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Braces.swift; sourceTree = ""; }; + 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkTypes.swift; sourceTree = ""; }; + 2E2BABE22C57F6DD00590239 /* AndOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AndOperator.swift; sourceTree = ""; }; + 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapLoopBodies.swift; sourceTree = ""; }; + 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantVoidReturnType.swift; sourceTree = ""; }; + 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantRawValues.swift; sourceTree = ""; }; + 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommas.swift; sourceTree = ""; }; + 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongifiedSelf.swift; sourceTree = ""; }; + 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyObjectProtocol.swift; sourceTree = ""; }; + 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBackticks.swift; sourceTree = ""; }; + 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundParens.swift; sourceTree = ""; }; + 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistAwait.swift; sourceTree = ""; }; + 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtStartOfScope.swift; sourceTree = ""; }; + 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpaqueGenericParameters.swift; sourceTree = ""; }; + 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingClosures.swift; sourceTree = ""; }; + 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedSwitchCases.swift; sourceTree = ""; }; + 2E2BABF02C57F6DD00590239 /* Acronyms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Acronyms.swift; sourceTree = ""; }; + 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortTypealiases.swift; sourceTree = ""; }; + 2E2BABF22C57F6DD00590239 /* DocComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocComments.swift; sourceTree = ""; }; + 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedPrivateDeclaration.swift; sourceTree = ""; }; + 2E2BABF42C57F6DD00590239 /* PropertyType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyType.swift; sourceTree = ""; }; + 2E2BABF52C57F6DD00590239 /* HoistTry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistTry.swift; sourceTree = ""; }; + 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberFormatting.swift; sourceTree = ""; }; + 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapArguments.swift; sourceTree = ""; }; + 2E2BABF82C57F6DD00590239 /* Specifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Specifiers.swift; sourceTree = ""; }; + 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YodaConditions.swift; sourceTree = ""; }; + 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantTypedThrows.swift; sourceTree = ""; }; + 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedArguments.swift; sourceTree = ""; }; + 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortSwitchCases.swift; sourceTree = ""; }; + 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantType.swift; sourceTree = ""; }; + 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDeclarations.swift; sourceTree = ""; }; 2E7D30A32A7940C500C32174 /* Singularize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Singularize.swift; sourceTree = ""; }; + 2E8DE68D2C57FEB30032BF25 /* RedundantClosureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantClosureTests.swift; sourceTree = ""; }; + 2E8DE68E2C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenChainedFunctionsTests.swift; sourceTree = ""; }; + 2E8DE68F2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundGenericsTests.swift; sourceTree = ""; }; + 2E8DE6902C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAroundMarkTests.swift; sourceTree = ""; }; + 2E8DE6912C57FEB30032BF25 /* WrapArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapArgumentsTests.swift; sourceTree = ""; }; + 2E8DE6922C57FEB30032BF25 /* RedundantSelfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantSelfTests.swift; sourceTree = ""; }; + 2E8DE6932C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenScopesTests.swift; sourceTree = ""; }; + 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuplicateImportsTests.swift; sourceTree = ""; }; + 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodosTests.swift; sourceTree = ""; }; + 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderTests.swift; sourceTree = ""; }; + 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumNamespacesTests.swift; sourceTree = ""; }; + 2E8DE6982C57FEB30032BF25 /* PreferForLoopTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferForLoopTests.swift; sourceTree = ""; }; + 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessControlTests.swift; sourceTree = ""; }; + 2E8DE69A2C57FEB30032BF25 /* TrailingCommasTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommasTests.swift; sourceTree = ""; }; + 2E8DE69B2C57FEB30032BF25 /* WrapLoopBodiesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapLoopBodiesTests.swift; sourceTree = ""; }; + 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongifiedSelfTests.swift; sourceTree = ""; }; + 2E8DE69D2C57FEB30032BF25 /* RedundantPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantPropertyTests.swift; sourceTree = ""; }; + 2E8DE69E2C57FEB30032BF25 /* OpaqueGenericParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpaqueGenericParametersTests.swift; sourceTree = ""; }; + 2E8DE69F2C57FEB30032BF25 /* SpaceInsideBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBracesTests.swift; sourceTree = ""; }; + 2E8DE6A02C57FEB30032BF25 /* ModifierOrderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifierOrderTests.swift; sourceTree = ""; }; + 2E8DE6A12C57FEB30032BF25 /* WrapAttributesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapAttributesTests.swift; sourceTree = ""; }; + 2E8DE6A22C57FEB30032BF25 /* RedundantObjcTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantObjcTests.swift; sourceTree = ""; }; + 2E8DE6A32C57FEB30032BF25 /* HoistPatternLetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistPatternLetTests.swift; sourceTree = ""; }; + 2E8DE6A42C57FEB30032BF25 /* WrapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapTests.swift; sourceTree = ""; }; + 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideGenericsTests.swift; sourceTree = ""; }; + 2E8DE6A62C57FEB30032BF25 /* RedundantStaticSelfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStaticSelfTests.swift; sourceTree = ""; }; + 2E8DE6A72C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtEndOfScopeTests.swift; sourceTree = ""; }; + 2E8DE6A82C57FEB30032BF25 /* RedundantInitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInitTests.swift; sourceTree = ""; }; + 2E8DE6A92C57FEB30032BF25 /* RedundantRawValuesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantRawValuesTests.swift; sourceTree = ""; }; + 2E8DE6AA2C57FEB30032BF25 /* SortImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortImportsTests.swift; sourceTree = ""; }; + 2E8DE6AB2C57FEB30032BF25 /* TrailingSpaceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingSpaceTests.swift; sourceTree = ""; }; + 2E8DE6AC2C57FEB30032BF25 /* YodaConditionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YodaConditionsTests.swift; sourceTree = ""; }; + 2E8DE6AD2C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveBlankLinesTests.swift; sourceTree = ""; }; + 2E8DE6AE2C57FEB30032BF25 /* UnusedArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedArgumentsTests.swift; sourceTree = ""; }; + 2E8DE6AF2C57FEB30032BF25 /* GenericExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericExtensionsTests.swift; sourceTree = ""; }; + 2E8DE6B02C57FEB30032BF25 /* NumberFormattingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberFormattingTests.swift; sourceTree = ""; }; + 2E8DE6B12C57FEB30032BF25 /* RedundantTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantTypeTests.swift; sourceTree = ""; }; + 2E8DE6B22C57FEB30032BF25 /* TypeSugarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeSugarTests.swift; sourceTree = ""; }; + 2E8DE6B32C57FEB30032BF25 /* SpaceAroundBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBracesTests.swift; sourceTree = ""; }; + 2E8DE6B42C57FEB30032BF25 /* SortTypealiasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortTypealiasesTests.swift; sourceTree = ""; }; + 2E8DE6B52C57FEB30032BF25 /* SortSwitchCasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortSwitchCasesTests.swift; sourceTree = ""; }; + 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyBracesTests.swift; sourceTree = ""; }; + 2E8DE6B72C57FEB30032BF25 /* SortDeclarationsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDeclarationsTests.swift; sourceTree = ""; }; + 2E8DE6B82C57FEB30032BF25 /* BlockCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockCommentsTests.swift; sourceTree = ""; }; + 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongOutletsTests.swift; sourceTree = ""; }; + 2E8DE6BA2C57FEB30032BF25 /* BracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BracesTests.swift; sourceTree = ""; }; + 2E8DE6BB2C57FEB30032BF25 /* AnyObjectProtocolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyObjectProtocolTests.swift; sourceTree = ""; }; + 2E8DE6BC2C57FEB30032BF25 /* RedundantBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBreakTests.swift; sourceTree = ""; }; + 2E8DE6BD2C57FEB30032BF25 /* RedundantNilInitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilInitTests.swift; sourceTree = ""; }; + 2E8DE6BE2C57FEB30032BF25 /* RedundantParensTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantParensTests.swift; sourceTree = ""; }; + 2E8DE6BF2C57FEB30032BF25 /* PreferKeyPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferKeyPathTests.swift; sourceTree = ""; }; + 2E8DE6C02C57FEB30032BF25 /* SpaceAroundParensTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundParensTests.swift; sourceTree = ""; }; + 2E8DE6C12C57FEB30032BF25 /* RedundantLetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLetTests.swift; sourceTree = ""; }; + 2E8DE6C22C57FEB30032BF25 /* VoidTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoidTests.swift; sourceTree = ""; }; + 2E8DE6C32C57FEB30032BF25 /* IndentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndentTests.swift; sourceTree = ""; }; + 2E8DE6C42C57FEB30032BF25 /* RedundantBackticksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBackticksTests.swift; sourceTree = ""; }; + 2E8DE6C52C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterSwitchCaseTests.swift; sourceTree = ""; }; + 2E8DE6C62C57FEB30032BF25 /* HoistAwaitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistAwaitTests.swift; sourceTree = ""; }; + 2E8DE6C72C57FEB30032BF25 /* RedundantLetErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLetErrorTests.swift; sourceTree = ""; }; + 2E8DE6C82C57FEB30032BF25 /* SpaceAroundCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundCommentsTests.swift; sourceTree = ""; }; + 2E8DE6C92C57FEB30032BF25 /* PropertyTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyTypeTests.swift; sourceTree = ""; }; + 2E8DE6CA2C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundOperatorsTests.swift; sourceTree = ""; }; + 2E8DE6CB2C57FEB30032BF25 /* SemicolonsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SemicolonsTests.swift; sourceTree = ""; }; + 2E8DE6CC2C57FEB30032BF25 /* RedundantPatternTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantPatternTests.swift; sourceTree = ""; }; + 2E8DE6CD2C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineConditionalAssignmentTests.swift; sourceTree = ""; }; + 2E8DE6CE2C57FEB30032BF25 /* RedundantInternalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInternalTests.swift; sourceTree = ""; }; + 2E8DE6CF2C57FEB30032BF25 /* IsEmptyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsEmptyTests.swift; sourceTree = ""; }; + 2E8DE6D02C57FEB30032BF25 /* AcronymsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcronymsTests.swift; sourceTree = ""; }; + 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsistentSwitchCaseSpacingTests.swift; sourceTree = ""; }; + 2E8DE6D22C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedPrivateDeclarationTests.swift; sourceTree = ""; }; + 2E8DE6D32C57FEB30032BF25 /* WrapEnumCasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapEnumCasesTests.swift; sourceTree = ""; }; + 2E8DE6D42C57FEB30032BF25 /* NoExplicitOwnershipTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExplicitOwnershipTests.swift; sourceTree = ""; }; + 2E8DE6D52C57FEB30032BF25 /* HoistTryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistTryTests.swift; sourceTree = ""; }; + 2E8DE6D62C57FEB30032BF25 /* RedundantOptionalBindingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantOptionalBindingTests.swift; sourceTree = ""; }; + 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveSpacesTests.swift; sourceTree = ""; }; + 2E8DE6D82C57FEB30032BF25 /* SpaceAroundBracketsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBracketsTests.swift; sourceTree = ""; }; + 2E8DE6D92C57FEB30032BF25 /* TrailingClosuresTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingClosuresTests.swift; sourceTree = ""; }; + 2E8DE6DA2C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineStatementBracesTests.swift; sourceTree = ""; }; + 2E8DE6DB2C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreakAtEndOfFileTests.swift; sourceTree = ""; }; + 2E8DE6DC2C57FEB30032BF25 /* ConditionalAssignmentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalAssignmentTests.swift; sourceTree = ""; }; + 2E8DE6DD2C57FEB30032BF25 /* RedundantGetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantGetTests.swift; sourceTree = ""; }; + 2E8DE6DE2C57FEB30032BF25 /* HeaderFileNameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFileNameTests.swift; sourceTree = ""; }; + 2E8DE6DF2C57FEB30032BF25 /* RedundantExtensionACLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantExtensionACLTests.swift; sourceTree = ""; }; + 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadingDelimitersTests.swift; sourceTree = ""; }; + 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapConditionalBodiesTests.swift; sourceTree = ""; }; + 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizeDeclarationsTests.swift; sourceTree = ""; }; + 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsTests.swift; sourceTree = ""; }; + 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElseOnSameLineTests.swift; sourceTree = ""; }; + 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantReturnTests.swift; sourceTree = ""; }; + 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreaksTests.swift; sourceTree = ""; }; + 2E8DE6E72C57FEB30032BF25 /* MarkTypesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkTypesTests.swift; sourceTree = ""; }; + 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideParensTests.swift; sourceTree = ""; }; + 2E8DE6E92C57FEB30032BF25 /* AssertionFailuresTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssertionFailuresTests.swift; sourceTree = ""; }; + 2E8DE6EA2C57FEB30032BF25 /* RedundantTypedThrowsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantTypedThrowsTests.swift; sourceTree = ""; }; + 2E8DE6EB2C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtStartOfScopeTests.swift; sourceTree = ""; }; + 2E8DE6EC2C57FEB30032BF25 /* ApplicationMainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationMainTests.swift; sourceTree = ""; }; + 2E8DE6ED2C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantVoidReturnTypeTests.swift; sourceTree = ""; }; + 2E8DE6EE2C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenImportsTests.swift; sourceTree = ""; }; + 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideCommentsTests.swift; sourceTree = ""; }; + 2E8DE6F02C57FEB30032BF25 /* AndOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AndOperatorTests.swift; sourceTree = ""; }; + 2E8DE6F12C57FEB30032BF25 /* WrapSwitchCasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSwitchCasesTests.swift; sourceTree = ""; }; + 2E8DE6F22C57FEB30032BF25 /* SpaceInsideBracketsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBracketsTests.swift; sourceTree = ""; }; + 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsBeforeAttributesTests.swift; sourceTree = ""; }; + 2E8DE6F42C57FEB30032BF25 /* BlankLineAfterImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterImportsTests.swift; sourceTree = ""; }; + 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitCoderUnavailableTests.swift; sourceTree = ""; }; + 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantFileprivateTests.swift; sourceTree = ""; }; + 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineCommentsTests.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -374,7 +1121,8 @@ 01A0EAA61D5DB4CF00A0A8E3 /* Sources */ = { isa = PBXGroup; children = ( - 012939DB2C56A3AA00BD0A24 /* Reporters */, + 2E2BAB902C57F6DD00590239 /* Rules */, + 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */, 01F17E811E25870700DCD359 /* CommandLine.swift */, E4E4D3C82033F17C000D7CB1 /* EnumAssociable.swift */, 01B3987C1D763493009ADE61 /* Formatter.swift */, @@ -383,7 +1131,7 @@ E4FABAD4202FEF060065716E /* OptionDescriptor.swift */, 01045A982119979400D2BE3D /* Arguments.swift */, 01045A90211988F100D2BE3D /* Inference.swift */, - 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */, + 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */, 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */, 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */, 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */, @@ -394,6 +1142,7 @@ 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */, 01BBD85821DAA2A000457380 /* Globs.swift */, D52F6A632A82E04600FE1448 /* GitFileInfo.swift */, + 012939DB2C56A3AA00BD0A24 /* Reporters */, ); path = Sources; sourceTree = ""; @@ -401,6 +1150,7 @@ 01A0EAB21D5DB4D000A0A8E3 /* Tests */ = { isa = PBXGroup; children = ( + 2E8DE68C2C57FEB30032BF25 /* Rules */, 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */, 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */, 01F17E841E258A4900DCD359 /* CommandLineTests.swift */, @@ -411,24 +1161,13 @@ E4E4D3CD2033F1EF000D7CB1 /* EnumAssociableTests.swift */, E43EF47B202FF47C00E523BD /* OptionDescriptorTests.swift */, 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */, - 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */, - 01C209B42502D27800E728A2 /* RulesTests+Braces.swift */, - 011676A12707D312001CCDCE /* RulesTests+General.swift */, - 01C209B22502CF8300E728A2 /* RulesTests+Indentation.swift */, - 01EFC8B629CF2B5100222029 /* RulesTests+Hoisting.swift */, - 01C209B02502CEF700E728A2 /* RulesTests+Linebreaks.swift */, - 01C209BA2502D62000E728A2 /* RulesTests+Organization.swift */, - 01C209B62502D2FD00E728A2 /* RulesTests+Parens.swift */, - 01C209BC2502D71F00E728A2 /* RulesTests+Redundancy.swift */, - 01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */, - 01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */, - 01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */, 015CE8B02B448CCE00924504 /* SingularizeTests.swift */, 0142F06E1D72FE10007D66CC /* SwiftFormatTests.swift */, 01BBD85D21DAA30700457380 /* GlobsTests.swift */, 018E82741D62E730008CA0F8 /* TokenizerTests.swift */, 011A53E921FFAA3A00DD9268 /* VersionTests.swift */, 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */, + 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */, ); path = Tests; sourceTree = ""; @@ -450,6 +1189,237 @@ path = Shared; sourceTree = ""; }; + 2E2BAB902C57F6DD00590239 /* Rules */ = { + isa = PBXGroup; + children = ( + 2E2BABF02C57F6DD00590239 /* Acronyms.swift */, + 2E2BABE22C57F6DD00590239 /* AndOperator.swift */, + 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */, + 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */, + 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */, + 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */, + 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */, + 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */, + 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */, + 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */, + 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */, + 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */, + 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */, + 2E2BABA92C57F6DD00590239 /* BlockComments.swift */, + 2E2BABE02C57F6DD00590239 /* Braces.swift */, + 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */, + 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */, + 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */, + 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */, + 2E2BABF22C57F6DD00590239 /* DocComments.swift */, + 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */, + 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */, + 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */, + 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */, + 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */, + 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */, + 2E2BABC02C57F6DD00590239 /* FileHeader.swift */, + 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */, + 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */, + 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */, + 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */, + 2E2BABF52C57F6DD00590239 /* HoistTry.swift */, + 2E2BAB942C57F6DD00590239 /* Indent.swift */, + 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */, + 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */, + 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */, + 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */, + 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */, + 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */, + 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */, + 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */, + 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */, + 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */, + 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */, + 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */, + 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */, + 2E2BABF42C57F6DD00590239 /* PropertyType.swift */, + 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */, + 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */, + 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */, + 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */, + 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */, + 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */, + 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */, + 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */, + 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */, + 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */, + 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */, + 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */, + 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */, + 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */, + 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */, + 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */, + 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */, + 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */, + 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */, + 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */, + 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */, + 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */, + 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */, + 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */, + 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */, + 2E2BABA62C57F6DD00590239 /* SortedImports.swift */, + 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */, + 2E2BABA52C57F6DD00590239 /* SortImports.swift */, + 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */, + 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */, + 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */, + 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */, + 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */, + 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */, + 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */, + 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */, + 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */, + 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */, + 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */, + 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */, + 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */, + 2E2BABF82C57F6DD00590239 /* Specifiers.swift */, + 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */, + 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */, + 2E2BAB9C2C57F6DD00590239 /* Todos.swift */, + 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */, + 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */, + 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */, + 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */, + 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */, + 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */, + 2E2BABBA2C57F6DD00590239 /* Void.swift */, + 2E2BABD62C57F6DD00590239 /* Wrap.swift */, + 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */, + 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */, + 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */, + 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */, + 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */, + 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */, + 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */, + 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */, + 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */, + 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */, + ); + path = Rules; + sourceTree = ""; + }; + 2E8DE68C2C57FEB30032BF25 /* Rules */ = { + isa = PBXGroup; + children = ( + 2E8DE6D02C57FEB30032BF25 /* AcronymsTests.swift */, + 2E8DE6F02C57FEB30032BF25 /* AndOperatorTests.swift */, + 2E8DE6BB2C57FEB30032BF25 /* AnyObjectProtocolTests.swift */, + 2E8DE6EC2C57FEB30032BF25 /* ApplicationMainTests.swift */, + 2E8DE6E92C57FEB30032BF25 /* AssertionFailuresTests.swift */, + 2E8DE6F42C57FEB30032BF25 /* BlankLineAfterImportsTests.swift */, + 2E8DE6C52C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift */, + 2E8DE6902C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift */, + 2E8DE6A72C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift */, + 2E8DE6EB2C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift */, + 2E8DE68E2C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift */, + 2E8DE6EE2C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift */, + 2E8DE6932C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift */, + 2E8DE6B82C57FEB30032BF25 /* BlockCommentsTests.swift */, + 2E8DE6BA2C57FEB30032BF25 /* BracesTests.swift */, + 2E8DE6DC2C57FEB30032BF25 /* ConditionalAssignmentTests.swift */, + 2E8DE6AD2C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift */, + 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */, + 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */, + 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */, + 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */, + 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */, + 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */, + 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */, + 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */, + 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */, + 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */, + 2E8DE6AF2C57FEB30032BF25 /* GenericExtensionsTests.swift */, + 2E8DE6DE2C57FEB30032BF25 /* HeaderFileNameTests.swift */, + 2E8DE6C62C57FEB30032BF25 /* HoistAwaitTests.swift */, + 2E8DE6A32C57FEB30032BF25 /* HoistPatternLetTests.swift */, + 2E8DE6D52C57FEB30032BF25 /* HoistTryTests.swift */, + 2E8DE6C32C57FEB30032BF25 /* IndentTests.swift */, + 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */, + 2E8DE6CF2C57FEB30032BF25 /* IsEmptyTests.swift */, + 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */, + 2E8DE6DB2C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift */, + 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */, + 2E8DE6E72C57FEB30032BF25 /* MarkTypesTests.swift */, + 2E8DE6A02C57FEB30032BF25 /* ModifierOrderTests.swift */, + 2E8DE6D42C57FEB30032BF25 /* NoExplicitOwnershipTests.swift */, + 2E8DE6B02C57FEB30032BF25 /* NumberFormattingTests.swift */, + 2E8DE69E2C57FEB30032BF25 /* OpaqueGenericParametersTests.swift */, + 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */, + 2E8DE6982C57FEB30032BF25 /* PreferForLoopTests.swift */, + 2E8DE6BF2C57FEB30032BF25 /* PreferKeyPathTests.swift */, + 2E8DE6C92C57FEB30032BF25 /* PropertyTypeTests.swift */, + 2E8DE6C42C57FEB30032BF25 /* RedundantBackticksTests.swift */, + 2E8DE6BC2C57FEB30032BF25 /* RedundantBreakTests.swift */, + 2E8DE68D2C57FEB30032BF25 /* RedundantClosureTests.swift */, + 2E8DE6DF2C57FEB30032BF25 /* RedundantExtensionACLTests.swift */, + 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */, + 2E8DE6DD2C57FEB30032BF25 /* RedundantGetTests.swift */, + 2E8DE6A82C57FEB30032BF25 /* RedundantInitTests.swift */, + 2E8DE6CE2C57FEB30032BF25 /* RedundantInternalTests.swift */, + 2E8DE6C72C57FEB30032BF25 /* RedundantLetErrorTests.swift */, + 2E8DE6C12C57FEB30032BF25 /* RedundantLetTests.swift */, + 2E8DE6BD2C57FEB30032BF25 /* RedundantNilInitTests.swift */, + 2E8DE6A22C57FEB30032BF25 /* RedundantObjcTests.swift */, + 2E8DE6D62C57FEB30032BF25 /* RedundantOptionalBindingTests.swift */, + 2E8DE6BE2C57FEB30032BF25 /* RedundantParensTests.swift */, + 2E8DE6CC2C57FEB30032BF25 /* RedundantPatternTests.swift */, + 2E8DE69D2C57FEB30032BF25 /* RedundantPropertyTests.swift */, + 2E8DE6A92C57FEB30032BF25 /* RedundantRawValuesTests.swift */, + 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */, + 2E8DE6922C57FEB30032BF25 /* RedundantSelfTests.swift */, + 2E8DE6A62C57FEB30032BF25 /* RedundantStaticSelfTests.swift */, + 2E8DE6EA2C57FEB30032BF25 /* RedundantTypedThrowsTests.swift */, + 2E8DE6B12C57FEB30032BF25 /* RedundantTypeTests.swift */, + 2E8DE6ED2C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift */, + 2E8DE6CB2C57FEB30032BF25 /* SemicolonsTests.swift */, + 2E8DE6B72C57FEB30032BF25 /* SortDeclarationsTests.swift */, + 2E8DE6AA2C57FEB30032BF25 /* SortImportsTests.swift */, + 2E8DE6B52C57FEB30032BF25 /* SortSwitchCasesTests.swift */, + 2E8DE6B42C57FEB30032BF25 /* SortTypealiasesTests.swift */, + 2E8DE6B32C57FEB30032BF25 /* SpaceAroundBracesTests.swift */, + 2E8DE6D82C57FEB30032BF25 /* SpaceAroundBracketsTests.swift */, + 2E8DE6C82C57FEB30032BF25 /* SpaceAroundCommentsTests.swift */, + 2E8DE68F2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift */, + 2E8DE6CA2C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift */, + 2E8DE6C02C57FEB30032BF25 /* SpaceAroundParensTests.swift */, + 2E8DE69F2C57FEB30032BF25 /* SpaceInsideBracesTests.swift */, + 2E8DE6F22C57FEB30032BF25 /* SpaceInsideBracketsTests.swift */, + 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */, + 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */, + 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */, + 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */, + 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */, + 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */, + 2E8DE6D92C57FEB30032BF25 /* TrailingClosuresTests.swift */, + 2E8DE69A2C57FEB30032BF25 /* TrailingCommasTests.swift */, + 2E8DE6AB2C57FEB30032BF25 /* TrailingSpaceTests.swift */, + 2E8DE6B22C57FEB30032BF25 /* TypeSugarTests.swift */, + 2E8DE6AE2C57FEB30032BF25 /* UnusedArgumentsTests.swift */, + 2E8DE6D22C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift */, + 2E8DE6C22C57FEB30032BF25 /* VoidTests.swift */, + 2E8DE6912C57FEB30032BF25 /* WrapArgumentsTests.swift */, + 2E8DE6A12C57FEB30032BF25 /* WrapAttributesTests.swift */, + 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */, + 2E8DE6D32C57FEB30032BF25 /* WrapEnumCasesTests.swift */, + 2E8DE69B2C57FEB30032BF25 /* WrapLoopBodiesTests.swift */, + 2E8DE6CD2C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift */, + 2E8DE6DA2C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift */, + 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */, + 2E8DE6F12C57FEB30032BF25 /* WrapSwitchCasesTests.swift */, + 2E8DE6A42C57FEB30032BF25 /* WrapTests.swift */, + 2E8DE6AC2C57FEB30032BF25 /* YodaConditionsTests.swift */, + ); + path = Rules; + sourceTree = ""; + }; 9016DEFD1DA5042E008A4E36 /* Resources */ = { isa = PBXGroup; children = ( @@ -678,9 +1648,6 @@ LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Nick Lockwood"; TargetAttributes = { - 015AF2B61DC6A538008F0A8C = { - DevelopmentTeam = 8VQKF583ED; - }; 01A0EAA31D5DB4CF00A0A8E3 = { CreatedOnToolsVersion = 7.3; DevelopmentTeam = 8VQKF583ED; @@ -806,27 +1773,138 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E2BAD9B2C57F6DD00590239 /* Specifiers.swift in Sources */, D52F6A642A82E04600FE1448 /* GitFileInfo.swift in Sources */, + 2E2BACEB2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BAC572C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, + 2E2BACAB2C57F6DD00590239 /* RedundantLetError.swift in Sources */, + 2E2BADA72C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BACCB2C57F6DD00590239 /* IsEmpty.swift in Sources */, + 2E2BAD0B2C57F6DD00590239 /* ApplicationMain.swift in Sources */, + 2E2BAD572C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAC032C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAC432C57F6DD00590239 /* RedundantGet.swift in Sources */, 01045A992119979400D2BE3D /* Arguments.swift in Sources */, 01567D2F225B2BFD00B22D41 /* ParsingHelpers.swift in Sources */, + 2E2BACB32C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD032C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BAC7B2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC1B2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, DD9AD39E2999FCC8001C2C0E /* GithubActionsLogReporter.swift in Sources */, + 2E2BAC5F2C57F6DD00590239 /* BlockComments.swift in Sources */, 01045A91211988F100D2BE3D /* Inference.swift in Sources */, + 2E2BAC4F2C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAC632C57F6DD00590239 /* StrongOutlets.swift in Sources */, + 2E2BAC532C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BADAF2C57F6DD00590239 /* RedundantType.swift in Sources */, + 2E2BAD7B2C57F6DD00590239 /* Acronyms.swift in Sources */, DD9AD3A32999FCC8001C2C0E /* Reporter.swift in Sources */, + 2E2BAC8F2C57F6DD00590239 /* RedundantLet.swift in Sources */, E4FABAD5202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BAD8F2C57F6DD00590239 /* HoistTry.swift in Sources */, + 2E2BAC732C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BAD6F2C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BACD32C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BACF72C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC4B2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC372C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BAD672C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAD832C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BAC2B2C57F6DD00590239 /* Todos.swift in Sources */, + 2E2BAC6F2C57F6DD00590239 /* AssertionFailures.swift in Sources */, + 2E2BAD5B2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAC072C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BADA32C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BAD4B2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, + 2E2BAC332C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAC0B2C57F6DD00590239 /* Indent.swift in Sources */, + 2E2BACC32C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BACF32C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BAC172C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, + 2E2BACE32C57F6DD00590239 /* GenericExtensions.swift in Sources */, 01BBD85921DAA2A000457380 /* Globs.swift in Sources */, 01ACAE05220CD90F003F3CCF /* Examples.swift in Sources */, 01D3B28624E9C9C700888DE0 /* FormattingHelpers.swift in Sources */, + 2E2BAD532C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAD5F2C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC272C57F6DD00590239 /* RedundantNilInit.swift in Sources */, + 2E2BACDB2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, + 2E2BAC772C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAD8B2C57F6DD00590239 /* PropertyType.swift in Sources */, + 2E2BAD3F2C57F6DD00590239 /* MarkTypes.swift in Sources */, + 2E2BAC972C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BAC132C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BAD0F2C57F6DD00590239 /* RedundantProperty.swift in Sources */, + 2E2BACFF2C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, E4E4D3C92033F17C000D7CB1 /* EnumAssociable.swift in Sources */, + 2E2BAC472C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BAD2F2C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAC932C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */, - 01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */, + 01A0EAC11D5DB4F700A0A8E3 /* FormatRule.swift in Sources */, + 2E2BAC9F2C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACA72C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BACC72C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BACD72C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC7F2C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAC5B2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BACBB2C57F6DD00590239 /* FileHeader.swift in Sources */, + 2E2BAD972C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAD272C57F6DD00590239 /* PreferKeyPath.swift in Sources */, + 2E2BAC6B2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BACCF2C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, + 2E2BAC9B2C57F6DD00590239 /* RedundantInit.swift in Sources */, + 2E2BAC3B2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, 01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */, + 2E2BAC232C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BAC872C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BAC8B2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BAC672C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, + 2E2BACFB2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BAD2B2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, + 2E2BAD872C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, + 2E2BAD1B2C57F6DD00590239 /* ModifierOrder.swift in Sources */, C2FFD1822BD13C9E00774F55 /* XMLReporter.swift in Sources */, + 2E2BAD632C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */, + 2E2BADB32C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BACAF2C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAB8C2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, 01B3987D1D763493009ADE61 /* Formatter.swift in Sources */, 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */, + 2E2BAD932C57F6DD00590239 /* NumberFormatting.swift in Sources */, 01F17E821E25870700DCD359 /* CommandLine.swift in Sources */, + 2E2BAD7F2C57F6DD00590239 /* SortTypealiases.swift in Sources */, + 2E2BAD772C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BACA32C57F6DD00590239 /* Void.swift in Sources */, + 2E2BAD9F2C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BAD3B2C57F6DD00590239 /* Braces.swift in Sources */, + 2E2BABFF2C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BACB72C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BAD132C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BACDF2C57F6DD00590239 /* RedundantReturn.swift in Sources */, + 2E2BAC0F2C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, + 2E2BAD072C57F6DD00590239 /* RedundantPattern.swift in Sources */, + 2E2BACE72C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BACEF2C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAD172C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAD332C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, + 2E2BAC2F2C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, + 2E2BADAB2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, + 2E2BAC832C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BAC3F2C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BAC1F2C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, + 2E2BACBF2C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BAD372C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAD472C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BAD432C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAD4F2C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BAD1F2C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BAD232C57F6DD00590239 /* RedundantSelf.swift in Sources */, 01F3DF8C1DB9FD3F00454944 /* Options.swift in Sources */, + 2E2BAD6B2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, 01A0EAC21D5DB4F700A0A8E3 /* Tokenizer.swift in Sources */, + 2E2BAD732C57F6DD00590239 /* TrailingClosures.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -834,33 +1912,129 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 01A0EAB41D5DB4D000A0A8E3 /* RulesTests.swift in Sources */, - 011676A22707D312001CCDCE /* RulesTests+General.swift in Sources */, - 01C209B92502D3C500E728A2 /* RulesTests+Wrapping.swift in Sources */, + 2E8DE74A2C57FEB30032BF25 /* RedundantExtensionACLTests.swift in Sources */, + 01A0EAB41D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift in Sources */, + 2E8DE7602C57FEB30032BF25 /* InitCoderUnavailableTests.swift in Sources */, + 2E8DE75C2C57FEB30032BF25 /* WrapSwitchCasesTests.swift in Sources */, + 2E8DE71B2C57FEB30032BF25 /* NumberFormattingTests.swift in Sources */, + 2E8DE74C2C57FEB30032BF25 /* WrapConditionalBodiesTests.swift in Sources */, + 2E8DE7332C57FEB30032BF25 /* SpaceAroundCommentsTests.swift in Sources */, + 2E8DE7342C57FEB30032BF25 /* PropertyTypeTests.swift in Sources */, + 2E8DE7162C57FEB30032BF25 /* TrailingSpaceTests.swift in Sources */, + 2E8DE7472C57FEB30032BF25 /* ConditionalAssignmentTests.swift in Sources */, 01F17E851E258A4900DCD359 /* CommandLineTests.swift in Sources */, + 2E8DE7082C57FEB30032BF25 /* RedundantPropertyTests.swift in Sources */, 01426E4E23AA29B100E7D871 /* ParsingHelpersTests.swift in Sources */, + 2E8DE70A2C57FEB30032BF25 /* SpaceInsideBracesTests.swift in Sources */, + 2E8DE7442C57FEB30032BF25 /* TrailingClosuresTests.swift in Sources */, + 2E8DE7402C57FEB30032BF25 /* HoistTryTests.swift in Sources */, + 2E8DE7062C57FEB30032BF25 /* WrapLoopBodiesTests.swift in Sources */, + 2E8DE6F92C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift in Sources */, + 2E8DE7382C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift in Sources */, + 2E8DE74F2C57FEB30032BF25 /* ElseOnSameLineTests.swift in Sources */, + 2E8DE6FD2C57FEB30032BF25 /* RedundantSelfTests.swift in Sources */, 018E82751D62E730008CA0F8 /* TokenizerTests.swift in Sources */, - 01C209BB2502D62000E728A2 /* RulesTests+Organization.swift in Sources */, + 2E8DE74D2C57FEB30032BF25 /* OrganizeDeclarationsTests.swift in Sources */, + 2E8DE71E2C57FEB30032BF25 /* SpaceAroundBracesTests.swift in Sources */, + 2E8DE73C2C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift in Sources */, + 2E8DE75B2C57FEB30032BF25 /* AndOperatorTests.swift in Sources */, + 2E8DE71C2C57FEB30032BF25 /* RedundantTypeTests.swift in Sources */, + 2E8DE7492C57FEB30032BF25 /* HeaderFileNameTests.swift in Sources */, + 2E8DE7172C57FEB30032BF25 /* YodaConditionsTests.swift in Sources */, + 2E8DE7152C57FEB30032BF25 /* SortImportsTests.swift in Sources */, + 2E8DE7302C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift in Sources */, 011A53EB21FFAA4200DD9268 /* VersionTests.swift in Sources */, - 01EF830F25616089003F6F2D /* RulesTests+Syntax.swift in Sources */, + 2E8DE72E2C57FEB30032BF25 /* IndentTests.swift in Sources */, + 2E8DE7532C57FEB30032BF25 /* SpaceInsideParensTests.swift in Sources */, + 2E8DE7462C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift in Sources */, 01BBD85E21DAA30700457380 /* GlobsTests.swift in Sources */, + 2E8DE7542C57FEB30032BF25 /* AssertionFailuresTests.swift in Sources */, 01B3987B1D763424009ADE61 /* FormatterTests.swift in Sources */, + 2E8DE7502C57FEB30032BF25 /* RedundantReturnTests.swift in Sources */, + 2E8DE7052C57FEB30032BF25 /* TrailingCommasTests.swift in Sources */, + 2E8DE7362C57FEB30032BF25 /* SemicolonsTests.swift in Sources */, + 2E8DE7272C57FEB30032BF25 /* RedundantBreakTests.swift in Sources */, + 2E8DE7232C57FEB30032BF25 /* BlockCommentsTests.swift in Sources */, + 2E8DE75A2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift in Sources */, + 2E8DE75D2C57FEB30032BF25 /* SpaceInsideBracketsTests.swift in Sources */, + 2E8DE72D2C57FEB30032BF25 /* VoidTests.swift in Sources */, + 2E8DE72B2C57FEB30032BF25 /* SpaceAroundParensTests.swift in Sources */, + 2E8DE73F2C57FEB30032BF25 /* NoExplicitOwnershipTests.swift in Sources */, 01C4D3292BB518D400BDF1AF /* ZRegressionTests.swift in Sources */, - 01C209BD2502D71F00E728A2 /* RulesTests+Redundancy.swift in Sources */, + 2E8DE7092C57FEB30032BF25 /* OpaqueGenericParametersTests.swift in Sources */, + 2E8DE7202C57FEB30032BF25 /* SortSwitchCasesTests.swift in Sources */, + 2E8DE7452C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift in Sources */, + 2E8DE7242C57FEB30032BF25 /* StrongOutletsTests.swift in Sources */, + 2E8DE7292C57FEB30032BF25 /* RedundantParensTests.swift in Sources */, 0142F06F1D72FE10007D66CC /* SwiftFormatTests.swift in Sources */, - 01C209AF2502CD3C00E728A2 /* RulesTests+Spacing.swift in Sources */, + 2E8DE7432C57FEB30032BF25 /* SpaceAroundBracketsTests.swift in Sources */, + 2E8DE7612C57FEB30032BF25 /* RedundantFileprivateTests.swift in Sources */, + 2E8DE6F82C57FEB30032BF25 /* RedundantClosureTests.swift in Sources */, + 2E8DE7562C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift in Sources */, + 2E8DE75F2C57FEB30032BF25 /* BlankLineAfterImportsTests.swift in Sources */, + 2E8DE7182C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift in Sources */, + 2E8DE7192C57FEB30032BF25 /* UnusedArgumentsTests.swift in Sources */, + 2E8DE7022C57FEB30032BF25 /* EnumNamespacesTests.swift in Sources */, + 2E8DE7572C57FEB30032BF25 /* ApplicationMainTests.swift in Sources */, + 2E8DE7102C57FEB30032BF25 /* SpaceInsideGenericsTests.swift in Sources */, + 2E8DE70C2C57FEB30032BF25 /* WrapAttributesTests.swift in Sources */, + 2E8DE73D2C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift in Sources */, + 2E8DE71D2C57FEB30032BF25 /* TypeSugarTests.swift in Sources */, + 2E8DE73E2C57FEB30032BF25 /* WrapEnumCasesTests.swift in Sources */, E4E4D3CE2033F1EF000D7CB1 /* EnumAssociableTests.swift in Sources */, + 2E8DE73B2C57FEB30032BF25 /* AcronymsTests.swift in Sources */, E43EF47C202FF47C00E523BD /* OptionDescriptorTests.swift in Sources */, + 2E8DE71F2C57FEB30032BF25 /* SortTypealiasesTests.swift in Sources */, + 2E8DE7012C57FEB30032BF25 /* FileHeaderTests.swift in Sources */, + 2E8DE7322C57FEB30032BF25 /* RedundantLetErrorTests.swift in Sources */, 01045A9E2119A37F00D2BE3D /* ArgumentsTests.swift in Sources */, - 01C209B52502D27800E728A2 /* RulesTests+Braces.swift in Sources */, - 01EFC8B729CF2B5100222029 /* RulesTests+Hoisting.swift in Sources */, - 01C209B72502D2FD00E728A2 /* RulesTests+Parens.swift in Sources */, + 2E8DE71A2C57FEB30032BF25 /* GenericExtensionsTests.swift in Sources */, + 2E8DE7372C57FEB30032BF25 /* RedundantPatternTests.swift in Sources */, + 2E8DE7622C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift in Sources */, + 2E8DE72A2C57FEB30032BF25 /* PreferKeyPathTests.swift in Sources */, + 2E8DE7592C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift in Sources */, + 2E8DE7392C57FEB30032BF25 /* RedundantInternalTests.swift in Sources */, + 2E8DE70B2C57FEB30032BF25 /* ModifierOrderTests.swift in Sources */, + 2E8DE7422C57FEB30032BF25 /* ConsecutiveSpacesTests.swift in Sources */, + 2E8DE7312C57FEB30032BF25 /* HoistAwaitTests.swift in Sources */, + 2E8DE7132C57FEB30032BF25 /* RedundantInitTests.swift in Sources */, + 2E8DE7142C57FEB30032BF25 /* RedundantRawValuesTests.swift in Sources */, + 2E8DE7262C57FEB30032BF25 /* AnyObjectProtocolTests.swift in Sources */, + 2E8DE70F2C57FEB30032BF25 /* WrapTests.swift in Sources */, + 2E8DE7032C57FEB30032BF25 /* PreferForLoopTests.swift in Sources */, + 2E8DE6FC2C57FEB30032BF25 /* WrapArgumentsTests.swift in Sources */, + 2E8DE70D2C57FEB30032BF25 /* RedundantObjcTests.swift in Sources */, + 2E8DE7212C57FEB30032BF25 /* EmptyBracesTests.swift in Sources */, + 2E8DE7072C57FEB30032BF25 /* StrongifiedSelfTests.swift in Sources */, + 2E8DE73A2C57FEB30032BF25 /* IsEmptyTests.swift in Sources */, 01BEC5772236E1A700D0DD83 /* MetadataTests.swift in Sources */, + 2E8DE6FB2C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift in Sources */, + 2E8DE7582C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift in Sources */, + 2E8DE7352C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift in Sources */, + 2E8DE7412C57FEB30032BF25 /* RedundantOptionalBindingTests.swift in Sources */, + 2E8DE7512C57FEB30032BF25 /* LinebreaksTests.swift in Sources */, 01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */, - 01C209B32502CF8300E728A2 /* RulesTests+Indentation.swift in Sources */, - 01C209B12502CEF700E728A2 /* RulesTests+Linebreaks.swift in Sources */, + 2E8DE7482C57FEB30032BF25 /* RedundantGetTests.swift in Sources */, + 2E8DE72F2C57FEB30032BF25 /* RedundantBackticksTests.swift in Sources */, + 2E8DE6FE2C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift in Sources */, + 2E8DE7002C57FEB30032BF25 /* TodosTests.swift in Sources */, + 2E8DE7282C57FEB30032BF25 /* RedundantNilInitTests.swift in Sources */, + 2E8DE6FA2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift in Sources */, + 2E8DE7222C57FEB30032BF25 /* SortDeclarationsTests.swift in Sources */, + 2E8DE7122C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift in Sources */, + 2E8DE7252C57FEB30032BF25 /* BracesTests.swift in Sources */, + 2E8DE74B2C57FEB30032BF25 /* LeadingDelimitersTests.swift in Sources */, + 2E8DE7552C57FEB30032BF25 /* RedundantTypedThrowsTests.swift in Sources */, 015CE8B12B448CCE00924504 /* SingularizeTests.swift in Sources */, + 2E8DE7042C57FEB30032BF25 /* ExtensionAccessControlTests.swift in Sources */, 015F83FB2BF1448D0060A07E /* ReporterTests.swift in Sources */, + 2E8DE70E2C57FEB30032BF25 /* HoistPatternLetTests.swift in Sources */, + 2E8DE74E2C57FEB30032BF25 /* DocCommentsTests.swift in Sources */, + 2E8DE7522C57FEB30032BF25 /* MarkTypesTests.swift in Sources */, + 2E8DE75E2C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift in Sources */, + 2E8DE6FF2C57FEB30032BF25 /* DuplicateImportsTests.swift in Sources */, + 2E8DE7112C57FEB30032BF25 /* RedundantStaticSelfTests.swift in Sources */, + 2E8DE72C2C57FEB30032BF25 /* RedundantLetTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -868,28 +2042,139 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 01A0EAD51D5DC08A00A0A8E3 /* Rules.swift in Sources */, + 01A0EAD51D5DC08A00A0A8E3 /* FormatRule.swift in Sources */, + 2E2BAC6C2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BAD2C2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, 01A0EAD61D5DC08A00A0A8E3 /* Tokenizer.swift in Sources */, + 2E2BACC82C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BACA42C57F6DD00590239 /* Void.swift in Sources */, + 2E2BACF42C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BACE42C57F6DD00590239 /* GenericExtensions.swift in Sources */, + 2E2BAC942C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, + 2E2BACDC2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, + 2E2BAD942C57F6DD00590239 /* NumberFormatting.swift in Sources */, + 2E2BAD442C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAC502C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAD702C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BAC7C2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC002C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BAC242C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BAC1C2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, + 2E2BADB42C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BAC642C57F6DD00590239 /* StrongOutlets.swift in Sources */, 01567D30225B2BFD00B22D41 /* ParsingHelpers.swift in Sources */, 01B3987F1D7634A0009ADE61 /* Formatter.swift in Sources */, + 2E2BAD802C57F6DD00590239 /* SortTypealiases.swift in Sources */, + 2E2BADA02C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BACA82C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BAC742C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BAD242C57F6DD00590239 /* RedundantSelf.swift in Sources */, + 2E2BAC282C57F6DD00590239 /* RedundantNilInit.swift in Sources */, 01A0EAD41D5DC08A00A0A8E3 /* SwiftFormat.swift in Sources */, + 2E2BADAC2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, + 2E2BAD882C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, + 2E2BAD642C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, DD9AD39F2999FCC8001C2C0E /* GithubActionsLogReporter.swift in Sources */, + 2E2BAC402C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BAD782C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BAD4C2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, E4E4D3CA2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, 01BBD85A21DAA2A600457380 /* Globs.swift in Sources */, + 2E2BACBC2C57F6DD00590239 /* FileHeader.swift in Sources */, + 2E2BAC582C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, + 2E2BADA42C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BAC0C2C57F6DD00590239 /* Indent.swift in Sources */, + 2E2BAD7C2C57F6DD00590239 /* Acronyms.swift in Sources */, + 2E2BAD842C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BACF02C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAC2C2C57F6DD00590239 /* Todos.swift in Sources */, 08180DCF2C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */, 01045A92211988F100D2BE3D /* Inference.swift in Sources */, + 2E2BACB02C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAC902C57F6DD00590239 /* RedundantLet.swift in Sources */, + 2E2BAC982C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BAD602C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC482C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BACE82C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BADA82C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BACF82C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC442C57F6DD00590239 /* RedundantGet.swift in Sources */, 01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */, E4FABAD6202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BACCC2C57F6DD00590239 /* IsEmpty.swift in Sources */, D52F6A652A82E04600FE1448 /* GitFileInfo.swift in Sources */, + 2E2BAD382C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAC882C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BAD142C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BAD682C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAC842C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BAD182C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAD402C57F6DD00590239 /* MarkTypes.swift in Sources */, A3DF48262620E03600F45A5F /* JSONReporter.swift in Sources */, + 2E2BACD02C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, 01A8320724EC7F7600A9D0EB /* FormattingHelpers.swift in Sources */, + 2E2BAD582C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAD302C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAD902C57F6DD00590239 /* HoistTry.swift in Sources */, 01F17E831E25870700DCD359 /* CommandLine.swift in Sources */, + 2E2BACB42C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD542C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAC042C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAD002C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, + 2E2BACB82C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BACC42C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BAD3C2C57F6DD00590239 /* Braces.swift in Sources */, 015243E22B04B0A600F65221 /* Singularize.swift in Sources */, + 2E2BAC3C2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, + 2E2BAD9C2C57F6DD00590239 /* Specifiers.swift in Sources */, + 2E2BAD742C57F6DD00590239 /* TrailingClosures.swift in Sources */, + 2E2BAC4C2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC602C57F6DD00590239 /* BlockComments.swift in Sources */, + 2E2BAD0C2C57F6DD00590239 /* ApplicationMain.swift in Sources */, 01ACAE06220CD914003F3CCF /* Examples.swift in Sources */, + 2E2BAD102C57F6DD00590239 /* RedundantProperty.swift in Sources */, + 2E2BAC9C2C57F6DD00590239 /* RedundantInit.swift in Sources */, C2FFD1832BD13C9E00774F55 /* XMLReporter.swift in Sources */, + 2E2BAC5C2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BACD82C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC382C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BAD202C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BAD282C57F6DD00590239 /* PreferKeyPath.swift in Sources */, + 2E2BAD5C2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAC082C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BAC802C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAC182C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, 01A0EACD1D5DB5F500A0A8E3 /* main.swift in Sources */, + 2E2BACA02C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACFC2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BAB8D2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, + 2E2BAC782C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAC682C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, + 2E2BAC8C2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BAC102C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, + 2E2BACC02C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BACEC2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BACE02C57F6DD00590239 /* RedundantReturn.swift in Sources */, + 2E2BAC142C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BAC702C57F6DD00590239 /* AssertionFailures.swift in Sources */, + 2E2BAD342C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, DD9AD3A42999FCC8001C2C0E /* Reporter.swift in Sources */, + 2E2BAD1C2C57F6DD00590239 /* ModifierOrder.swift in Sources */, + 2E2BAD982C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAC342C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAD502C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BACAC2C57F6DD00590239 /* RedundantLetError.swift in Sources */, 01045A9A2119979400D2BE3D /* Arguments.swift in Sources */, + 2E2BAD482C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BAD082C57F6DD00590239 /* RedundantPattern.swift in Sources */, + 2E2BAD042C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BADB02C57F6DD00590239 /* RedundantType.swift in Sources */, + 2E2BACD42C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BAC542C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BAD8C2C57F6DD00590239 /* PropertyType.swift in Sources */, + 2E2BAC202C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, + 2E2BAD6C2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, + 2E2BAC302C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -897,31 +2182,142 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E2BAD6D2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, + 2E2BAC252C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BADB52C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BACB92C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BAD7D2C57F6DD00590239 /* Acronyms.swift in Sources */, + 2E2BAD752C57F6DD00590239 /* TrailingClosures.swift in Sources */, + 2E2BADAD2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, E4083191202C049200CAF11D /* SwiftFormat.swift in Sources */, E4FABAD7202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BAD792C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BAD412C57F6DD00590239 /* MarkTypes.swift in Sources */, + 2E2BAC092C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BAC0D2C57F6DD00590239 /* Indent.swift in Sources */, 015D3A562995A0340065B2D9 /* AboutViewController.swift in Sources */, + 2E2BACAD2C57F6DD00590239 /* RedundantLetError.swift in Sources */, + 2E2BACB12C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAD092C57F6DD00590239 /* RedundantPattern.swift in Sources */, E41CB5C52027700100C1BEDE /* FreeTextTableCellView.swift in Sources */, + 2E2BAD992C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAC852C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BACE52C57F6DD00590239 /* GenericExtensions.swift in Sources */, + 2E2BAC652C57F6DD00590239 /* StrongOutlets.swift in Sources */, E4872125201D980D0014845E /* BinarySelectionTableCellView.swift in Sources */, + 2E2BAD392C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAC392C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BADA52C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BACC12C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BACA12C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACD92C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC792C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAC3D2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, + 2E2BAC612C57F6DD00590239 /* BlockComments.swift in Sources */, + 2E2BACC52C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BAC8D2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BACA92C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BAC452C57F6DD00590239 /* RedundantGet.swift in Sources */, + 2E2BAD212C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BACED2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BAC692C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, E487211D201D885A0014845E /* RulesViewController.swift in Sources */, + 2E2BAC212C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, 01045A9B2119979400D2BE3D /* Arguments.swift in Sources */, + 2E2BAC712C57F6DD00590239 /* AssertionFailures.swift in Sources */, E4872114201D3B8C0014845E /* Tokenizer.swift in Sources */, + 2E2BAD612C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC752C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BACF92C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC512C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAC112C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, 015243E32B04B0A600F65221 /* Singularize.swift in Sources */, - E4872112201D3B860014845E /* Rules.swift in Sources */, + 2E2BAD552C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAD012C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, + 2E2BAD652C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, + 2E2BAC412C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BADA12C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BAD8D2C57F6DD00590239 /* PropertyType.swift in Sources */, + E4872112201D3B860014845E /* FormatRule.swift in Sources */, + 2E2BAB8E2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, + 2E2BAC892C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BACD52C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BACA52C57F6DD00590239 /* Void.swift in Sources */, + 2E2BAC492C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BAC812C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAD052C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BAD892C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, E4962DE0203F3CD500A02013 /* OptionsStore.swift in Sources */, + 2E2BAD2D2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, + 2E2BACDD2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, 01ACAE07220CD915003F3CCF /* Examples.swift in Sources */, + 2E2BAD692C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAD192C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAC052C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAC192C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, + 2E2BAC7D2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC592C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, E4872113201D3B890014845E /* Formatter.swift in Sources */, 08180DD02C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */, E4E4D3CB2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, + 2E2BAC992C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BACE92C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BAC4D2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC6D2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BAD592C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAD912C57F6DD00590239 /* HoistTry.swift in Sources */, + 2E2BADA92C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BAD452C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAC312C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, + 2E2BAD5D2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAD492C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BACF52C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BACCD2C57F6DD00590239 /* IsEmpty.swift in Sources */, + 2E2BAD3D2C57F6DD00590239 /* Braces.swift in Sources */, + 2E2BAD712C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BAC9D2C57F6DD00590239 /* RedundantInit.swift in Sources */, + 2E2BACB52C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD0D2C57F6DD00590239 /* ApplicationMain.swift in Sources */, + 2E2BAC2D2C57F6DD00590239 /* Todos.swift in Sources */, + 2E2BAD852C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BAC292C57F6DD00590239 /* RedundantNilInit.swift in Sources */, + 2E2BADB12C57F6DD00590239 /* RedundantType.swift in Sources */, 01BBD85B21DAA2A700457380 /* Globs.swift in Sources */, + 2E2BAD112C57F6DD00590239 /* RedundantProperty.swift in Sources */, 01A8320824EC7F7700A9D0EB /* FormattingHelpers.swift in Sources */, + 2E2BACFD2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BACF12C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAD4D2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, 01045A93211988F100D2BE3D /* Inference.swift in Sources */, E41CB5C32026CACD00C1BEDE /* ListSelectionTableCellView.swift in Sources */, + 2E2BAC912C57F6DD00590239 /* RedundantLet.swift in Sources */, + 2E2BAD292C57F6DD00590239 /* PreferKeyPath.swift in Sources */, E4872129201E3DD50014845E /* RulesStore.swift in Sources */, + 2E2BAC152C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BACBD2C57F6DD00590239 /* FileHeader.swift in Sources */, E41CB5BF2025761D00C1BEDE /* UserSelection.swift in Sources */, + 2E2BAC952C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, + 2E2BAC1D2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, + 2E2BACD12C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, + 2E2BAD312C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAD9D2C57F6DD00590239 /* Specifiers.swift in Sources */, E4872111201D3B830014845E /* Options.swift in Sources */, + 2E2BACC92C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BAD252C57F6DD00590239 /* RedundantSelf.swift in Sources */, 01A95BD3225BEDE400744931 /* ParsingHelpers.swift in Sources */, + 2E2BACE12C57F6DD00590239 /* RedundantReturn.swift in Sources */, D52F6A662A82E04600FE1448 /* GitFileInfo.swift in Sources */, + 2E2BAD952C57F6DD00590239 /* NumberFormatting.swift in Sources */, + 2E2BAD812C57F6DD00590239 /* SortTypealiases.swift in Sources */, 90C4B6CD1DA4B04A009EB000 /* AppDelegate.swift in Sources */, + 2E2BAC552C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BAD352C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, + 2E2BAC012C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BAC352C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAD512C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BAC5D2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BAD152C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BAD1D2C57F6DD00590239 /* ModifierOrder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -929,31 +2325,142 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E2BAD6E2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, + 2E2BAC262C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BADB62C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BACBA2C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BAD7E2C57F6DD00590239 /* Acronyms.swift in Sources */, + 2E2BAD762C57F6DD00590239 /* TrailingClosures.swift in Sources */, + 2E2BADAE2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, 01045AA0211A1EE300D2BE3D /* Arguments.swift in Sources */, 01BBD85C21DAA2A700457380 /* Globs.swift in Sources */, + 2E2BAD7A2C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BAD422C57F6DD00590239 /* MarkTypes.swift in Sources */, + 2E2BAC0A2C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BAC0E2C57F6DD00590239 /* Indent.swift in Sources */, 01045A9F2119D30D00D2BE3D /* Inference.swift in Sources */, + 2E2BACAE2C57F6DD00590239 /* RedundantLetError.swift in Sources */, + 2E2BACB22C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAD0A2C57F6DD00590239 /* RedundantPattern.swift in Sources */, 01A95BD2225BEDE300744931 /* ParsingHelpers.swift in Sources */, + 2E2BAD9A2C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAC862C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BACE62C57F6DD00590239 /* GenericExtensions.swift in Sources */, + 2E2BAC662C57F6DD00590239 /* StrongOutlets.swift in Sources */, 90C4B6E51DA4B059009EB000 /* SourceEditorExtension.swift in Sources */, + 2E2BAD3A2C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAC3A2C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BADA62C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BACC22C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BACA22C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACDA2C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC7A2C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAC3E2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, + 2E2BAC622C57F6DD00590239 /* BlockComments.swift in Sources */, + 2E2BACC62C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BAC8E2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BACAA2C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BAC462C57F6DD00590239 /* RedundantGet.swift in Sources */, + 2E2BAD222C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BACEE2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BAC6A2C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, 0142C77023C3FB6D005D5832 /* LintFileCommand.swift in Sources */, + 2E2BAC222C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, E4E4D3CC2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, + 2E2BAC722C57F6DD00590239 /* AssertionFailures.swift in Sources */, E4FABAD8202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BAD622C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC762C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BACFA2C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC522C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAC122C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, 015243E42B04B0A700F65221 /* Singularize.swift in Sources */, + 2E2BAD562C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAD022C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, + 2E2BAD662C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, + 2E2BAC422C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BADA22C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BAD8E2C57F6DD00590239 /* PropertyType.swift in Sources */, 9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */, + 2E2BAB8F2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, + 2E2BAC8A2C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BACD62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BACA62C57F6DD00590239 /* Void.swift in Sources */, + 2E2BAC4A2C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BAC822C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAD062C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BAD8A2C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, 90F16AFB1DA5ED9A00EB4EA1 /* CommandErrors.swift in Sources */, + 2E2BAD2E2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, + 2E2BACDE2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, 01A8320924EC7F7800A9D0EB /* FormattingHelpers.swift in Sources */, + 2E2BAD6A2C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAD1A2C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAC062C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAC1A2C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, + 2E2BAC7E2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC5A2C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, 018541CF1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift in Sources */, 08180DD12C4EB68000FD60FF /* DeclarationHelpers.swift in Sources */, E4962DE1203F3CD500A02013 /* OptionsStore.swift in Sources */, + 2E2BAC9A2C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BACEA2C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BAC4E2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC6E2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BAD5A2C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAD922C57F6DD00590239 /* HoistTry.swift in Sources */, + 2E2BADAA2C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BAD462C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAC322C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, + 2E2BAD5E2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAD4A2C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BACF62C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BACCE2C57F6DD00590239 /* IsEmpty.swift in Sources */, + 2E2BAD3E2C57F6DD00590239 /* Braces.swift in Sources */, + 2E2BAD722C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BAC9E2C57F6DD00590239 /* RedundantInit.swift in Sources */, + 2E2BACB62C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD0E2C57F6DD00590239 /* ApplicationMain.swift in Sources */, + 2E2BAC2E2C57F6DD00590239 /* Todos.swift in Sources */, + 2E2BAD862C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BAC2A2C57F6DD00590239 /* RedundantNilInit.swift in Sources */, + 2E2BADB22C57F6DD00590239 /* RedundantType.swift in Sources */, 9028F7851DA4B435009FE5B4 /* Formatter.swift in Sources */, + 2E2BAD122C57F6DD00590239 /* RedundantProperty.swift in Sources */, E487212A201E3DD50014845E /* RulesStore.swift in Sources */, + 2E2BACFE2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BACF22C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAD4E2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, 01F3DF8E1DB9FD3F00454944 /* Options.swift in Sources */, 9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */, + 2E2BAC922C57F6DD00590239 /* RedundantLet.swift in Sources */, + 2E2BAD2A2C57F6DD00590239 /* PreferKeyPath.swift in Sources */, B9C4F55C2387FA3E0088DBEE /* SupportedContentUTIs.swift in Sources */, + 2E2BAC162C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BACBE2C57F6DD00590239 /* FileHeader.swift in Sources */, 90C4B6E71DA4B059009EB000 /* FormatSelectionCommand.swift in Sources */, + 2E2BAC962C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, + 2E2BAC1E2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, + 2E2BACD22C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, + 2E2BAD322C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAD9E2C57F6DD00590239 /* Specifiers.swift in Sources */, 90F16AF81DA5EB4600EB4EA1 /* FormatFileCommand.swift in Sources */, + 2E2BACCA2C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BAD262C57F6DD00590239 /* RedundantSelf.swift in Sources */, 01ACAE08220CD916003F3CCF /* Examples.swift in Sources */, + 2E2BACE22C57F6DD00590239 /* RedundantReturn.swift in Sources */, D52F6A672A82E04600FE1448 /* GitFileInfo.swift in Sources */, - 9028F7861DA4B435009FE5B4 /* Rules.swift in Sources */, + 2E2BAD962C57F6DD00590239 /* NumberFormatting.swift in Sources */, + 2E2BAD822C57F6DD00590239 /* SortTypealiases.swift in Sources */, + 9028F7861DA4B435009FE5B4 /* FormatRule.swift in Sources */, + 2E2BAC562C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BAD362C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, + 2E2BAC022C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BAC362C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAD522C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BAC5E2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BAD162C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BAD1E2C57F6DD00590239 /* ModifierOrder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -995,6 +2502,7 @@ CLANG_ENABLE_CODE_COVERAGE = NO; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; + "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; ENABLE_TESTABILITY = YES; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1014,8 +2522,10 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_CODE_COVERAGE = NO; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; + "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; ENABLE_TESTABILITY = YES; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index e061ba99..dee617d1 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -398,12 +398,12 @@ class CommandLineTests: XCTestCase { Package.swift #bar #baz - Sources/Rules.swift + Sources/FormatRule.swift CommandLineTool/*.swift """ XCTAssertEqual(try parseFileList(source, in: projectDirectory.path), [ URL(fileURLWithPath: "\(projectDirectory.path)/Package.swift"), - URL(fileURLWithPath: "\(projectDirectory.path)/Sources/Rules.swift"), + URL(fileURLWithPath: "\(projectDirectory.path)/Sources/FormatRule.swift"), URL(fileURLWithPath: "\(projectDirectory.path)/CommandLineTool/main.swift"), ]) } diff --git a/Tests/FormatterTests.swift b/Tests/FormatterTests.swift index 69fba48b..a9dc5ffc 100644 --- a/Tests/FormatterTests.swift +++ b/Tests/FormatterTests.swift @@ -392,7 +392,7 @@ class FormatterTests: XCTestCase { .linebreak("\n", 3), .endOfScope("}"), ]) - FormatRules.blankLinesAtStartOfScope.apply(with: formatter) + FormatRule.blankLinesAtStartOfScope.apply(with: formatter) XCTAssertEqual(formatter.tokens, [ .identifier("foo"), .space(" "), @@ -427,7 +427,7 @@ class FormatterTests: XCTestCase { """) XCTAssertEqual(try format( input, - rules: [FormatRules.consecutiveSpaces], + rules: [.consecutiveSpaces], range: 10 ..< 13 ), output1) let output2 = """ @@ -437,7 +437,7 @@ class FormatterTests: XCTestCase { """ XCTAssertEqual(try sourceCode(for: format( input, - rules: [FormatRules.blankLinesAtStartOfScope], + rules: [.blankLinesAtStartOfScope], range: 6 ..< 9 )), output2) } diff --git a/Tests/GlobsTests.swift b/Tests/GlobsTests.swift index 543461f7..66622323 100644 --- a/Tests/GlobsTests.swift +++ b/Tests/GlobsTests.swift @@ -20,14 +20,14 @@ class GlobsTests: XCTestCase { } func testExpandPathWithWildcardInMiddle() { - let path = "Rule*.swift" + let path = "FormatRul*.swift" let directory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Sources") XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 1) } func testExpandPathWithSingleCharacterWildcardInMiddle() { - let path = "Rule?.swift" + let path = "FormatRul?.swift" let directory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Sources") XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 1) @@ -85,14 +85,14 @@ class GlobsTests: XCTestCase { func testExpandPathWithWildcardAtStart() { let path = "*Tests.swift" let directory = URL(fileURLWithPath: #file).deletingLastPathComponent() - XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 16) + XCTAssertGreaterThanOrEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 15) } func testExpandPathWithSubdirectoryAndWildcard() { let path = "Tests/*Tests.swift" let directory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent() - XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 16) + XCTAssertGreaterThanOrEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 15) } func testSingleWildcardDoesNotMatchDirectorySlash() { diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 1d72d844..550d3720 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -28,6 +28,21 @@ private let rulesURL = private let rulesFile = try! String(contentsOf: rulesURL, encoding: .utf8) +private let ruleRegistryURL = + projectDirectory.appendingPathComponent("Sources/RuleRegistry.generated.swift") + +private let allRuleFiles: [URL] = { + var rulesFiles: [URL] = [] + let rulesDirectory = projectDirectory.appendingPathComponent("Sources/Rules") + _ = enumerateFiles(withInputURL: rulesDirectory) { ruleFileURL, _, _ in + { + guard ruleFileURL.pathExtension == "swift" else { return } + rulesFiles.append(ruleFileURL) + } + } + return rulesFiles +}() + private let swiftFormatVersion: String = { let string = try! String(contentsOf: projectURL) let start = string.range(of: "MARKETING_VERSION = ")!.upperBound @@ -143,118 +158,122 @@ class MetadataTests: XCTestCase { // MARK: options func testRulesOptions() throws { + var allOptions = Set(formattingArguments).subtracting(deprecatedArguments) + var allSharedOptions = allOptions var optionsByProperty = [String: OptionDescriptor]() for descriptor in Descriptors.formatting.reversed() { optionsByProperty[descriptor.propertyName] = descriptor } - let rulesFile = projectDirectory.appendingPathComponent("Sources/Rules.swift") - let rulesSource = try String(contentsOf: rulesFile, encoding: .utf8) - let tokens = tokenize(rulesSource) - let formatter = Formatter(tokens) - var rulesByOption = [String: String]() - var allOptions = Set(formattingArguments).subtracting(deprecatedArguments) - var allSharedOptions = allOptions - formatter.forEach(.identifier("FormatRule")) { i, _ in - guard formatter.next(.nonSpaceOrLinebreak, after: i) == .startOfScope("("), - case let .identifier(name)? = formatter.last(.identifier, before: i), - let scopeStart = formatter.index(of: .startOfScope("{"), after: i), - let scopeEnd = formatter.index(of: .endOfScope("}"), after: scopeStart), - let rule = FormatRules.byName[name] - else { - return - } - for option in rule.options where !rule.isDeprecated { - if let oldName = rulesByOption[option] { - XCTFail("\(option) set as (non-shared) option for both \(name) and \(oldName)") - } - rulesByOption[option] = name - } - let ruleOptions = rule.options + rule.sharedOptions - allOptions.subtract(rule.options) - allSharedOptions.subtract(ruleOptions) - var referencedOptions = [OptionDescriptor]() - for index in scopeStart + 1 ..< scopeEnd { - guard formatter.token(at: index - 1) == .operator(".", .infix), - formatter.token(at: index - 2) == .identifier("formatter") + for rulesFile in allRuleFiles { + let rulesSource = try String(contentsOf: rulesFile, encoding: .utf8) + let tokens = tokenize(rulesSource) + let formatter = Formatter(tokens) + var rulesByOption = [String: String]() + formatter.forEach(.identifier("FormatRule")) { i, _ in + guard let nextToken = formatter.next(.nonSpaceOrComment, after: i), + [.startOfScope("("), .operator("=", .infix)].contains(nextToken), + case let .identifier(name)? = formatter.last(.identifier, before: i), + let scopeStart = formatter.index(of: .startOfScope("{"), after: i), + let scopeEnd = formatter.index(of: .endOfScope("}"), after: scopeStart), + let rule = FormatRules.byName[name] else { - continue + return } - switch formatter.tokens[index] { - case .identifier("spaceEquivalentToWidth"), - .identifier("spaceEquivalentToTokens"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, - ] - case .identifier("tokenLength"): - referencedOptions += [Descriptors.indent, Descriptors.tabWidth] - case .identifier("lineLength"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, - ] - case .identifier("isCommentedCode"): - referencedOptions.append(Descriptors.indent) - case .identifier("insertLinebreak"), .identifier("linebreakToken"): - referencedOptions.append(Descriptors.linebreak) - case .identifier("wrapCollectionsAndArguments"): - referencedOptions += [ - Descriptors.wrapArguments, Descriptors.wrapParameters, Descriptors.wrapCollections, - Descriptors.closingParenPosition, Descriptors.callSiteClosingParenPosition, - Descriptors.linebreak, Descriptors.truncateBlankLines, - Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, Descriptors.maxWidth, - Descriptors.assetLiteralWidth, Descriptors.wrapReturnType, Descriptors.wrapEffects, - Descriptors.wrapConditions, Descriptors.wrapTypealiases, Descriptors.wrapTernaryOperators, - ] - case .identifier("wrapStatementBody"): - referencedOptions += [Descriptors.indent, Descriptors.linebreak] - case .identifier("indexWhereLineShouldWrapInLine"), .identifier("indexWhereLineShouldWrap"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, - Descriptors.noWrapOperators, - ] - case .identifier("modifierOrder"): - referencedOptions.append(Descriptors.modifierOrder) - case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix): - if case let .identifier(property)? = formatter.token(at: index + 2), - let option = optionsByProperty[property] - { - referencedOptions.append(option) + for option in rule.options where !rule.isDeprecated { + if let oldName = rulesByOption[option] { + XCTFail("\(option) set as (non-shared) option for both \(name) and \(oldName)") } - case .identifier("organizeDeclaration"): - referencedOptions += [ - Descriptors.categoryMarkComment, - Descriptors.markCategories, - Descriptors.beforeMarks, - Descriptors.lifecycleMethods, - Descriptors.organizeTypes, - Descriptors.organizeStructThreshold, - Descriptors.organizeClassThreshold, - Descriptors.organizeEnumThreshold, - Descriptors.organizeExtensionThreshold, - Descriptors.lineAfterMarks, - Descriptors.organizationMode, - Descriptors.alphabeticallySortedDeclarationPatterns, - Descriptors.visibilityOrder, - Descriptors.typeOrder, - Descriptors.customVisibilityMarks, - Descriptors.customTypeMarks, - ] - case .identifier("removeSelf"): - referencedOptions += [ - Descriptors.selfRequired, - ] - default: - continue + rulesByOption[option] = name + } + let ruleOptions = rule.options + rule.sharedOptions + allOptions.subtract(rule.options) + allSharedOptions.subtract(ruleOptions) + var referencedOptions = [OptionDescriptor]() + for index in scopeStart + 1 ..< scopeEnd { + guard formatter.token(at: index - 1) == .operator(".", .infix), + formatter.token(at: index - 2) == .identifier("formatter") + else { + continue + } + switch formatter.tokens[index] { + case .identifier("spaceEquivalentToWidth"), + .identifier("spaceEquivalentToTokens"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, + ] + case .identifier("tokenLength"): + referencedOptions += [Descriptors.indent, Descriptors.tabWidth] + case .identifier("lineLength"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, + ] + case .identifier("isCommentedCode"): + referencedOptions.append(Descriptors.indent) + case .identifier("insertLinebreak"), .identifier("linebreakToken"): + referencedOptions.append(Descriptors.linebreak) + case .identifier("wrapCollectionsAndArguments"): + referencedOptions += [ + Descriptors.wrapArguments, Descriptors.wrapParameters, Descriptors.wrapCollections, + Descriptors.closingParenPosition, Descriptors.callSiteClosingParenPosition, + Descriptors.linebreak, Descriptors.truncateBlankLines, + Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, Descriptors.maxWidth, + Descriptors.assetLiteralWidth, Descriptors.wrapReturnType, Descriptors.wrapEffects, + Descriptors.wrapConditions, Descriptors.wrapTypealiases, Descriptors.wrapTernaryOperators, + ] + case .identifier("wrapStatementBody"): + referencedOptions += [Descriptors.indent, Descriptors.linebreak] + case .identifier("indexWhereLineShouldWrapInLine"), .identifier("indexWhereLineShouldWrap"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, + Descriptors.noWrapOperators, + ] + case .identifier("modifierOrder"): + referencedOptions.append(Descriptors.modifierOrder) + case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix): + if case let .identifier(property)? = formatter.token(at: index + 2), + let option = optionsByProperty[property] + { + referencedOptions.append(option) + } + case .identifier("organizeDeclaration"): + referencedOptions += [ + Descriptors.categoryMarkComment, + Descriptors.markCategories, + Descriptors.beforeMarks, + Descriptors.lifecycleMethods, + Descriptors.organizeTypes, + Descriptors.organizeStructThreshold, + Descriptors.organizeClassThreshold, + Descriptors.organizeEnumThreshold, + Descriptors.organizeExtensionThreshold, + Descriptors.lineAfterMarks, + Descriptors.organizationMode, + Descriptors.alphabeticallySortedDeclarationPatterns, + Descriptors.visibilityOrder, + Descriptors.typeOrder, + Descriptors.customVisibilityMarks, + Descriptors.customTypeMarks, + ] + case .identifier("removeSelf"): + referencedOptions += [ + Descriptors.selfRequired, + ] + default: + continue + } + } + + for option in referencedOptions { + XCTAssert(ruleOptions.contains(option.argumentName) || option.isDeprecated, + "\(option.argumentName) not listed in \(name) rule") + } + for argName in ruleOptions { + XCTAssert(referencedOptions.contains { $0.argumentName == argName }, + "\(argName) not used in \(name) rule") } - } - for option in referencedOptions { - XCTAssert(ruleOptions.contains(option.argumentName) || option.isDeprecated, - "\(option.argumentName) not listed in \(name) rule") - } - for argName in ruleOptions { - XCTAssert(referencedOptions.contains { $0.argumentName == argName }, - "\(argName) not used in \(name) rule") } } + XCTAssert(allSharedOptions.isEmpty, "Options \(allSharedOptions.joined(separator: ",")) not shared by any rule)") XCTAssert(allOptions.isEmpty, "Options \(allSharedOptions.joined(separator: ",")) not owned by any rule)") } @@ -298,8 +317,12 @@ class MetadataTests: XCTestCase { // MARK: keywords func testContextualKeywordsReferencedCorrectly() throws { - for file in ["Rules", "ParsingHelpers", "FormattingHelpers"] { - let sourceFile = projectDirectory.appendingPathComponent("Sources/\(file).swift") + let filesToVerify = allRuleFiles + [ + projectDirectory.appendingPathComponent("Sources/ParsingHelpers.swift"), + projectDirectory.appendingPathComponent("Sources/FormattingHelpers.swift"), + ] + + for sourceFile in filesToVerify { let fileSource = try String(contentsOf: sourceFile, encoding: .utf8) let tokens = tokenize(fileSource) let formatter = Formatter(tokens) @@ -322,7 +345,7 @@ class MetadataTests: XCTestCase { } guard keywords.contains(keyword) || keyword.hasPrefix("#") || keyword.hasPrefix("@") else { let line = formatter.originalLine(at: i) - XCTFail("'\(keyword)' referenced on line \(line) of '\(file).swift' is not a valid Swift keyword. " + XCTFail("'\(keyword)' referenced on line \(line) of '\(sourceFile)' is not a valid Swift keyword. " + "Contextual keywords should be referenced with `.identifier(...)`") return } @@ -338,16 +361,6 @@ class MetadataTests: XCTestCase { } } - // MARK: order - - func testRuleOrderNamesAreValid() { - for rule in FormatRules.all { - for name in rule.orderAfter { - XCTAssert(FormatRules.byName[name] != nil, "\(name) rule does not exist") - } - } - } - // MARK: releases func testLatestVersionInChangelog() { @@ -385,3 +398,103 @@ class MetadataTests: XCTestCase { } } } + +/// The cached result from the first run of `generateRuleRegistryIfNecessary()` +private var cachedGenerateRuleRegistryResult: Result? + +extension _FormatRules { + /// Generates `RuleRegistry.generated.swift` if it hasn't been generated yet for this test run. + func generateRuleRegistryIfNecessary() throws { + switch cachedGenerateRuleRegistryResult { + case .success: + break + + case let .failure(error): + throw error + + case .none: + do { + try generateRuleRegistry() + cachedGenerateRuleRegistryResult = .success(()) + } catch { + cachedGenerateRuleRegistryResult = .failure(error) + throw error + } + } + } + + private func generateRuleRegistry() throws { + let validatedRules = try validatedRuleNames() + let ruleRegistryContent = generateRuleRegistryContent(for: validatedRules) + let currentRuleRegistryContent = try String(contentsOf: ruleRegistryURL) + + if ruleRegistryContent != currentRuleRegistryContent { + try ruleRegistryContent.write(to: ruleRegistryURL, atomically: true, encoding: .utf8) + fatalError("Updated rule registry. You can now re-run the test case or test suite.") + } + } + + /// Finds all of the rules defines in `Sources/Rules` and validates that it matches the + /// expected scheme, where each file defines exactly one `FormatRule` with the same name. + private func validatedRuleNames() throws -> [String] { + try allRuleFiles.map { ruleFile in + let titleCaseRuleName = ruleFile.lastPathComponent.replacingOccurrences(of: ".swift", with: "") + let camelCaseRuleName = titleCaseRuleName.first!.lowercased() + titleCaseRuleName.dropFirst() + try validateRuleImplementation(for: camelCaseRuleName, in: ruleFile) + return camelCaseRuleName + } + } + + /// Generates the content of the `RuleRegistry.generated.swift` file + private func generateRuleRegistryContent(for rules: [String]) -> String { + var ruleRegistryContents = """ + // + // RuleRegistry.generated.swift + // SwiftFormat + // + // Created by Cal Stephens on 7/27/24. + // Copyright © 2024 Nick Lockwood. All rights reserved. + // + + /// All of the rules defined in the Rules directory. + /// **Generated automatically when running tests. Do not modify.** + let ruleRegistry: [String: FormatRule] = [\n + """ + + for rule in rules.sorted() { + ruleRegistryContents.append(""" + "\(rule)": .\(rule),\n + """) + } + + ruleRegistryContents.append(""" + ]\n + """) + + return ruleRegistryContents + } + + /// Validates that the given file defines exactly one `FormatRule` with the expected name + private func validateRuleImplementation(for expectedRuleName: String, in file: URL) throws { + let fileContents = try String(contentsOf: file) + let formatter = Formatter(tokenize(fileContents)) + + // Find all rules defined in the file, like `let ruleName = FormatRule(` or `let ruleName: FormatRule = ...`. + var definedRules: [String] = [] + formatter.forEach(.identifier("FormatRule")) { index, _ in + guard let nextToken = formatter.next(.nonSpaceOrComment, after: index), + [.startOfScope("("), .operator("=", .infix)].contains(nextToken), + let declarationKeyword = formatter.indexOfLastSignificantKeyword(at: index), + let ruleNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: declarationKeyword) + else { return } + + definedRules.append(formatter.tokens[ruleNameIndex].string) + } + + if definedRules != [expectedRuleName] { + fatalError(""" + \(file.lastPathComponent) must define a single FormatRule named \(expectedRuleName). Currently defines rules: \(definedRules). + """) + } + } +} diff --git a/Tests/ReporterTests.swift b/Tests/ReporterTests.swift index 926bba84..2f3e2eb2 100644 --- a/Tests/ReporterTests.swift +++ b/Tests/ReporterTests.swift @@ -35,7 +35,7 @@ import XCTest class ReporterTests: XCTestCase { func testWrite() throws { let reporter = GithubActionsLogReporter(environment: ["GITHUB_WORKSPACE": "/bar"]) - let rule = FormatRules.consecutiveSpaces + let rule = FormatRule.consecutiveSpaces reporter.report([ .init(line: 1, rule: rule, filePath: "/bar/foo.swift"), .init(line: 2, rule: rule, filePath: "/bar/foo.swift"), diff --git a/Tests/Rules/AcronymsTests.swift b/Tests/Rules/AcronymsTests.swift new file mode 100644 index 00000000..0798b03a --- /dev/null +++ b/Tests/Rules/AcronymsTests.swift @@ -0,0 +1,78 @@ +// +// AcronymsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AcronymsTests: XCTestCase { + func testUppercaseAcronyms() { + let input = """ + let url: URL + let destinationUrl: URL + let id: ID + let screenId = "screenId" // We intentionally don't change the content of strings + let validUrls: Set + let validUrlschemes: Set + + let uniqueIdentifier = UUID() + + /// Opens Urls based on their scheme + struct UrlRouter {} + + /// The Id of a screen that can be displayed in the app + struct ScreenId {} + """ + + let output = """ + let url: URL + let destinationURL: URL + let id: ID + let screenID = "screenId" // We intentionally don't change the content of strings + let validURLs: Set + let validUrlschemes: Set + + let uniqueIdentifier = UUID() + + /// Opens URLs based on their scheme + struct URLRouter {} + + /// The ID of a screen that can be displayed in the app + struct ScreenID {} + """ + + testFormatting(for: input, output, rule: .acronyms, exclude: [.propertyType]) + } + + func testUppercaseCustomAcronym() { + let input = """ + let url: URL + let destinationUrl: URL + let pngData: Data + let imageInPngFormat: UIImage + """ + + let output = """ + let url: URL + let destinationUrl: URL + let pngData: Data + let imageInPNGFormat: UIImage + """ + + testFormatting(for: input, output, rule: .acronyms, options: FormatOptions(acronyms: ["png"])) + } + + func testDisableUppercaseAcronym() { + let input = """ + // swiftformat:disable:next acronyms + typeNotOwnedByAuthor.destinationUrl = URL() + typeOwnedByAuthor.destinationURL = URL() + """ + + testFormatting(for: input, rule: .acronyms) + } +} diff --git a/Tests/Rules/AndOperatorTests.swift b/Tests/Rules/AndOperatorTests.swift new file mode 100644 index 00000000..ecfee639 --- /dev/null +++ b/Tests/Rules/AndOperatorTests.swift @@ -0,0 +1,184 @@ +// +// AndOperatorTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/14/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AndOperatorTests: XCTestCase { + func testIfAndReplaced() { + let input = "if true && true {}" + let output = "if true, true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testGuardAndReplaced() { + let input = "guard true && true\nelse { return }" + let output = "guard true, true\nelse { return }" + testFormatting(for: input, output, rule: .andOperator, + exclude: [.wrapConditionalBodies]) + } + + func testWhileAndReplaced() { + let input = "while true && true {}" + let output = "while true, true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testIfDoubleAndReplaced() { + let input = "if true && true && true {}" + let output = "if true, true, true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testIfAndParensReplaced() { + let input = "if true && (true && true) {}" + let output = "if true, (true && true) {}" + testFormatting(for: input, output, rule: .andOperator, + exclude: [.redundantParens]) + } + + func testIfFunctionAndReplaced() { + let input = "if functionReturnsBool() && true {}" + let output = "if functionReturnsBool(), true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testNoReplaceIfOrAnd() { + let input = "if foo || bar && baz {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceIfAndOr() { + let input = "if foo && bar || baz {}" + testFormatting(for: input, rule: .andOperator) + } + + func testIfAndReplacedInFunction() { + let input = "func someFunc() { if bar && baz {} }" + let output = "func someFunc() { if bar, baz {} }" + testFormatting(for: input, output, rule: .andOperator) + } + + func testNoReplaceIfCaseLetAnd() { + let input = "if case let a = foo && bar {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceWhileCaseLetAnd() { + let input = "while case let a = foo && bar {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceRepeatWhileAnd() { + let input = """ + repeat {} while true && !false + foo {} + """ + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceIfLetAndLetAnd() { + let input = "if let a = b && c, let d = e && f {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceIfTryAnd() { + let input = "if try true && explode() {}" + testFormatting(for: input, rule: .andOperator) + } + + func testHandleAndAtStartOfLine() { + let input = "if a == b\n && b == c {}" + let output = "if a == b,\n b == c {}" + testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) + } + + func testHandleAndAtStartOfLineAfterComment() { + let input = "if a == b // foo\n && b == c {}" + let output = "if a == b, // foo\n b == c {}" + testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) + } + + func testNoReplaceAndOperatorWhereGenericsAmbiguous() { + let input = "if x < y && z > (a * b) {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceAndOperatorWhereGenericsAmbiguous2() { + let input = "if x < y && z && w > (a * b) {}" + let output = "if x < y, z && w > (a * b) {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testAndOperatorCrash() { + let input = """ + DragGesture().onChanged { gesture in + if gesture.translation.width < 50 && gesture.translation.height > 50 { + offset = gesture.translation + } + } + """ + let output = """ + DragGesture().onChanged { gesture in + if gesture.translation.width < 50, gesture.translation.height > 50 { + offset = gesture.translation + } + } + """ + testFormatting(for: input, output, rule: .andOperator) + } + + func testNoReplaceAndInViewBuilder() { + let input = """ + SomeView { + if foo == 5 && bar { + Text("5") + } else { + Text("Not 5") + } + } + """ + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceAndInViewBuilder2() { + let input = """ + var body: some View { + ZStack { + if self.foo && self.bar { + self.closedPath + } + } + } + """ + testFormatting(for: input, rule: .andOperator) + } + + func testReplaceAndInViewBuilderInSwift5_3() { + let input = """ + SomeView { + if foo == 5 && bar { + Text("5") + } else { + Text("Not 5") + } + } + """ + let output = """ + SomeView { + if foo == 5, bar { + Text("5") + } else { + Text("Not 5") + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .andOperator, options: options) + } +} diff --git a/Tests/Rules/AnyObjectProtocolTests.swift b/Tests/Rules/AnyObjectProtocolTests.swift new file mode 100644 index 00000000..ab15d4b3 --- /dev/null +++ b/Tests/Rules/AnyObjectProtocolTests.swift @@ -0,0 +1,52 @@ +// +// AnyObjectProtocolTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/23/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AnyObjectProtocolTests: XCTestCase { + func testClassReplacedByAnyObject() { + let input = "protocol Foo: class {}" + let output = "protocol Foo: AnyObject {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) + } + + func testClassReplacedByAnyObjectWithOtherProtocols() { + let input = "protocol Foo: class, Codable {}" + let output = "protocol Foo: AnyObject, Codable {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) + } + + func testClassReplacedByAnyObjectImmediatelyAfterImport() { + let input = "import Foundation\nprotocol Foo: class {}" + let output = "import Foundation\nprotocol Foo: AnyObject {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options, + exclude: [.blankLineAfterImports]) + } + + func testClassDeclarationNotReplacedByAnyObject() { + let input = "class Foo: Codable {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, rule: .anyObjectProtocol, options: options) + } + + func testClassImportNotReplacedByAnyObject() { + let input = "import class Foo.Bar" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, rule: .anyObjectProtocol, options: options) + } + + func testClassNotReplacedByAnyObjectIfSwiftVersionLessThan4_1() { + let input = "protocol Foo: class {}" + let options = FormatOptions(swiftVersion: "4.0") + testFormatting(for: input, rule: .anyObjectProtocol, options: options) + } +} diff --git a/Tests/Rules/ApplicationMainTests.swift b/Tests/Rules/ApplicationMainTests.swift new file mode 100644 index 00000000..fb94db92 --- /dev/null +++ b/Tests/Rules/ApplicationMainTests.swift @@ -0,0 +1,47 @@ +// +// ApplicationMainTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 5/20/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ApplicationMainTests: XCTestCase { + func testUIApplicationMainReplacedByMain() { + let input = """ + @UIApplicationMain + class AppDelegate: UIResponder, UIApplicationDelegate {} + """ + let output = """ + @main + class AppDelegate: UIResponder, UIApplicationDelegate {} + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .applicationMain, options: options) + } + + func testNSApplicationMainReplacedByMain() { + let input = """ + @NSApplicationMain + class AppDelegate: NSObject, NSApplicationDelegate {} + """ + let output = """ + @main + class AppDelegate: NSObject, NSApplicationDelegate {} + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .applicationMain, options: options) + } + + func testNSApplicationMainNotReplacedInSwift5_2() { + let input = """ + @NSApplicationMain + class AppDelegate: NSObject, NSApplicationDelegate {} + """ + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .applicationMain, options: options) + } +} diff --git a/Tests/Rules/AssertionFailuresTests.swift b/Tests/Rules/AssertionFailuresTests.swift new file mode 100644 index 00000000..52537a3e --- /dev/null +++ b/Tests/Rules/AssertionFailuresTests.swift @@ -0,0 +1,62 @@ +// +// AssertionFailuresTests.swift +// SwiftFormatTests +// +// Created by sanjanapruthi on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AssertionFailuresTests: XCTestCase { + func testAssertionFailuresForAssertFalse() { + let input = "assert(false)" + let output = "assertionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertFalseWithSpaces() { + let input = "assert ( false )" + let output = "assertionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertFalseWithLinebreaks() { + let input = """ + assert( + false + ) + """ + let output = "assertionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertTrue() { + let input = "assert(true)" + testFormatting(for: input, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertFalseWithArgs() { + let input = "assert(false, msg, 20, 21)" + let output = "assertionFailure(msg, 20, 21)" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForPreconditionFalse() { + let input = "precondition(false)" + let output = "preconditionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForPreconditionTrue() { + let input = "precondition(true)" + testFormatting(for: input, rule: .assertionFailures) + } + + func testAssertionFailuresForPreconditionFalseWithArgs() { + let input = "precondition(false, msg, 0, 1)" + let output = "preconditionFailure(msg, 0, 1)" + testFormatting(for: input, output, rule: .assertionFailures) + } +} diff --git a/Tests/Rules/BlankLineAfterImportsTests.swift b/Tests/Rules/BlankLineAfterImportsTests.swift new file mode 100644 index 00000000..13caf373 --- /dev/null +++ b/Tests/Rules/BlankLineAfterImportsTests.swift @@ -0,0 +1,110 @@ +// +// BlankLineAfterImportsTests.swift +// SwiftFormatTests +// +// Created by Tsungyu Yu on 5/1/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLineAfterImportsTests: XCTestCase { + func testBlankLineAfterImport() { + let input = """ + import ModuleA + @testable import ModuleB + import ModuleC + @testable import ModuleD + @_exported import ModuleE + @_implementationOnly import ModuleF + @_spi(SPI) import ModuleG + @_spiOnly import ModuleH + @preconcurrency import ModuleI + class foo {} + """ + let output = """ + import ModuleA + @testable import ModuleB + import ModuleC + @testable import ModuleD + @_exported import ModuleE + @_implementationOnly import ModuleF + @_spi(SPI) import ModuleG + @_spiOnly import ModuleH + @preconcurrency import ModuleI + + class foo {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } + + func testBlankLinesBetweenConditionalImports() { + let input = """ + #if foo + import ModuleA + #else + import ModuleB + #endif + import ModuleC + func foo() {} + """ + let output = """ + #if foo + import ModuleA + #else + import ModuleB + #endif + import ModuleC + + func foo() {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } + + func testBlankLinesBetweenNestedConditionalImports() { + let input = """ + #if foo + import ModuleA + #if bar + import ModuleB + #endif + #else + import ModuleC + #endif + import ModuleD + func foo() {} + """ + let output = """ + #if foo + import ModuleA + #if bar + import ModuleB + #endif + #else + import ModuleC + #endif + import ModuleD + + func foo() {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } + + func testBlankLineAfterScopedImports() { + let input = """ + internal import UIKit + internal import Foundation + private import Time + public class Foo {} + """ + let output = """ + internal import UIKit + internal import Foundation + private import Time + + public class Foo {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } +} diff --git a/Tests/Rules/BlankLineAfterSwitchCaseTests.swift b/Tests/Rules/BlankLineAfterSwitchCaseTests.swift new file mode 100644 index 00000000..4d851051 --- /dev/null +++ b/Tests/Rules/BlankLineAfterSwitchCaseTests.swift @@ -0,0 +1,213 @@ +// +// BlankLineAfterSwitchCaseTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 2/1/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLineAfterSwitchCaseTests: XCTestCase { + func testAddsBlankLineAfterMultilineSwitchCases() { + let input = """ + func handle(_ action: SpaceshipAction) { + switch action { + // The warp drive can be engaged by pressing a button on the control panel + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + // Triggered automatically whenever we detect an energy blast was fired in our direction + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + """ + + let output = """ + func handle(_ action: SpaceshipAction) { + switch action { + // The warp drive can be engaged by pressing a button on the control panel + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + // Triggered automatically whenever we detect an energy blast was fired in our direction + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + """ + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase) + } + + func testRemovesBlankLineAfterLastSwitchCase() { + let input = """ + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArticialLife() + + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + + } + } + """ + + let output = """ + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArticialLife() + + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + """ + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase) + } + + func testDoesntAddBlankLineAfterSingleLineSwitchCase() { + let input = """ + var planetType: PlanetType { + switch self { + case .mercury, .venus, .earth, .mars: + // The terrestrial planets are smaller and have a solid, rocky surface + .terrestrial + case .jupiter, .saturn, .uranus, .neptune: + // The gas giants are huge and lack a solid surface + .gasGiant + } + } + + var planetType: PlanetType { + switch self { + // The terrestrial planets are smaller and have a solid, rocky surface + case .mercury, .venus, .earth, .mars: + .terrestrial + // The gas giants are huge and lack a solid surface + case .jupiter, .saturn, .uranus, .neptune: + .gasGiant + } + } + + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + case .venus: + "Venus" + // The best planet, where everything cool happens + case .earth: + "Earth" + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + case .jupiter: + "Jupiter" + case .saturn: + // Other planets have rings, but satun's are the best. + // It's rings are the only once that are usually visible in photos. + "Saturn" + case .uranus: + /* + * The pronunciation of this planet's name is subject of scholarly debate + */ + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + testFormatting(for: input, rule: .blankLineAfterSwitchCase, exclude: [.sortSwitchCases, .wrapSwitchCases, .blockComments]) + } + + func testMixedSingleLineAndMultiLineCases() { + let input = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase, exclude: [.consistentSwitchCaseSpacing]) + } + + func testAllowsBlankLinesAfterSingleLineCases() { + let input = """ + switch action { + case .engageWarpDrive: + warpDrive.engage() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case let .scanPlanet(planet): + scanner.scan(planet) + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + testFormatting(for: input, rule: .blankLineAfterSwitchCase) + } +} diff --git a/Tests/Rules/BlankLinesAroundMarkTests.swift b/Tests/Rules/BlankLinesAroundMarkTests.swift new file mode 100644 index 00000000..abab1ceb --- /dev/null +++ b/Tests/Rules/BlankLinesAroundMarkTests.swift @@ -0,0 +1,123 @@ +// +// BlankLinesAroundMarkTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 11/29/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesAroundMarkTests: XCTestCase { + func testInsertBlankLinesAroundMark() { + let input = """ + let foo = "foo" + // MARK: bar + let bar = "bar" + """ + let output = """ + let foo = "foo" + + // MARK: bar + + let bar = "bar" + """ + testFormatting(for: input, output, rule: .blankLinesAroundMark) + } + + func testNoInsertExtraBlankLinesAroundMark() { + let input = """ + let foo = "foo" + + // MARK: bar + + let bar = "bar" + """ + testFormatting(for: input, rule: .blankLinesAroundMark) + } + + func testInsertBlankLineAfterMarkAtStartOfFile() { + let input = """ + // MARK: bar + let bar = "bar" + """ + let output = """ + // MARK: bar + + let bar = "bar" + """ + testFormatting(for: input, output, rule: .blankLinesAroundMark) + } + + func testInsertBlankLineBeforeMarkAtEndOfFile() { + let input = """ + let foo = "foo" + // MARK: bar + """ + let output = """ + let foo = "foo" + + // MARK: bar + """ + testFormatting(for: input, output, rule: .blankLinesAroundMark) + } + + func testNoInsertBlankLineBeforeMarkAtStartOfScope() { + let input = """ + do { + // MARK: foo + + let foo = "foo" + } + """ + testFormatting(for: input, rule: .blankLinesAroundMark) + } + + func testNoInsertBlankLineAfterMarkAtEndOfScope() { + let input = """ + do { + let foo = "foo" + + // MARK: foo + } + """ + testFormatting(for: input, rule: .blankLinesAroundMark) + } + + func testInsertBlankLinesJustBeforeMarkNotAfter() { + let input = """ + let foo = "foo" + // MARK: bar + let bar = "bar" + """ + let output = """ + let foo = "foo" + + // MARK: bar + let bar = "bar" + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, output, rule: .blankLinesAroundMark, options: options) + } + + func testNoInsertExtraBlankLinesAroundMarkWithNoBlankLineAfterMark() { + let input = """ + let foo = "foo" + + // MARK: bar + let bar = "bar" + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, rule: .blankLinesAroundMark, options: options) + } + + func testNoInsertBlankLineAfterMarkAtStartOfFile() { + let input = """ + // MARK: bar + let bar = "bar" + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, rule: .blankLinesAroundMark, options: options) + } +} diff --git a/Tests/Rules/BlankLinesAtEndOfScopeTests.swift b/Tests/Rules/BlankLinesAtEndOfScopeTests.swift new file mode 100644 index 00000000..80c279c9 --- /dev/null +++ b/Tests/Rules/BlankLinesAtEndOfScopeTests.swift @@ -0,0 +1,107 @@ +// +// BlankLinesAtEndOfScopeTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/30/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesAtEndOfScopeTests: XCTestCase { + func testBlankLinesRemovedAtEndOfFunction() { + let input = "func foo() {\n // code\n\n}" + let output = "func foo() {\n // code\n}" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLinesRemovedAtEndOfParens() { + let input = "(\n foo: Int\n\n)" + let output = "(\n foo: Int\n)" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLinesRemovedAtEndOfBrackets() { + let input = "[\n foo,\n bar,\n\n]" + let output = "[\n foo,\n bar,\n]" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLineNotRemovedBeforeElse() { + let input = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n\n}" + let output = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n}" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, + exclude: [.blankLinesAtStartOfScope]) + } + + func testBlankLineRemovedFromEndOfTypeByDefault() { + let input = """ + class FooTests { + func testFoo() {} + + } + """ + + let output = """ + class FooTests { + func testFoo() {} + } + """ + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLinesNotRemovedFromEndOfTypeWithOptionEnabled() { + let input = """ + class FooClass { + func fooMethod() {} + + } + + struct FooStruct { + func fooMethod() {} + + } + + enum FooEnum { + func fooMethod() {} + + } + + actor FooActor { + func fooMethod() {} + + } + + protocol FooProtocol { + func fooMethod() + } + + extension Array where Element == Foo { + func fooMethod() {} + + } + """ + testFormatting(for: input, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } + + func testBlankLineAtEndOfScopeRemovedFromMethodInType() { + let input = """ + class Foo { + func bar() { + print("hello world") + + } + } + """ + + let output = """ + class Foo { + func bar() { + print("hello world") + } + } + """ + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } +} diff --git a/Tests/Rules/BlankLinesAtStartOfScopeTests.swift b/Tests/Rules/BlankLinesAtStartOfScopeTests.swift new file mode 100644 index 00000000..97ea8c3d --- /dev/null +++ b/Tests/Rules/BlankLinesAtStartOfScopeTests.swift @@ -0,0 +1,106 @@ +// +// BlankLinesAtStartOfScopeTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 2/1/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesAtStartOfScopeTests: XCTestCase { + func testBlankLinesRemovedAtStartOfFunction() { + let input = "func foo() {\n\n // code\n}" + let output = "func foo() {\n // code\n}" + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesRemovedAtStartOfParens() { + let input = "(\n\n foo: Int\n)" + let output = "(\n foo: Int\n)" + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesRemovedAtStartOfBrackets() { + let input = "[\n\n foo,\n bar,\n]" + let output = "[\n foo,\n bar,\n]" + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesNotRemovedBetweenElementsInsideBrackets() { + let input = "[foo,\n\n bar]" + testFormatting(for: input, rule: .blankLinesAtStartOfScope, exclude: [.wrapArguments]) + } + + func testBlankLineRemovedFromStartOfTypeByDefault() { + let input = """ + class FooTests { + + func testFoo() {} + } + """ + + let output = """ + class FooTests { + func testFoo() {} + } + """ + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesNotRemovedFromStartOfTypeWithOptionEnabled() { + let input = """ + class FooClass { + + func fooMethod() {} + } + + struct FooStruct { + + func fooMethod() {} + } + + enum FooEnum { + + func fooMethod() {} + } + + actor FooActor { + + func fooMethod() {} + } + + protocol FooProtocol { + + func fooMethod() + } + + extension Array where Element == Foo { + + func fooMethod() {} + } + """ + testFormatting(for: input, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } + + func testBlankLineAtStartOfScopeRemovedFromMethodInType() { + let input = """ + class Foo { + func bar() { + + print("hello world") + } + } + """ + + let output = """ + class Foo { + func bar() { + print("hello world") + } + } + """ + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } +} diff --git a/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift b/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift new file mode 100644 index 00000000..63af1db3 --- /dev/null +++ b/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift @@ -0,0 +1,64 @@ +// +// BlankLinesBetweenChainedFunctionsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 7/28/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesBetweenChainedFunctionsTests: XCTestCase { + func testBlankLinesBetweenChainedFunctions() { + let input = """ + [0, 1, 2] + .map { $0 * 2 } + + + + .map { $0 * 3 } + """ + let output1 = """ + [0, 1, 2] + .map { $0 * 2 } + .map { $0 * 3 } + """ + let output2 = """ + [0, 1, 2] + .map { $0 * 2 } + .map { $0 * 3 } + """ + testFormatting(for: input, [output1, output2], rules: [.blankLinesBetweenChainedFunctions]) + } + + func testBlankLinesWithCommentsBetweenChainedFunctions() { + let input = """ + [0, 1, 2] + .map { $0 * 2 } + + // Multiplies by 3 + + .map { $0 * 3 } + """ + let output = """ + [0, 1, 2] + .map { $0 * 2 } + // Multiplies by 3 + .map { $0 * 3 } + """ + testFormatting(for: input, output, rule: .blankLinesBetweenChainedFunctions) + } + + func testBlankLinesWithMarkCommentBetweenChainedFunctions() { + let input = """ + [0, 1, 2] + .map { $0 * 2 } + + // MARK: hello + + .map { $0 * 3 } + """ + testFormatting(for: input, rules: [.blankLinesBetweenChainedFunctions, .blankLinesAroundMark]) + } +} diff --git a/Tests/Rules/BlankLinesBetweenImportsTests.swift b/Tests/Rules/BlankLinesBetweenImportsTests.swift new file mode 100644 index 00000000..6d1b2cb5 --- /dev/null +++ b/Tests/Rules/BlankLinesBetweenImportsTests.swift @@ -0,0 +1,75 @@ +// +// BlankLinesBetweenImportsTests.swift +// SwiftFormatTests +// +// Created by Huy Vo on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesBetweenImportsTests: XCTestCase { + func testBlankLinesBetweenImportsShort() { + let input = """ + import ModuleA + + import ModuleB + """ + let output = """ + import ModuleA + import ModuleB + """ + testFormatting(for: input, output, rule: .blankLinesBetweenImports) + } + + func testBlankLinesBetweenImportsLong() { + let input = """ + import ModuleA + import ModuleB + + import ModuleC + import ModuleD + import ModuleE + + import ModuleF + + import ModuleG + import ModuleH + """ + let output = """ + import ModuleA + import ModuleB + import ModuleC + import ModuleD + import ModuleE + import ModuleF + import ModuleG + import ModuleH + """ + testFormatting(for: input, output, rule: .blankLinesBetweenImports) + } + + func testBlankLinesBetweenImportsWithTestable() { + let input = """ + import ModuleA + + @testable import ModuleB + import ModuleC + + @testable import ModuleD + @testable import ModuleE + + @testable import ModuleF + """ + let output = """ + import ModuleA + @testable import ModuleB + import ModuleC + @testable import ModuleD + @testable import ModuleE + @testable import ModuleF + """ + testFormatting(for: input, output, rule: .blankLinesBetweenImports) + } +} diff --git a/Tests/Rules/BlankLinesBetweenScopesTests.swift b/Tests/Rules/BlankLinesBetweenScopesTests.swift new file mode 100644 index 00000000..8f13671a --- /dev/null +++ b/Tests/Rules/BlankLinesBetweenScopesTests.swift @@ -0,0 +1,218 @@ +// +// BlankLinesBetweenScopesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 9/7/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesBetweenScopesTests: XCTestCase { + func testBlankLineBetweenFunctions() { + let input = "func foo() {\n}\nfunc bar() {\n}" + let output = "func foo() {\n}\n\nfunc bar() {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoBlankLineBetweenPropertyAndFunction() { + let input = "var foo: Int\nfunc bar() {\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testBlankLineBetweenFunctionsIsBeforeComment() { + let input = "func foo() {\n}\n/// headerdoc\nfunc bar() {\n}" + let output = "func foo() {\n}\n\n/// headerdoc\nfunc bar() {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testBlankLineBeforeAtObjcOnLineBeforeProtocol() { + let input = "@objc\nprotocol Foo {\n}\n@objc\nprotocol Bar {\n}" + let output = "@objc\nprotocol Foo {\n}\n\n@objc\nprotocol Bar {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testBlankLineBeforeAtAvailabilityOnLineBeforeClass() { + let input = "protocol Foo {\n}\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" + let output = "protocol Foo {\n}\n\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoExtraBlankLineBetweenFunctions() { + let input = "func foo() {\n}\n\nfunc bar() {\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testNoBlankLineBetweenFunctionsInProtocol() { + let input = "protocol Foo {\n func bar()\n func baz() -> Int\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineInsideInitFunction() { + let input = "init() {\n super.init()\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testBlankLineAfterProtocolBeforeProperty() { + let input = "protocol Foo {\n}\nvar bar: String" + let output = "protocol Foo {\n}\n\nvar bar: String" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoExtraBlankLineAfterSingleLineComment() { + let input = "var foo: Bar? // comment\n\nfunc bar() {}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoExtraBlankLineAfterMultilineComment() { + let input = "var foo: Bar? /* comment */\n\nfunc bar() {}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBeforeFuncAsIdentifier() { + let input = "var foo: Bar?\nfoo.func(x) {}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenFunctionsWithInlineBody() { + let input = "class Foo {\n func foo() { print(\"foo\") }\n func bar() { print(\"bar\") }\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenIfStatements() { + let input = "func foo() {\n if x {\n }\n if y {\n }\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testNoBlanksInsideClassFunc() { + let input = "class func foo {\n if x {\n }\n if y {\n }\n}" + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, + exclude: [.emptyBraces]) + } + + func testNoBlanksInsideClassVar() { + let input = "class var foo: Int {\n if x {\n }\n if y {\n }\n}" + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, + exclude: [.emptyBraces]) + } + + func testBlankLineBetweenCalledClosures() { + let input = "class Foo {\n var foo = {\n }()\n func bar {\n }\n}" + let output = "class Foo {\n var foo = {\n }()\n\n func bar {\n }\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoBlankLineAfterCalledClosureAtEndOfScope() { + let input = "class Foo {\n var foo = {\n }()\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testNoBlankLineBeforeWhileInRepeatWhile() { + let input = """ + repeat + { print("foo") } + while false + { print("bar") }() + """ + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: [.redundantClosure, .wrapLoopBodies]) + } + + func testBlankLineBeforeWhileIfNotRepeatWhile() { + let input = "func foo(x)\n{\n}\nwhile true\n{\n}" + let output = "func foo(x)\n{\n}\n\nwhile true\n{\n}" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, options: options, + exclude: [.emptyBraces]) + } + + func testNoInsertBlankLinesInConditionalCompilation() { + let input = """ + struct Foo { + #if BAR + func something() { + } + #else + func something() { + } + #endif + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoInsertBlankLineAfterBraceBeforeSourceryComment() { + let input = """ + struct Foo { + var bar: String + + // sourcery:inline:Foo.init + public init(bar: String) { + self.bar = bar + } + // sourcery:end + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenChainedClosures() { + let input = """ + foo { + doFoo() + } + // bar + .bar { + doBar() + } + // baz + .baz { + doBaz($0) + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenTrailingClosures() { + let input = """ + UIView.animate(withDuration: 0) { + fromView.transform = .identity + } + completion: { finished in + context.completeTransition(finished) + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testBlankLineBetweenTrailingClosureAndLabelledLoop() { + let input = """ + UIView.animate(withDuration: 0) { + fromView.transform = .identity + } + completion: for foo in bar { + print(foo) + } + """ + let output = """ + UIView.animate(withDuration: 0) { + fromView.transform = .identity + } + + completion: for foo in bar { + print(foo) + } + """ + testFormatting(for: input, output, rule: .blankLinesBetweenScopes) + } +} diff --git a/Tests/Rules/BlockCommentsTests.swift b/Tests/Rules/BlockCommentsTests.swift new file mode 100644 index 00000000..c4901c2a --- /dev/null +++ b/Tests/Rules/BlockCommentsTests.swift @@ -0,0 +1,298 @@ +// +// BlockCommentsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 11/6/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlockCommentsTests: XCTestCase { + func testBlockCommentsOneLine() { + let input = "foo = bar /* comment */" + let output = "foo = bar // comment" + testFormatting(for: input, output, rule: .blockComments) + } + + func testDocBlockCommentsOneLine() { + let input = "foo = bar /** doc comment */" + let output = "foo = bar /// doc comment" + testFormatting(for: input, output, rule: .blockComments) + } + + func testPreservesBlockCommentInSingleLineScope() { + let input = "if foo { /* code */ }" + testFormatting(for: input, rule: .blockComments) + } + + func testBlockCommentsMultiLine() { + let input = """ + /* + * foo + * bar + */ + """ + let output = """ + // foo + // bar + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsWithoutBlankFirstLine() { + let input = """ + /* foo + * bar + */ + """ + let output = """ + // foo + // bar + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsWithBlankLine() { + let input = """ + /* + * foo + * + * bar + */ + """ + let output = """ + // foo + // + // bar + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockDocCommentsWithAsterisksOnEachLine() { + let input = """ + /** + * This is a documentation comment, + * not a standard comment. + */ + """ + let output = """ + /// This is a documentation comment, + /// not a standard comment. + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockDocCommentsWithoutAsterisksOnEachLine() { + let input = """ + /** + This is a documentation comment, + not a standard comment. + */ + """ + let output = """ + /// This is a documentation comment, + /// not a standard comment. + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockCommentWithBulletPoints() { + let input = """ + /* + This is a list of nice colors: + + * green + * blue + * red + + Yellow is also great. + */ + + /* + * Another comment. + */ + """ + let output = """ + // This is a list of nice colors: + // + // * green + // * blue + // * red + // + // Yellow is also great. + + // Another comment. + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsNested() { + let input = """ + /* + * comment + * /* inside */ + * a comment + */ + """ + let output = """ + // comment + // inside + // a comment + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsIndentPreserved() { + let input = """ + func foo() { + /* + foo + bar. + */ + } + """ + let output = """ + func foo() { + // foo + // bar. + } + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsIndentPreserved2() { + let input = """ + func foo() { + /* + * foo + * bar. + */ + } + """ + let output = """ + func foo() { + // foo + // bar. + } + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockDocCommentsIndentPreserved() { + let input = """ + func foo() { + /** + * foo + * bar. + */ + } + """ + let output = """ + func foo() { + /// foo + /// bar. + } + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testLongBlockCommentsWithoutPerLineMarkersFullyConverted() { + let input = """ + /* + The beginnings of the lines in this multiline comment body + have only spaces in them. There are no asterisks, only spaces. + + This should not cause the blockComments rule to convert only + part of the comment body and leave the rest hanging. + + The comment must have at least this many lines to trigger the bug. + */ + """ + let output = """ + // The beginnings of the lines in this multiline comment body + // have only spaces in them. There are no asterisks, only spaces. + // + // This should not cause the blockComments rule to convert only + // part of the comment body and leave the rest hanging. + // + // The comment must have at least this many lines to trigger the bug. + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentImmediatelyFollowedByCode() { + let input = """ + /** + foo + + bar + */ + func foo() {} + """ + let output = """ + /// foo + /// + /// bar + func foo() {} + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentImmediatelyFollowedByCode2() { + let input = """ + /** + Line 1. + + Line 2. + + Line 3. + */ + foo(bar) + """ + let output = """ + /// Line 1. + /// + /// Line 2. + /// + /// Line 3. + foo(bar) + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockCommentImmediatelyFollowedByCode3() { + let input = """ + /* foo + bar */ + func foo() {} + """ + let output = """ + // foo + // bar + func foo() {} + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockCommentFollowedByBlankLine() { + let input = """ + /** + foo + + bar + */ + + func foo() {} + """ + let output = """ + /// foo + /// + /// bar + + func foo() {} + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } +} diff --git a/Tests/RulesTests+Braces.swift b/Tests/Rules/BracesTests.swift similarity index 73% rename from Tests/RulesTests+Braces.swift rename to Tests/Rules/BracesTests.swift index dd95a77a..1d1ecbf0 100644 --- a/Tests/RulesTests+Braces.swift +++ b/Tests/Rules/BracesTests.swift @@ -1,21 +1,19 @@ // -// RulesTests+Braces.swift +// BracesTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class BracesTests: RulesTests { - // MARK: - braces - +class BracesTests: XCTestCase { func testAllmanBracesAreConverted() { let input = "func foo()\n{\n statement\n}" let output = "func foo() {\n statement\n}" - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testNestedAllmanBracesAreConverted() { @@ -35,17 +33,17 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testKnRBracesAfterComment() { let input = "func foo() // comment\n{\n statement\n}" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRBracesAfterMultilineComment() { let input = "func foo() /* comment/ncomment */\n{\n statement\n}" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRBracesAfterMultilineComment2() { @@ -57,17 +55,17 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRExtraSpaceNotAddedBeforeBrace() { let input = "foo({ bar })" - testFormatting(for: input, rule: FormatRules.braces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .braces, exclude: [.trailingClosures]) } func testKnRLinebreakNotRemovedBeforeInlineBlockNot() { let input = "func foo() -> Bool\n{ return false }" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRNoMangleCommentBeforeClosure() { @@ -81,7 +79,7 @@ class BracesTests: RulesTests { }(), ] """ - testFormatting(for: input, rule: FormatRules.braces, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .braces, exclude: [.redundantClosure]) } func testKnRNoMangleClosureReturningClosure() { @@ -92,7 +90,7 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRNoMangleClosureReturningClosure2() { @@ -103,7 +101,7 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testAllmanNoMangleClosureReturningClosure() { @@ -116,7 +114,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.braces, options: options) + testFormatting(for: input, rule: .braces, options: options) } func testKnRUnwrapClosure() { @@ -131,7 +129,7 @@ class BracesTests: RulesTests { bar() } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testKnRNoUnwrapClosureIfWidthExceeded() { @@ -142,18 +140,18 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, rule: FormatRules.braces, options: options, exclude: ["indent"]) + testFormatting(for: input, rule: .braces, options: options, exclude: [.indent]) } func testKnRClosingBraceWrapped() { let input = "func foo() {\n print(bar) }" let output = "func foo() {\n print(bar)\n}" - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testKnRInlineBracesNotWrapped() { let input = "func foo() { print(bar) }" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testAllmanComputedPropertyBracesConverted() { @@ -168,7 +166,7 @@ class BracesTests: RulesTests { return 5 } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testAllmanInitBracesConverted() { @@ -183,7 +181,7 @@ class BracesTests: RulesTests { foo = 5 } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testAllmanSubscriptBracesConverted() { @@ -198,7 +196,7 @@ class BracesTests: RulesTests { foo[i] } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForStructDeclaration() { @@ -213,7 +211,7 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForInit() { @@ -228,7 +226,7 @@ class BracesTests: RulesTests { self.foo = foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForIfStatement() { @@ -243,7 +241,7 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForExtension() { @@ -258,7 +256,7 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForOptionalInit() { @@ -273,7 +271,7 @@ class BracesTests: RulesTests { return nil } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBraceUnwrappedIfWrapMultilineStatementBracesRuleDisabled() { @@ -290,8 +288,8 @@ class BracesTests: RulesTests { return nil } """ - testFormatting(for: input, output, rule: FormatRules.braces, - exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .braces, + exclude: [.wrapMultilineStatementBraces]) } func testBraceNotUnwrappedIfWrapMultilineStatementBracesRuleDisabled() { @@ -303,7 +301,7 @@ class BracesTests: RulesTests { } """ testFormatting(for: input, rules: [ - FormatRules.braces, FormatRules.wrapMultilineStatementBraces, + .braces, .wrapMultilineStatementBraces, ]) } @@ -319,7 +317,7 @@ class BracesTests: RulesTests { // } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } // allman style @@ -328,86 +326,86 @@ class BracesTests: RulesTests { let input = "func foo() {\n statement\n}" let output = "func foo()\n{\n statement\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBlankLineAfterBraceRemoved() { let input = "func foo() {\n \n statement\n}" let output = "func foo()\n{\n statement\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceInsideParensNotConverted() { let input = "foo({\n bar\n})" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.braces, options: options, - exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .braces, options: options, + exclude: [.trailingClosures]) } func testAllmanBraceDoClauseIndent() { let input = "do {\n foo\n}" let output = "do\n{\n foo\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceCatchClauseIndent() { let input = "do {\n try foo\n}\ncatch {\n}" let output = "do\n{\n try foo\n}\ncatch\n{\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options, - exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .braces, options: options, + exclude: [.emptyBraces]) } func testAllmanBraceDoThrowsCatchClauseIndent() { let input = "do throws(Foo) {\n try foo\n}\ncatch {\n}" let output = "do throws(Foo)\n{\n try foo\n}\ncatch\n{\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options, - exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .braces, options: options, + exclude: [.emptyBraces]) } func testAllmanBraceRepeatWhileIndent() { let input = "repeat {\n foo\n}\nwhile x" let output = "repeat\n{\n foo\n}\nwhile x" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceOptionalComputedPropertyIndent() { let input = "var foo: Int? {\n return 5\n}" let output = "var foo: Int?\n{\n return 5\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceThrowsFunctionIndent() { let input = "func foo() throws {\n bar\n}" let output = "func foo() throws\n{\n bar\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceAsyncFunctionIndent() { let input = "func foo() async {\n bar\n}" let output = "func foo() async\n{\n bar\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceAfterCommentIndent() { let input = "func foo() { // foo\n\n bar\n}" let output = "func foo()\n{ // foo\n bar\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceAfterSwitch() { let input = "switch foo {\ncase bar: break\n}" let output = "switch foo\n{\ncase bar: break\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForStructDeclaration() { @@ -425,7 +423,7 @@ class BracesTests: RulesTests { let options = FormatOptions(allmanBraces: true) testFormatting( for: input, output, - rule: FormatRules.braces, + rule: .braces, options: options ) } @@ -443,7 +441,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForOptionalInit() { @@ -459,7 +457,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForIfStatement() { @@ -475,7 +473,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForIfStatement2() { @@ -491,7 +489,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForExtension() { @@ -507,7 +505,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testEmptyAllmanIfElseBraces() { @@ -526,7 +524,7 @@ class BracesTests: RulesTests { """ let options = FormatOptions(allmanBraces: true) testFormatting(for: input, [output], rules: [ - FormatRules.braces, FormatRules.emptyBraces, FormatRules.elseOnSameLine, + .braces, .emptyBraces, .elseOnSameLine, ], options: options) } @@ -543,7 +541,7 @@ class BracesTests: RulesTests { """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, rules: [FormatRules.braces, FormatRules.wrapMultilineStatementBraces], options: options) + testFormatting(for: input, rules: [.braces, .wrapMultilineStatementBraces], options: options) } func testTrailingClosureWrappingAfterMethodWithPartialWrappingAndClosures() { @@ -558,7 +556,7 @@ class BracesTests: RulesTests { """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, rules: [FormatRules.braces, FormatRules.wrapMultilineStatementBraces], options: options) + testFormatting(for: input, rules: [.braces, .wrapMultilineStatementBraces], options: options) } func testWrapInitBraceWithComplexWhereClause() { @@ -574,6 +572,6 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, rules: [FormatRules.braces, FormatRules.wrapMultilineStatementBraces]) + testFormatting(for: input, rules: [.braces, .wrapMultilineStatementBraces]) } } diff --git a/Tests/Rules/ConditionalAssignmentTests.swift b/Tests/Rules/ConditionalAssignmentTests.swift new file mode 100644 index 00000000..fed0806d --- /dev/null +++ b/Tests/Rules/ConditionalAssignmentTests.swift @@ -0,0 +1,828 @@ +// +// ConditionalAssignmentTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConditionalAssignmentTests: XCTestCase { + func testDoesntConvertIfStatementAssignmentSwift5_8() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testConvertsIfStatementAssignment() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let output = """ + let foo: Foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) + } + + func testConvertsSimpleSwitchStatementAssignment() { + let input = """ + let foo: Foo + switch condition { + case true: + foo = Foo("foo") + case false: + foo = Foo("bar") + } + """ + let output = """ + let foo: Foo = switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) + } + + func testConvertsTrivialSwitchStatementAssignment() { + let input = """ + let foo: Foo + switch enumWithOnceCase(let value) { + case singleCase: + foo = value + } + """ + let output = """ + let foo: Foo = switch enumWithOnceCase(let value) { + case singleCase: + value + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testConvertsNestedIfAndStatementAssignments() { + let input = """ + let foo: Foo + switch condition { + case true: + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + + case false: + switch condition { + case true: + foo = Foo("baaz") + + case false: + if condition { + foo = Foo("quux") + } else { + foo = Foo("quack") + } + } + } + """ + let output = """ + let foo: Foo = switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + switch condition { + case true: + Foo("baaz") + + case false: + if condition { + Foo("quux") + } else { + Foo("quack") + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) + } + + func testConvertsIfStatementAssignmentPreservingComment() { + let input = """ + let foo: Foo + // This is a comment between the property and condition + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let output = """ + let foo: Foo + // This is a comment between the property and condition + = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.indent, .redundantType, .wrapMultilineConditionalAssignment]) + } + + func testDoesntConvertsIfStatementAssigningMultipleProperties() { + let input = """ + let foo: Foo + let bar: Bar + if condition { + foo = Foo("foo") + bar = Bar("foo") + } else { + foo = Foo("bar") + bar = Bar("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertsIfStatementAssigningDifferentProperties() { + let input = """ + var foo: Foo? + var bar: Bar? + if condition { + foo = Foo("foo") + } else { + bar = Bar("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertNonExhaustiveIfStatementAssignment1() { + let input = """ + var foo: Foo? + if condition { + foo = Foo("foo") + } else if someOtherCondition { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertNonExhaustiveIfStatementAssignment2() { + let input = """ + var foo: Foo? + if condition { + if condition { + foo = Foo("foo") + } + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment1() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + print("Multi-statement") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment2() { + let input = """ + let foo: Foo + switch condition { + case true: + foo = Foo("foo") + print("Multi-statement") + + case false: + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment3() { + let input = """ + let foo: Foo + if condition { + if condition { + foo = Foo("bar") + } else { + foo = Foo("baaz") + } + print("Multi-statement") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment4() { + let input = """ + let foo: Foo + switch condition { + case true: + if condition { + foo = Foo("bar") + } else { + foo = Foo("baaz") + } + print("Multi-statement") + + case false: + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithStringLiteral() { + let input = """ + let text: String + if conditionOne { + text = "Hello World!" + doSomeStuffHere() + } else { + text = "Goodbye!" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithCollectionLiteral() { + let input = """ + let text: [String] + if conditionOne { + text = [] + doSomeStuffHere() + } else { + text = ["Goodbye!"] + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithIntLiteral() { + let input = """ + let number: Int? + if conditionOne { + number = 5 + doSomeStuffHere() + } else { + number = 10 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithNilLiteral() { + let input = """ + let number: Int? + if conditionOne { + number = nil + doSomeStuffHere() + } else { + number = 10 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithOtherProperty() { + let input = """ + let number: Int? + if conditionOne { + number = someOtherProperty + doSomeStuffHere() + } else { + number = 10 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertConditionalCastInSwift5_9() { + // The following code doesn't compile in Swift 5.9 due to this issue: + // https://github.com/apple/swift/issues/68764 + // + // let result = if condition { + // foo as? String + // } else { + // "bar" + // } + // + let input = """ + let result1: String? + if condition { + result1 = foo as? String + } else { + result1 = "bar" + } + + let result2: String? + switch condition { + case true: + result2 = foo as? String + case false: + result2 = "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testAllowsAsWithinInnerScope() { + let input = """ + let result: String? + switch condition { + case true: + result = method(string: foo as? String) + case false: + result = "bar" + } + """ + + let output = """ + let result: String? = switch condition { + case true: + method(string: foo as? String) + case false: + "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + // TODO: update branches parser to handle this case properly + func testIgnoreSwitchWithConditionalCompilation() { + let input = """ + func foo() -> String? { + let result: String? + switch condition { + #if os(macOS) + case .foo: + result = method(string: foo as? String) + #endif + case .bar: + return nil + } + return result + } + """ + + let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + // TODO: update branches parser to handle this scenario properly + func testIgnoreSwitchWithConditionalCompilation2() { + let input = """ + func foo() -> String? { + let result: String? + switch condition { + case .foo: + result = method(string: foo as? String) + #if os(macOS) + case .bar: + return nil + #endif + } + return result + } + """ + + let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testConvertsConditionalCastInSwift5_10() { + let input = """ + let result1: String? + if condition { + result1 = foo as? String + } else { + result1 = "bar" + } + + let result2: String? + switch condition { + case true: + result2 = foo as? String + case false: + result2 = "bar" + } + """ + + let output = """ + let result1: String? = if condition { + foo as? String + } else { + "bar" + } + + let result2: String? = switch condition { + case true: + foo as? String + case false: + "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.10") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testConvertsSwitchWithDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + default: + foo = Foo("default") + } + """ + + let output = """ + let foo: Foo = switch condition { + case .foo: + Foo("foo") + case .bar: + Foo("bar") + default: + Foo("default") + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) + } + + func testConvertsSwitchWithUnknownDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + @unknown default: + foo = Foo("default") + } + """ + + let output = """ + let foo: Foo = switch condition { + case .foo: + Foo("foo") + case .bar: + Foo("bar") + @unknown default: + Foo("default") + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) + } + + func testPreservesSwitchWithReturnInDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + default: + return + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testPreservesSwitchWithReturnInUnknownDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + @unknown default: + return + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertIfStatementWithForLoopInBranch() { + let input = """ + var foo: Foo? + if condition { + foo = Foo("foo") + for foo in foos { + print(foo) + } + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testConvertsIfStatementNotFollowingPropertyDefinition() { + let input = """ + if condition { + property = Foo("foo") + } else { + property = Foo("bar") + } + """ + + let output = """ + property = + if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesIfStatementNotFollowingPropertyDefinitionWithInvalidBranch() { + let input = """ + if condition { + property = Foo("foo") + } else { + property = Foo("bar") + print("A second expression on this branch") + } + + if condition { + property = Foo("foo") + } else { + if otherCondition { + property = Foo("foo") + } + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesNonExhaustiveIfStatementNotFollowingPropertyDefinition() { + let input = """ + if condition { + property = Foo("foo") + } + + if condition { + property = Foo("foo") + } else if otherCondition { + property = Foo("foo") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testConvertsSwitchStatementNotFollowingPropertyDefinition() { + let input = """ + switch condition { + case true: + property = Foo("foo") + case false: + property = Foo("bar") + } + """ + + let output = """ + property = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testConvertsSwitchStatementWithComplexLValueNotFollowingPropertyDefinition() { + let input = """ + switch condition { + case true: + property?.foo!.bar["baaz"] = Foo("foo") + case false: + property?.foo!.bar["baaz"] = Foo("bar") + } + """ + + let output = """ + property?.foo!.bar["baaz"] = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testDoesntMergePropertyWithUnrelatedCondition() { + let input = """ + let differentProperty: Foo + switch condition { + case true: + property = Foo("foo") + case false: + property = Foo("bar") + } + """ + + let output = """ + let differentProperty: Foo + property = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testConvertsNestedIfSwitchStatementNotFollowingPropertyDefinition() { + let input = """ + switch firstCondition { + case true: + if secondCondition { + property = Foo("foo") + } else { + property = Foo("bar") + } + + case false: + if thirdCondition { + property = Foo("baaz") + } else { + property = Foo("quux") + } + } + """ + + let output = """ + property = + switch firstCondition { + case true: + if secondCondition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + if thirdCondition { + Foo("baaz") + } else { + Foo("quux") + } + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesSwitchConditionWithIneligibleBranch() { + let input = """ + switch firstCondition { + case true: + // Even though this condition is eligible to be converted, + // we leave it as-is because it's nested in an ineligible condition. + if secondCondition { + property = Foo("foo") + } else { + property = Foo("bar") + } + + case false: + if thirdCondition { + property = Foo("baaz") + } else { + property = Foo("quux") + print("A second expression on this branch") + } + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesIfConditionWithIneligibleBranch() { + let input = """ + if firstCondition { + // Even though this condition is eligible to be converted, + // we leave it as-is because it's nested in an ineligible condition. + if secondCondition { + property = Foo("foo") + } else { + property = Foo("bar") + } + } else { + if thirdCondition { + property = Foo("baaz") + } else { + property = Foo("quux") + print("A second expression on this branch") + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } +} diff --git a/Tests/Rules/ConsecutiveBlankLinesTests.swift b/Tests/Rules/ConsecutiveBlankLinesTests.swift new file mode 100644 index 00000000..5aed2ecd --- /dev/null +++ b/Tests/Rules/ConsecutiveBlankLinesTests.swift @@ -0,0 +1,86 @@ +// +// ConsecutiveBlankLinesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/30/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConsecutiveBlankLinesTests: XCTestCase { + func testConsecutiveBlankLines() { + let input = "foo\n\n \nbar" + let output = "foo\n\nbar" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAtEndOfFile() { + let input = "foo\n\n" + let output = "foo\n" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAtStartOfFile() { + let input = "\n\n\nfoo" + let output = "\n\nfoo" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesInsideStringLiteral() { + let input = "\"\"\"\nhello\n\n\nworld\n\"\"\"" + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAtStartOfStringLiteral() { + let input = "\"\"\"\n\n\nhello world\n\"\"\"" + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAfterStringLiteral() { + let input = "\"\"\"\nhello world\n\"\"\"\n\n\nfoo()" + let output = "\"\"\"\nhello world\n\"\"\"\n\nfoo()" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testFragmentWithTrailingLinebreaks() { + let input = "func foo() {}\n\n\n" + let output = "func foo() {}\n\n" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .consecutiveBlankLines, options: options) + } + + func testConsecutiveBlankLinesNoInterpolation() { + let input = """ + \"\"\" + AAA + ZZZ + + + + \"\"\" + """ + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAfterInterpolation() { + let input = """ + \"\"\" + AAA + \\(interpolated) + + + + \"\"\" + """ + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testLintingConsecutiveBlankLinesReportsCorrectLine() { + let input = "foo\n \n\nbar" + XCTAssertEqual(try lint(input, rules: [.consecutiveBlankLines]), [ + .init(line: 3, rule: .consecutiveBlankLines, filePath: nil), + ]) + } +} diff --git a/Tests/Rules/ConsecutiveSpacesTests.swift b/Tests/Rules/ConsecutiveSpacesTests.swift new file mode 100644 index 00000000..86c77032 --- /dev/null +++ b/Tests/Rules/ConsecutiveSpacesTests.swift @@ -0,0 +1,56 @@ +// +// ConsecutiveSpacesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/30/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConsecutiveSpacesTests: XCTestCase { + func testConsecutiveSpaces() { + let input = "let foo = bar" + let output = "let foo = bar" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesAfterComment() { + let input = "// comment\nfoo bar" + let output = "// comment\nfoo bar" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntStripIndent() { + let input = "{\n let foo = bar\n}" + let output = "{\n let foo = bar\n}" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectMultilineComments() { + let input = "/* comment */" + testFormatting(for: input, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesRemovedBetweenComments() { + let input = "/* foo */ /* bar */" + let output = "/* foo */ /* bar */" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectNestedMultilineComments() { + let input = "/* foo /* bar */ baz */" + testFormatting(for: input, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectNestedMultilineComments2() { + let input = "/* /* foo */ /* bar */ */" + testFormatting(for: input, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectSingleLineComments() { + let input = "// foo bar" + testFormatting(for: input, rule: .consecutiveSpaces) + } +} diff --git a/Tests/Rules/ConsistentSwitchCaseSpacingTests.swift b/Tests/Rules/ConsistentSwitchCaseSpacingTests.swift new file mode 100644 index 00000000..45b63218 --- /dev/null +++ b/Tests/Rules/ConsistentSwitchCaseSpacingTests.swift @@ -0,0 +1,327 @@ +// +// ConsistentSwitchCaseSpacingTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 2/1/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConsistentSwitchCaseSpacingTests: XCTestCase { + func testInsertsBlankLinesToMakeSwitchStatementSpacingConsistent1() { + let input = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testInsertsBlankLinesToMakeSwitchStatementSpacingConsistent2() { + let input = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testInsertsBlankLinesToMakeSwitchStatementSpacingConsistent3() { + let input = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + // Similar to Earth but way more deadly + case .venus: + "Venus" + + // The best planet, where everything cool happens + case .earth: + "Earth" + + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + + // The biggest planet with the most moons + case .jupiter: + "Jupiter" + + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + let output = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + + // Similar to Earth but way more deadly + case .venus: + "Venus" + + // The best planet, where everything cool happens + case .earth: + "Earth" + + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + + // The biggest planet with the most moons + case .jupiter: + "Jupiter" + + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + + case .uranus: + "Uranus" + + case .neptune: + "Neptune" + } + } + """ + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testRemovesBlankLinesToMakeSwitchStatementConsistent() { + let input = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + + case .venus: + "Venus" + // The best planet, where everything cool happens + case .earth: + "Earth" + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + case .jupiter: + "Jupiter" + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + let output = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + case .venus: + "Venus" + // The best planet, where everything cool happens + case .earth: + "Earth" + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + case .jupiter: + "Jupiter" + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testSingleLineAndMultiLineSwitchCase1() { + let input = """ + switch planetType { + case .terrestrial: + if options.treatPlutoAsPlanet { + [.mercury, .venus, .earth, .mars, .pluto] + } else { + [.mercury, .venus, .earth, .mars] + } + case .gasGiant: + [.jupiter, .saturn, .uranus, .neptune] + } + """ + + let output = """ + switch planetType { + case .terrestrial: + if options.treatPlutoAsPlanet { + [.mercury, .venus, .earth, .mars, .pluto] + } else { + [.mercury, .venus, .earth, .mars] + } + + case .gasGiant: + [.jupiter, .saturn, .uranus, .neptune] + } + """ + + testFormatting(for: input, [output], rules: [.blankLineAfterSwitchCase, .consistentSwitchCaseSpacing]) + } + + func testSingleLineAndMultiLineSwitchCase2() { + let input = """ + switch planetType { + case .gasGiant: + [.jupiter, .saturn, .uranus, .neptune] + case .terrestrial: + if options.treatPlutoAsPlanet { + [.mercury, .venus, .earth, .mars, .pluto] + } else { + [.mercury, .venus, .earth, .mars] + } + } + """ + + testFormatting(for: input, rule: .consistentSwitchCaseSpacing) + } + + func testSwitchStatementWithSingleMultilineCase_blankLineAfterSwitchCaseEnabled() { + let input = """ + switch action { + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + case let .scanPlanet(planet): + scanner.scan(planet) + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.scan(planet) + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + testFormatting(for: input, [output], rules: [.consistentSwitchCaseSpacing, .blankLineAfterSwitchCase]) + } + + func testSwitchStatementWithSingleMultilineCase_blankLineAfterSwitchCaseDisabled() { + let input = """ + switch action { + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + case let .scanPlanet(planet): + scanner.scan(planet) + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + testFormatting(for: input, rule: .consistentSwitchCaseSpacing, exclude: [.blankLineAfterSwitchCase]) + } +} diff --git a/Tests/Rules/DocCommentsBeforeAttributesTests.swift b/Tests/Rules/DocCommentsBeforeAttributesTests.swift new file mode 100644 index 00000000..cd01da0b --- /dev/null +++ b/Tests/Rules/DocCommentsBeforeAttributesTests.swift @@ -0,0 +1,197 @@ +// +// DocCommentsBeforeAttributesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/22/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class DocCommentsBeforeAttributesTests: XCTestCase { + func testDocCommentsBeforeAttributes() { + let input = """ + @MainActor + /// Doc comment on this type declaration + public struct Baaz { + @available(*, deprecated) + /// Doc comment on this property declaration. + /// This comment spans multiple lines. + private var bar: Bar + + @FooBarMacro(arg1: true, arg2: .baaz) + /** + * Doc comment on this function declaration + */ + func foo() {} + } + """ + + let output = """ + /// Doc comment on this type declaration + @MainActor + public struct Baaz { + /// Doc comment on this property declaration. + /// This comment spans multiple lines. + @available(*, deprecated) + private var bar: Bar + + /** + * Doc comment on this function declaration + */ + @FooBarMacro(arg1: true, arg2: .baaz) + func foo() {} + } + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) + } + + func testDocCommentsBeforeMultipleAttributes() { + let input = """ + @MainActor @Macro(argument: true) @available(*, deprecated) + /// Doc comment on this function declaration after several attributes + public func foo() {} + + @MainActor + @Macro(argument: true) + @available(*, deprecated) + /// Doc comment on this function declaration after several attributes + public func bar() {} + """ + + let output = """ + /// Doc comment on this function declaration after several attributes + @MainActor @Macro(argument: true) @available(*, deprecated) + public func foo() {} + + /// Doc comment on this function declaration after several attributes + @MainActor + @Macro(argument: true) + @available(*, deprecated) + public func bar() {} + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) + } + + func testUpdatesCommentsAfterMark() { + let input = """ + import FooBarKit + + // MARK: - Foo + + @MainActor + /// Doc comment on this type declaration. + enum Foo { + + // MARK: Public + + @MainActor + /// Doc comment on this function declaration. + public func foo() {} + + // MARK: Private + + // TODO: This function also has a TODO comment. + @MainActor + /// Doc comment on this function declaration. + private func bar() {} + + } + """ + + let output = """ + import FooBarKit + + // MARK: - Foo + + /// Doc comment on this type declaration. + @MainActor + enum Foo { + + // MARK: Public + + /// Doc comment on this function declaration. + @MainActor + public func foo() {} + + // MARK: Private + + // TODO: This function also has a TODO comment. + /// Doc comment on this function declaration. + @MainActor + private func bar() {} + + } + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testPreservesCommentsBetweenAttributes() { + let input = """ + @MainActor + /// Doc comment between attributes + @available(*, deprecated) + /// Doc comment before declaration + func bar() {} + + @MainActor /// Doc comment after main actor attribute + @available(*, deprecated) /// Doc comment after deprecation attribute + /// Doc comment before declaration + func bar() {} + """ + + let output = """ + /// Doc comment before declaration + @MainActor + /// Doc comment between attributes + @available(*, deprecated) + func bar() {} + + /// Doc comment before declaration + @MainActor /// Doc comment after main actor attribute + @available(*, deprecated) /// Doc comment after deprecation attribute + func bar() {} + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) + } + + func testPreservesCommentOnSameLineAsAttribute() { + let input = """ + @MainActor /// Doc comment trailing attributes + func foo() {} + """ + + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) + } + + func testPreservesRegularComments() { + let input = """ + @MainActor + // Comment after attribute + func foo() {} + """ + + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) + } + + func testCombinesWithDocCommentsRule() { + let input = """ + @MainActor + // Comment after attribute + func foo() {} + """ + + let output = """ + /// Comment after attribute + @MainActor + func foo() {} + """ + + testFormatting(for: input, [output], rules: [.docComments, .docCommentsBeforeAttributes]) + } +} diff --git a/Tests/Rules/DocCommentsTests.swift b/Tests/Rules/DocCommentsTests.swift new file mode 100644 index 00000000..50b6913d --- /dev/null +++ b/Tests/Rules/DocCommentsTests.swift @@ -0,0 +1,579 @@ +// +// DocCommentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 10/19/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class DocCommentsTests: XCTestCase { + func testConvertCommentsToDocComments() { + let input = """ + // Multi-line comment before class with + // attribute between comment and declaration + @objc + class Foo { + // Single line comment before property + let foo = Foo() + + // Single line comment before property with property wrapper + @State + let bar = Bar() + + // Single line comment + func foo() {} + + /* Single line block comment before method */ + func baaz() {} + + /* + Multi-line block comment before method with attribute. + + This comment has a blank line in it. + */ + @nonobjc + func baaz() {} + } + + // Enum with a case + enum Quux { + // Documentation on an enum case + case quux + } + + extension Collection where Element: Foo { + // Property in extension with where clause + var foo: Foo { + first! + } + } + """ + + let output = """ + /// Multi-line comment before class with + /// attribute between comment and declaration + @objc + class Foo { + /// Single line comment before property + let foo = Foo() + + /// Single line comment before property with property wrapper + @State + let bar = Bar() + + /// Single line comment + func foo() {} + + /** Single line block comment before method */ + func baaz() {} + + /** + Multi-line block comment before method with attribute. + + This comment has a blank line in it. + */ + @nonobjc + func baaz() {} + } + + /// Enum with a case + enum Quux { + /// Documentation on an enum case + case quux + } + + extension Collection where Element: Foo { + /// Property in extension with where clause + var foo: Foo { + first! + } + } + """ + + testFormatting(for: input, output, rule: .docComments, + exclude: [.spaceInsideComments, .propertyType]) + } + + func testConvertDocCommentsToComments() { + let input = """ + /// Comment not associated with class + + class Foo { + /** Comment not associated with function */ + + func bar() { + /// Comment inside function declaration. + /// This one is multi-line. + + /// This comment is inside a function and precedes a declaration, + /// but we don't want to use doc comments inside property or function + /// scopes since users typically don't think of these as doc comments, + /// and this also breaks a common pattern where comments introduce + /// an entire following block of code (not just the property) + let bar: Bar? = Bar() + print(bar) + } + + var baaz: Baaz { + /// Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + var quux: Quux { + didSet { + /// Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + let output = """ + // Comment not associated with class + + class Foo { + /* Comment not associated with function */ + + func bar() { + // Comment inside function declaration. + // This one is multi-line. + + // This comment is inside a function and precedes a declaration, + // but we don't want to use doc comments inside property or function + // scopes since users typically don't think of these as doc comments, + // and this also breaks a common pattern where comments introduce + // an entire following block of code (not just the property) + let bar: Bar? = Bar() + print(bar) + } + + var baaz: Baaz { + // Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + var quux: Quux { + didSet { + // Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + testFormatting(for: input, output, rule: .docComments, + exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) + } + + func testPreservesDocComments() { + let input = """ + /// Comment not associated with class + + class Foo { + /** Comment not associated with function */ + + // Documentation for function + func bar() { + /// Comment inside function declaration. + /// This one is multi-line. + + /// This comment is inside a function and precedes a declaration. + /// Since the option to preserve doc comments is enabled, + /// it should be left as-is. + let bar: Bar? = Bar() + print(bar) + } + + // Documentation for property + var baaz: Baaz { + /// Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + // Documentation for function + var quux: Quux { + didSet { + /// Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + let output = """ + /// Comment not associated with class + + class Foo { + /** Comment not associated with function */ + + /// Documentation for function + func bar() { + /// Comment inside function declaration. + /// This one is multi-line. + + /// This comment is inside a function and precedes a declaration. + /// Since the option to preserve doc comments is enabled, + /// it should be left as-is. + let bar: Bar? = Bar() + print(bar) + } + + /// Documentation for property + var baaz: Baaz { + /// Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + /// Documentation for function + var quux: Quux { + didSet { + /// Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + let options = FormatOptions(preserveDocComments: true) + testFormatting(for: input, output, rule: .docComments, options: options, exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) + } + + func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { + let input = """ + // Names of the planets + struct PlanetNames { + // Inner planets + let mercury = "Mercury" + let venus = "Venus" + let earth = "Earth" + let mars = "Mars" + + // Inner planets + let jupiter = "Jupiter" + let saturn = "Saturn" + let uranus = "Uranus" + let neptune = "Neptune" + + /// Dwarf planets + let pluto = "Pluto" + let ceres = "Ceres" + } + """ + + let output = """ + /// Names of the planets + struct PlanetNames { + // Inner planets + let mercury = "Mercury" + let venus = "Venus" + let earth = "Earth" + let mars = "Mars" + + // Inner planets + let jupiter = "Jupiter" + let saturn = "Saturn" + let uranus = "Uranus" + let neptune = "Neptune" + + /// Dwarf planets + let pluto = "Pluto" + let ceres = "Ceres" + } + """ + + testFormatting(for: input, output, rule: .docComments) + } + + func testConvertsCommentsToDocCommentsInConsecutiveDeclarations() { + let input = """ + // Names of the planets + enum PlanetNames { + // Mercuy + case mercury + // Venus + case venus + // Earth + case earth + // Mars + case mars + + // Jupiter + case jupiter + + // Saturn + case saturn + + // Uranus + case uranus + + // Neptune + case neptune + } + """ + + let output = """ + /// Names of the planets + enum PlanetNames { + /// Mercuy + case mercury + /// Venus + case venus + /// Earth + case earth + /// Mars + case mars + + /// Jupiter + case jupiter + + /// Saturn + case saturn + + /// Uranus + case uranus + + /// Neptune + case neptune + } + """ + + testFormatting(for: input, output, rule: .docComments) + } + + func testDoesntConvertCommentBeforeConsecutiveEnumCasesToDocComment() { + let input = """ + // Names of the planets + enum PlanetNames { + // Inner planets + case mercury + case venus + case earth + case mars + + // Inner planets + case jupiter + case saturn + case uranus + case neptune + + // Dwarf planets + case pluto + case ceres + } + """ + + let output = """ + /// Names of the planets + enum PlanetNames { + // Inner planets + case mercury + case venus + case earth + case mars + + // Inner planets + case jupiter + case saturn + case uranus + case neptune + + // Dwarf planets + case pluto + case ceres + } + """ + + testFormatting(for: input, output, rule: .docComments) + } + + func testDoesntConvertAnnotationCommentsToDocComments() { + let input = """ + // swiftformat:disable some_swift_format_rule + let testSwiftLint: Foo + + // swiftlint:disable some_swift_lint_rule + let testSwiftLint: Foo + + // sourcery:directive + let testSourcery: Foo + """ + + testFormatting(for: input, rule: .docComments) + } + + func testDoesntConvertTODOCommentsToDocComments() { + let input = """ + // TODO: Clean up this mess + func doSomething() {} + """ + + testFormatting(for: input, rule: .docComments) + } + + func testDoesntConvertCommentAfterTODOToDocComments() { + let input = """ + // TODO: Clean up this mess + // because it's bothering me + func doSomething() {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testDoesntConvertCommentBeforeTODOToDocComments() { + let input = """ + // Something, something + // TODO: Clean up this mess + func doSomething() {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testConvertNoteCommentsToDocComments() { + let input = """ + // Does something + // Note: not really + func doSomething() {} + """ + let output = """ + /// Does something + /// Note: not really + func doSomething() {} + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testConvertURLCommentsToDocComments() { + let input = """ + // Does something + // http://example.com + func doSomething() {} + """ + let output = """ + /// Does something + /// http://example.com + func doSomething() {} + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testMultilineDocCommentReplaced() { + let input = """ + // A class + // With some other details + class Foo {} + """ + let output = """ + /// A class + /// With some other details + class Foo {} + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testCommentWithBlankLineNotReplaced() { + let input = """ + // A class + // With some other details + + class Foo {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testDocCommentsAssociatedTypeNotReplaced() { + let input = """ + /// An interesting comment about Foo. + associatedtype Foo + """ + testFormatting(for: input, rule: .docComments) + } + + func testNonDocCommentsAssociatedTypeReplaced() { + let input = """ + // An interesting comment about Foo. + associatedtype Foo + """ + let output = """ + /// An interesting comment about Foo. + associatedtype Foo + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testConditionalDeclarationCommentNotReplaced() { + let input = """ + if let foo = bar, + // baz + let baz = bar + {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testCommentInsideSwitchCaseNotReplaced() { + let input = """ + switch foo { + case .bar: + // bar + let bar = baz() + + default: + // baz + let baz = quux() + } + """ + testFormatting(for: input, rule: .docComments) + } + + func testDocCommentInsideIfdef() { + let input = """ + #if DEBUG + // return 3 + func returnNumber() { 3 } + #endif + """ + let output = """ + #if DEBUG + /// return 3 + func returnNumber() { 3 } + #endif + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testDocCommentInsideIfdefElse() { + let input = """ + #if DEBUG + #elseif PROD + /// return 2 + func returnNumber() { 2 } + #else + /// return 3 + func returnNumber() { 3 } + #endif + """ + testFormatting(for: input, rule: .docComments) + } + + func testDocCommentForMacro() { + let input = """ + /// Adds a static `logger` member to the type. + @attached(member, names: named(logger)) public macro StaticLogger( + subsystem: String? = nil, + category: String? = nil + ) = #externalMacro(module: "StaticLoggerMacros", type: "StaticLogger") + """ + testFormatting(for: input, rule: .docComments) + } +} diff --git a/Tests/Rules/DuplicateImportsTests.swift b/Tests/Rules/DuplicateImportsTests.swift new file mode 100644 index 00000000..42569d5a --- /dev/null +++ b/Tests/Rules/DuplicateImportsTests.swift @@ -0,0 +1,64 @@ +// +// DuplicateImportsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 2/7/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class DuplicateImportsTests: XCTestCase { + func testRemoveDuplicateImport() { + let input = "import Foundation\nimport Foundation" + let output = "import Foundation" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testRemoveDuplicateConditionalImport() { + let input = "#if os(iOS)\n import Foo\n import Foo\n#else\n import Bar\n import Bar\n#endif" + let output = "#if os(iOS)\n import Foo\n#else\n import Bar\n#endif" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveOverlappingImports() { + let input = "import MyModule\nimport MyModule.Private" + testFormatting(for: input, rule: .duplicateImports) + } + + func testNoRemoveCaseDifferingImports() { + let input = "import Auth0.Authentication\nimport Auth0.authentication" + testFormatting(for: input, rule: .duplicateImports) + } + + func testRemoveDuplicateImportFunc() { + let input = "import func Foo.bar\nimport func Foo.bar" + let output = "import func Foo.bar" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveTestableDuplicateImport() { + let input = "import Foo\n@testable import Foo" + let output = "\n@testable import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveTestableDuplicateImport2() { + let input = "@testable import Foo\nimport Foo" + let output = "@testable import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveExportedDuplicateImport() { + let input = "import Foo\n@_exported import Foo" + let output = "\n@_exported import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveExportedDuplicateImport2() { + let input = "@_exported import Foo\nimport Foo" + let output = "@_exported import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } +} diff --git a/Tests/Rules/ElseOnSameLineTests.swift b/Tests/Rules/ElseOnSameLineTests.swift new file mode 100644 index 00000000..157e521e --- /dev/null +++ b/Tests/Rules/ElseOnSameLineTests.swift @@ -0,0 +1,382 @@ +// +// ElseOnSameLineTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ElseOnSameLineTests: XCTestCase { + func testElseOnSameLine() { + let input = "if true {\n 1\n}\nelse { 2 }" + let output = "if true {\n 1\n} else { 2 }" + testFormatting(for: input, output, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testElseOnSameLineOnlyAppliedToDanglingBrace() { + let input = "if true { 1 }\nelse { 2 }" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testGuardNotAffectedByElseOnSameLine() { + let input = "guard true\nelse { return }" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testElseOnSameLineDoesntEatPreviousStatement() { + let input = "if true {}\nguard true else { return }" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testElseNotOnSameLineForAllman() { + let input = "if true\n{\n 1\n} else { 2 }" + let output = "if true\n{\n 1\n}\nelse { 2 }" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testElseOnNextLineOption() { + let input = "if true {\n 1\n} else { 2 }" + let output = "if true {\n 1\n}\nelse { 2 }" + let options = FormatOptions(elseOnNextLine: true) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testGuardNotAffectedByElseOnSameLineForAllman() { + let input = "guard true else { return }" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testRepeatWhileNotOnSameLineForAllman() { + let input = "repeat\n{\n foo\n} while x" + let output = "repeat\n{\n foo\n}\nwhile x" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) + } + + func testWhileNotAffectedByElseOnSameLineIfNotRepeatWhile() { + let input = "func foo(x) {}\n\nwhile true {}" + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testCommentsNotDiscardedByElseOnSameLineRule() { + let input = "if true {\n 1\n}\n\n// comment\nelse {}" + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testElseOnSameLineInferenceEdgeCase() { + let input = """ + func foo() { + if let foo == bar { + // ... + } else { + // ... + } + + if let foo == bar, + let baz = quux + { + print() + } + + if let foo == bar, + let baz = quux + { + print() + } + + if let foo == bar, + let baz = quux + { + print() + } + + if let foo == bar, + let baz = quux + { + print() + } + } + """ + let options = FormatOptions(elseOnNextLine: false) + testFormatting(for: input, rule: .elseOnSameLine, options: options, + exclude: [.braces]) + } + + // guardelse = auto + + func testSingleLineGuardElseNotWrappedByDefault() { + let input = "guard foo = bar else {}" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseNotUnwrappedByDefault() { + let input = "guard foo = bar\nelse {}" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseWrappedByDefaultIfBracesOnNextLine() { + let input = "guard foo = bar else\n{}" + let output = "guard foo = bar\nelse {}" + testFormatting(for: input, output, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testMultilineGuardElseNotWrappedByDefault() { + let input = """ + guard let foo = bar, + bar > 5 else { + return + } + """ + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapMultilineStatementBraces]) + } + + func testMultilineGuardElseWrappedByDefaultIfBracesOnNextLine() { + let input = """ + guard let foo = bar, + bar > 5 else + { + return + } + """ + let output = """ + guard let foo = bar, + bar > 5 + else { + return + } + """ + testFormatting(for: input, output, rule: .elseOnSameLine) + } + + func testWrappedMultilineGuardElseCorrectlyIndented() { + let input = """ + func foo() { + guard let foo = bar, + bar > 5 else + { + return + } + } + """ + let output = """ + func foo() { + guard let foo = bar, + bar > 5 + else { + return + } + } + """ + testFormatting(for: input, output, rule: .elseOnSameLine) + } + + // guardelse = nextLine + + func testSingleLineGuardElseNotWrapped() { + let input = "guard foo = bar else {}" + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseNotUnwrapped() { + let input = "guard foo = bar\nelse {}" + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseWrappedIfBracesOnNextLine() { + let input = "guard foo = bar else\n{}" + let output = "guard foo = bar\nelse {}" + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testMultilineGuardElseWrapped() { + let input = """ + guard let foo = bar, + bar > 5 else { + return + } + """ + let output = """ + guard let foo = bar, + bar > 5 + else { + return + } + """ + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testMultilineGuardElseEndingInParen() { + let input = """ + guard let foo = bar, + let baz = quux() else + { + return + } + """ + let output = """ + guard let foo = bar, + let baz = quux() + else { + return + } + """ + let options = FormatOptions(guardElsePosition: .auto) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options) + } + + // guardelse = sameLine + + func testMultilineGuardElseUnwrapped() { + let input = """ + guard let foo = bar, + bar > 5 + else { + return + } + """ + let output = """ + guard let foo = bar, + bar > 5 else { + return + } + """ + let options = FormatOptions(guardElsePosition: .sameLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testGuardElseUnwrappedIfBracesOnNextLine() { + let input = "guard foo = bar\nelse {}" + let output = "guard foo = bar else {}" + let options = FormatOptions(guardElsePosition: .sameLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options) + } + + func testPreserveBlankLineBeforeElse() { + let input = """ + if foo { + print("foo") + } + + else if bar { + print("bar") + } + + else { + print("baaz") + } + """ + + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testPreserveBlankLineBeforeElseOnSameLine() { + let input = """ + if foo { + print("foo") + } + + else if bar { + print("bar") + } + + else { + print("baaz") + } + """ + + let options = FormatOptions(elseOnNextLine: false) + testFormatting(for: input, rule: .elseOnSameLine, options: options) + } + + func testPreserveBlankLineBeforeElseWithComments() { + let input = """ + if foo { + print("foo") + } + // Comment before else if + else if bar { + print("bar") + } + + // Comment before else + else { + print("baaz") + } + """ + + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testPreserveBlankLineBeforeElseDoesntAffectOtherCases() { + let input = """ + if foo { + print("foo") + } + else { + print("bar") + } + + guard foo else { + return + } + + guard + let foo, + let bar, + lat baaz else + { + return + } + """ + + let output = """ + if foo { + print("foo") + } else { + print("bar") + } + + guard foo else { + return + } + + guard + let foo, + let bar, + lat baaz + else { + return + } + """ + + let options = FormatOptions(elseOnNextLine: false, guardElsePosition: .nextLine) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) + } +} diff --git a/Tests/Rules/EmptyBracesTests.swift b/Tests/Rules/EmptyBracesTests.swift new file mode 100644 index 00000000..71be6d6f --- /dev/null +++ b/Tests/Rules/EmptyBracesTests.swift @@ -0,0 +1,111 @@ +// +// EmptyBracesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/2/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class EmptyBracesTests: XCTestCase { + func testLinebreaksRemovedInsideBraces() { + let input = "func foo() {\n \n }" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .emptyBraces) + } + + func testCommentNotRemovedInsideBraces() { + let input = "func foo() { // foo\n}" + testFormatting(for: input, rule: .emptyBraces) + } + + func testEmptyBracesNotRemovedInDoCatch() { + let input = """ + do { + } catch is FooError { + } catch {} + """ + testFormatting(for: input, rule: .emptyBraces) + } + + func testEmptyBracesNotRemovedInIfElse() { + let input = """ + if bar { + } else if foo { + } else {} + """ + testFormatting(for: input, rule: .emptyBraces) + } + + func testSpaceRemovedInsideEmptybraces() { + let input = "foo { }" + let output = "foo {}" + testFormatting(for: input, output, rule: .emptyBraces) + } + + func testSpaceAddedInsideEmptyBracesWithSpacedConfiguration() { + let input = "foo {}" + let output = "foo { }" + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, output, rule: .emptyBraces, options: options) + } + + func testLinebreaksRemovedInsideBracesWithSpacedConfiguration() { + let input = "func foo() {\n \n }" + let output = "func foo() { }" + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, output, rule: .emptyBraces, options: options) + } + + func testCommentNotRemovedInsideBracesWithSpacedConfiguration() { + let input = "func foo() { // foo\n}" + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesSpaceNotRemovedInDoCatchWithSpacedConfiguration() { + let input = """ + do { + } catch is FooError { + } catch { } + """ + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesSpaceNotRemovedInIfElseWithSpacedConfiguration() { + let input = """ + if bar { + } else if foo { + } else { } + """ + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesLinebreakNotRemovedInIfElseWithLinebreakConfiguration() { + let input = """ + if bar { + } else if foo { + } else { + } + """ + let options = FormatOptions(emptyBracesSpacing: .linebreak) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesLinebreakIndentedCorrectly() { + let input = """ + func foo() { + if bar { + } else if foo { + } else { + } + } + """ + let options = FormatOptions(emptyBracesSpacing: .linebreak) + testFormatting(for: input, rule: .emptyBraces, options: options) + } +} diff --git a/Tests/Rules/EnumNamespacesTests.swift b/Tests/Rules/EnumNamespacesTests.swift new file mode 100644 index 00000000..0d9a9247 --- /dev/null +++ b/Tests/Rules/EnumNamespacesTests.swift @@ -0,0 +1,455 @@ +// +// EnumNamespacesTests.swift +// SwiftFormatTests +// +// Created by Facundo Menzella on 9/20/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class EnumNamespacesTests: XCTestCase { + func testEnumNamespacesClassAsProtocolRestriction() { + let input = """ + @objc protocol Foo: class { + @objc static var expressionTypes: [String: RuntimeType] { get } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesConformingOtherType() { + let input = "private final class CustomUITableViewCell: UITableViewCell {}" + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesImportClass() { + let input = """ + import class MyUIKit.AutoHeightTableView + + enum Foo { + static var bar: String + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesImportStruct() { + let input = """ + import struct Core.CurrencyFormatter + + enum Foo { + static var bar: String + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesClassFunction() { + let input = """ + class Container { + class func bar() {} + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesRemovingExtraKeywords() { + let input = """ + final class MyNamespace { + static let bar = "bar" + } + """ + let output = """ + enum MyNamespace { + static let bar = "bar" + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes() { + let input = """ + enum Namespace {} + extension Namespace { + struct Constants { + static let bar = "bar" + } + } + """ + let output = """ + enum Namespace {} + extension Namespace { + enum Constants { + static let bar = "bar" + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes2() { + let input = """ + struct Namespace { + struct NestedNamespace { + static let foo: Int + static let bar: Int + } + } + """ + let output = """ + enum Namespace { + enum NestedNamespace { + static let foo: Int + static let bar: Int + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes3() { + let input = """ + struct Namespace { + struct TypeNestedInNamespace { + let foo: Int + let bar: Int + } + } + """ + let output = """ + enum Namespace { + struct TypeNestedInNamespace { + let foo: Int + let bar: Int + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes4() { + let input = """ + struct Namespace { + static func staticFunction() { + struct NestedType { + init() {} + } + } + } + """ + let output = """ + enum Namespace { + static func staticFunction() { + struct NestedType { + init() {} + } + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes5() { + let input = """ + struct Namespace { + static func staticFunction() { + func nestedFunction() { /* ... */ } + } + } + """ + let output = """ + enum Namespace { + static func staticFunction() { + func nestedFunction() { /* ... */ } + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticVariable() { + let input = """ + struct Constants { + static let β = 0, 5 + } + """ + let output = """ + enum Constants { + static let β = 0, 5 + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticAndInstanceVariable() { + let input = """ + struct Constants { + static let β = 0, 5 + let Ɣ = 0, 3 + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticFunction() { + let input = """ + struct Constants { + static func remoteConfig() -> Int { + return 10 + } + } + """ + let output = """ + enum Constants { + static func remoteConfig() -> Int { + return 10 + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticAndInstanceFunction() { + let input = """ + struct Constants { + static func remoteConfig() -> Int { + return 10 + } + + func instanceConfig(offset: Int) -> Int { + return offset + 10 + } + } + """ + + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespaceDoesNothing() { + let input = """ + struct Foo { + #if BAR + func something() {} + #else + func something() {} + #endif + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespaceDoesNothingForEmptyDeclaration() { + let input = """ + struct Foo {} + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfTypeInitializedInternally() { + let input = """ + struct Foo { + static func bar() { + Foo().baz + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfInitializedInternally() { + let input = """ + struct Foo { + static func bar() { + Self().baz + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfInitializedInternally2() { + let input = """ + struct Foo { + static func bar() -> Foo { + self.init() + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfAssignedInternally() { + let input = """ + class Foo { + public static func bar() { + let bundle = Bundle(for: self) + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfAssignedInternally2() { + let input = """ + class Foo { + public static func bar() { + let `class` = self + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfAssignedInternally3() { + let input = """ + class Foo { + public static func bar() { + let `class` = Foo.self + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testClassFuncNotReplacedByEnum() { + let input = """ + class Foo { + class override func foo() { + Bar.bar() + } + } + """ + testFormatting(for: input, rule: .enumNamespaces, + exclude: [.modifierOrder]) + } + + func testOpenClassNotReplacedByEnum() { + let input = """ + open class Foo { + public static let bar = "bar" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testClassNotReplacedByEnum() { + let input = """ + class Foo { + public static let bar = "bar" + } + """ + let options = FormatOptions(enumNamespaces: .structsOnly) + testFormatting(for: input, rule: .enumNamespaces, options: options) + } + + func testEnumNamespacesAfterImport() { + // https://github.com/nicklockwood/SwiftFormat/issues/1569 + let input = """ + import Foundation + + final class MyViewModel2 { + static let = "A" + } + """ + + let output = """ + import Foundation + + enum MyViewModel2 { + static let = "A" + } + """ + + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesAfterImport2() { + // https://github.com/nicklockwood/SwiftFormat/issues/1569 + let input = """ + final class MyViewModel { + static let = "A" + } + + import Foundation + + final class MyViewModel2 { + static let = "A" + } + """ + + let output = """ + enum MyViewModel { + static let = "A" + } + + import Foundation + + enum MyViewModel2 { + static let = "A" + } + """ + + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedToNonFinalClass() { + let input = """ + class Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfObjC() { + let input = """ + @objc(NSFoo) + final class Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfMacro() { + let input = """ + @FooBar + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfParameterizedMacro() { + let input = """ + @FooMacro(arg: "Foo") + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfGenericMacro() { + let input = """ + @FooMacro + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfGenericParameterizedMacro() { + let input = """ + @FooMacro(arg: 5) + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } +} diff --git a/Tests/Rules/ExtensionAccessControlTests.swift b/Tests/Rules/ExtensionAccessControlTests.swift new file mode 100644 index 00000000..38eb7a8f --- /dev/null +++ b/Tests/Rules/ExtensionAccessControlTests.swift @@ -0,0 +1,463 @@ +// +// ExtensionAccessControlTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 9/25/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ExtensionAccessControlTests: XCTestCase { + func testUpdatesVisibilityOfExtensionMembers() { + let input = """ + private extension Foo { + var publicProperty: Int { 10 } + public func publicFunction1() {} + func publicFunction2() {} + internal func internalFunction() {} + private func privateFunction() {} + fileprivate var privateProperty: Int { 10 } + } + """ + + let output = """ + extension Foo { + fileprivate var publicProperty: Int { 10 } + public func publicFunction1() {} + fileprivate func publicFunction2() {} + internal func internalFunction() {} + private func privateFunction() {} + fileprivate var privateProperty: Int { 10 } + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations), + exclude: [.redundantInternal] + ) + } + + func testUpdatesVisibilityOfExtensionInConditionalCompilationBlock() { + let input = """ + #if DEBUG + public extension Foo { + var publicProperty: Int { 10 } + } + #endif + """ + + let output = """ + #if DEBUG + extension Foo { + public var publicProperty: Int { 10 } + } + #endif + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testUpdatesVisibilityOfExtensionMembersInConditionalCompilationBlock() { + let input = """ + public extension Foo { + #if DEBUG + var publicProperty: Int { 10 } + #endif + } + """ + + let output = """ + extension Foo { + #if DEBUG + public var publicProperty: Int { 10 } + #endif + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testDoesntUpdateDeclarationsInsideTypeInsideExtension() { + let input = """ + public extension Foo { + struct Bar { + var baz: Int + var quux: Int + } + } + """ + + let output = """ + extension Foo { + public struct Bar { + var baz: Int + var quux: Int + } + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testDoesNothingForInternalExtension() { + let input = """ + extension Foo { + func bar() {} + func baz() {} + public func quux() {} + } + """ + + testFormatting( + for: input, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testPlacesVisibilityKeywordAfterAnnotations() { + let input = """ + public extension Foo { + @discardableResult + func bar() -> Int { 10 } + + /// Doc comment + @discardableResult + @available(iOS 10.0, *) + func baz() -> Int { 10 } + + @objc func quux() {} + @available(iOS 10.0, *) func quixotic() {} + } + """ + + let output = """ + extension Foo { + @discardableResult + public func bar() -> Int { 10 } + + /// Doc comment + @discardableResult + @available(iOS 10.0, *) + public func baz() -> Int { 10 } + + @objc public func quux() {} + @available(iOS 10.0, *) public func quixotic() {} + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testConvertsExtensionPrivateToMemberFileprivate() { + let input = """ + private extension Foo { + var bar: Int + } + + let bar = Foo().bar + """ + + let output = """ + extension Foo { + fileprivate var bar: Int + } + + let bar = Foo().bar + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), + exclude: [.propertyType] + ) + } + + // MARK: extensionAccessControl .onExtension + + func testUpdatedVisibilityOfExtension() { + let input = """ + extension Foo { + public func bar() {} + public var baz: Int { 10 } + + public struct Foo2 { + var quux: Int + } + } + """ + + let output = """ + public extension Foo { + func bar() {} + var baz: Int { 10 } + + struct Foo2 { + var quux: Int + } + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testUpdatedVisibilityOfExtensionWithDeclarationsInConditionalCompilation() { + let input = """ + extension Foo { + #if DEBUG + public func bar() {} + public var baz: Int { 10 } + #endif + } + """ + + let output = """ + public extension Foo { + #if DEBUG + func bar() {} + var baz: Int { 10 } + #endif + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionVisibilityWithoutMajorityBodyVisibility() { + let input = """ + extension Foo { + public func foo() {} + public func bar() {} + var baz: Int { 10 } + var quux: Int { 5 } + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testUpdateExtensionVisibilityWithMajorityBodyVisibility() { + let input = """ + extension Foo { + public func foo() {} + public func bar() {} + public var baz: Int { 10 } + var quux: Int { 5 } + } + """ + + let output = """ + public extension Foo { + func foo() {} + func bar() {} + var baz: Int { 10 } + internal var quux: Int { 5 } + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionVisibilityWhenMajorityBodyVisibilityIsntMostVisible() { + let input = """ + extension Foo { + func foo() {} + func bar() {} + public var baz: Int { 10 } + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionVisibilityWithInternalDeclarations() { + let input = """ + extension Foo { + func bar() {} + var baz: Int { 10 } + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionThatAlreadyHasCorrectVisibilityKeyword() { + let input = """ + public extension Foo { + func bar() {} + func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testUpdatesExtensionThatHasHigherACLThanBodyDeclarations() { + let input = """ + public extension Foo { + fileprivate func bar() {} + fileprivate func baz() {} + } + """ + + let output = """ + fileprivate extension Foo { + func bar() {} + func baz() {} + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl, + exclude: [.redundantFileprivate]) + } + + func testDoesntHoistPrivateVisibilityFromExtensionBodyDeclarations() { + let input = """ + extension Foo { + private var bar() {} + private func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntUpdatesExtensionThatHasLowerACLThanBodyDeclarations() { + let input = """ + private extension Foo { + public var bar() {} + public func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntReduceVisibilityOfImplicitInternalDeclaration() { + let input = """ + extension Foo { + fileprivate var bar() {} + func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testUpdatesExtensionThatHasRedundantACLOnBodyDeclarations() { + let input = """ + public extension Foo { + func bar() {} + public func baz() {} + } + """ + + let output = """ + public extension Foo { + func bar() {} + func baz() {} + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testNoHoistAccessModifierForOpenMethod() { + let input = """ + extension Foo { + open func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDontChangePrivateExtensionToFileprivate() { + let input = """ + private extension Foo { + func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDontRemoveInternalKeywordFromExtension() { + let input = """ + internal extension Foo { + func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantInternal]) + } + + func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() { + let input = """ + extension Foo: Bar { + public func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testProtocolConformanceCheckNotFooledByWhereClause() { + let input = """ + extension Foo where Self: Bar { + public func bar() {} + } + """ + let output = """ + public extension Foo where Self: Bar { + func bar() {} + } + """ + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testAccessNotHoistedIfTypeVisibilityIsLower() { + let input = """ + class Foo {} + + extension Foo { + public func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() { + let input = """ + #if os(Linux) + #error("Linux is currently not supported") + #endif + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() { + let input = """ + struct Foo { + // This type is empty + } + + extension Foo { + // This extension is empty + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } +} diff --git a/Tests/Rules/FileHeaderTests.swift b/Tests/Rules/FileHeaderTests.swift new file mode 100644 index 00000000..98d0d493 --- /dev/null +++ b/Tests/Rules/FileHeaderTests.swift @@ -0,0 +1,514 @@ +// +// FileHeaderTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/7/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class FileHeaderTests: XCTestCase { + func testStripHeader() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testStripHeaderWithWhenHeaderContainsUrl() { + let input = """ + // + // RulesTests+General.swift + // SwiftFormatTests + // + // Created by Nick Lockwood on 02/10/2021. + // Copyright © 2021 Nick Lockwood. All rights reserved. + // https://some.example.com + // + + /// func + func foo() {} + """ + let output = "/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testReplaceHeaderWhenFileContainsNoCode() { + let input = "// foobar" + let options = FormatOptions(fileHeader: "// foobar") + testFormatting(for: input, rule: .fileHeader, options: options, + exclude: [.linebreakAtEndOfFile]) + } + + func testReplaceHeaderWhenFileContainsNoCode2() { + let input = "// foobar\n" + let options = FormatOptions(fileHeader: "// foobar") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testMultilineCommentHeader() { + let input = "/****************************/\n/* Created by Nick Lockwood */\n/****************************/\n\n\n/// func\nfunc foo() {}" + let output = "/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoStripHeaderWhenDisabled() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: .ignore) + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripComment() { + let input = "\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripPackageHeader() { + let input = "// swift-tools-version:4.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripFormatDirective() { + let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripFormatDirectiveAfterHeader() { + let input = "// header\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoReplaceFormatDirective() { + let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let output = "// Hello World\n\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "// Hello World") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testSetSingleLineHeader() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "// Hello World\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "// Hello World") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testSetMultilineHeader() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "// Hello\n// World\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "// Hello\n// World") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testSetMultilineHeaderWithMarkup() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "/*--- Hello ---*/\n/*--- World ---*/\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "/*--- Hello ---*/\n/*--- World ---*/") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoStripHeaderIfRuleDisabled() { + let input = "// swiftformat:disable fileHeader\n// test\n// swiftformat:enable fileHeader\n\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripHeaderIfNextRuleDisabled() { + let input = "// swiftformat:disable:next fileHeader\n// test\n\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripHeaderDocWithNewlineBeforeCode() { + let input = "/// Header doc\n\nclass Foo {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options, exclude: [.docComments]) + } + + func testNoDuplicateHeaderIfMissingTrailingBlankLine() { + let input = "// Header comment\nclass Foo {}" + let output = "// Header comment\n\nclass Foo {}" + let options = FormatOptions(fileHeader: "Header comment") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoDuplicateHeaderContainingPossibleCommentDirective() { + let input = """ + // Copyright (c) 2010-2023 Foobar + // + // SPDX-License-Identifier: EPL-2.0 + + class Foo {} + """ + let output = """ + // Copyright (c) 2010-2024 Foobar + // + // SPDX-License-Identifier: EPL-2.0 + + class Foo {} + """ + let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// SPDX-License-Identifier: EPL-2.0") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoDuplicateHeaderContainingCommentDirective() { + let input = """ + // Copyright (c) 2010-2023 Foobar + // + // swiftformat:disable all + + class Foo {} + """ + let output = """ + // Copyright (c) 2010-2024 Foobar + // + // swiftformat:disable all + + class Foo {} + """ + let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// swiftformat:disable all") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderYearReplacement() { + let input = "let foo = bar" + let output: String = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy" + return "// Copyright © \(formatter.string(from: Date()))\n\nlet foo = bar" + }() + let options = FormatOptions(fileHeader: "// Copyright © {year}") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderCreationYearReplacement() { + let input = "let foo = bar" + let date = Date(timeIntervalSince1970: 0) + let output: String = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy" + return "// Copyright © \(formatter.string(from: date))\n\nlet foo = bar" + }() + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// Copyright © {created.year}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderAuthorReplacement() { + let name = "Test User" + let email = "test@email.com" + let input = "let foo = bar" + let output = "// Created by \(name) \(email)\n\nlet foo = bar" + let fileInfo = FileInfo(replacements: [.authorName: .constant(name), .authorEmail: .constant(email)]) + let options = FormatOptions(fileHeader: "// Created by {author.name} {author.email}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderAuthorReplacement2() { + let author = "Test User " + let input = "let foo = bar" + let output = "// Created by \(author)\n\nlet foo = bar" + let fileInfo = FileInfo(replacements: [.author: .constant(author)]) + let options = FormatOptions(fileHeader: "// Created by {author}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderMultipleReplacement() { + let name = "Test User" + let input = "let foo = bar" + let output = "// Copyright © \(name)\n// Created by \(name)\n\nlet foo = bar" + let fileInfo = FileInfo(replacements: [.authorName: .constant(name)]) + let options = FormatOptions(fileHeader: "// Copyright © {author.name}\n// Created by {author.name}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderCreationDateReplacement() { + let input = "let foo = bar" + let date = Date(timeIntervalSince1970: 0) + let output: String = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + return "// Created by Nick Lockwood on \(formatter.string(from: date)).\n\nlet foo = bar" + }() + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingIso() { + let date = createTestDate("2023-08-09") + + let input = "let foo = bar" + let output = "// 2023-08-09\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", dateFormat: .iso, fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingDayMonthYear() { + let date = createTestDate("2023-08-09") + + let input = "let foo = bar" + let output = "// 09/08/2023\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", dateFormat: .dayMonthYear, fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingMonthDayYear() { + let date = createTestDate("2023-08-09") + + let input = "let foo = bar" + let output = "// 08/09/2023\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", + dateFormat: .monthDayYear, + fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingCustom() { + let date = createTestDate("2023-08-09T12:59:30.345Z", .timestamp) + + let input = "let foo = bar" + let output = "// 23.08.09-12.59.30.345\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", + dateFormat: .custom("yy.MM.dd-HH.mm.ss.SSS"), + timeZone: .identifier("UTC"), + fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderFileReplacement() { + let input = "let foo = bar" + let output = "// MyFile.swift\n\nlet foo = bar" + let fileInfo = FileInfo(filePath: "~/MyFile.swift") + let options = FormatOptions(fileHeader: "// {file}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testEdgeCaseHeaderEndIndexPlusNewHeaderTokensCountEqualsFileTokensEndIndex() { + let input = "// Header comment\n\nclass Foo {}" + let output = "// Header line1\n// Header line2\n\nclass Foo {}" + let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderBlankLineNotRemovedBeforeFollowingComment() { + let input = """ + // + // Header + // + + // Something else... + """ + let options = FormatOptions(fileHeader: "//\n// Header\n//") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testFileHeaderBlankLineNotRemovedBeforeFollowingComment2() { + let input = """ + // + // Header + // + + // + // Something else... + // + """ + let options = FormatOptions(fileHeader: "//\n// Header\n//") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testFileHeaderRemovedAfterHashbang() { + let input = """ + #!/usr/bin/swift + + // Header line1 + // Header line2 + + let foo = 5 + """ + let output = """ + #!/usr/bin/swift + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderPlacedAfterHashbang() { + let input = """ + #!/usr/bin/swift + + let foo = 5 + """ + let output = """ + #!/usr/bin/swift + + // Header line1 + // Header line2 + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testBlankLineAfterHashbangNotRemovedByFileHeader() { + let input = """ + #!/usr/bin/swift + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testLineAfterHashbangNotAffectedByFileHeaderRemoval() { + let input = """ + #!/usr/bin/swift + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testDisableFileHeaderCommentRespectedAfterHashbang() { + let input = """ + #!/usr/bin/swift + // swiftformat:disable fileHeader + + // Header line1 + // Header line2 + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testDisableFileHeaderCommentRespectedAfterHashbang2() { + let input = """ + #!/usr/bin/swift + + // swiftformat:disable fileHeader + // Header line1 + // Header line2 + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + private func testTimeZone( + timeZone: FormatTimeZone, + tests: [String: String] + ) { + for (input, expected) in tests { + let date = createTestDate(input, .time) + let input = "let foo = bar" + let output = "// \(expected)\n\nlet foo = bar" + + let fileInfo = FileInfo(creationDate: date) + + let options = FormatOptions( + fileHeader: "// {created}", + dateFormat: .custom("HH:mm"), + timeZone: timeZone, + fileInfo: fileInfo + ) + + testFormatting(for: input, output, + rule: .fileHeader, + options: options) + } + } + + func testFileHeaderDateTimeZoneSystem() { + let baseDate = createTestDate("15:00Z", .time) + let offset = TimeZone.current.secondsFromGMT(for: baseDate) + + let date = baseDate.addingTimeInterval(Double(offset)) + + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + + let expected = formatter.string(from: date) + + testTimeZone(timeZone: .system, tests: [ + "15:00Z": expected, + "16:00+1": expected, + "01:00+10": expected, + "16:30+0130": expected, + ]) + } + + func testFileHeaderDateTimeZoneAbbreviations() { + // GMT+0530 + testTimeZone(timeZone: FormatTimeZone(rawValue: "IST")!, tests: [ + "15:00Z": "20:30", + "16:00+1": "20:30", + "01:00+10": "20:30", + "16:30+0130": "20:30", + ]) + } + + func testFileHeaderDateTimeZoneIdentifiers() { + // GMT+0845 + testTimeZone(timeZone: FormatTimeZone(rawValue: "Australia/Eucla")!, tests: [ + "15:00Z": "23:45", + "16:00+1": "23:45", + "01:00+10": "23:45", + "16:30+0130": "23:45", + ]) + } + + func testGitHelpersReturnsInfo() { + let info = GitFileInfo(url: URL(fileURLWithPath: #file)) + XCTAssertNotNil(info?.authorName) + XCTAssertNotNil(info?.authorEmail) + XCTAssertNotNil(info?.creationDate) + } + + func testFileHeaderRuleThrowsIfCreationDateUnavailable() { + let input = "let foo = bar" + let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: FileInfo()) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) + } + + func testFileHeaderRuleThrowsIfFileNameUnavailable() { + let input = "let foo = bar" + let options = FormatOptions(fileHeader: "// {file}.", fileInfo: FileInfo()) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) + } +} + +private enum TestDateFormat: String { + case basic = "yyyy-MM-dd" + case time = "HH:mmZZZZZ" + case timestamp = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" +} + +private func createTestDate( + _ input: String, + _ format: TestDateFormat = .basic +) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = format.rawValue + formatter.timeZone = .current + + return formatter.date(from: input)! +} diff --git a/Tests/Rules/GenericExtensionsTests.swift b/Tests/Rules/GenericExtensionsTests.swift new file mode 100644 index 00000000..5537b280 --- /dev/null +++ b/Tests/Rules/GenericExtensionsTests.swift @@ -0,0 +1,121 @@ +// +// GenericExtensionsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/18/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class GenericExtensionsTests: XCTestCase { + func testUpdatesArrayGenericExtensionToAngleBracketSyntax() { + let input = "extension Array where Element == Foo {}" + let output = "extension Array {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { + let input = "extension Optional where Wrapped == Foo {}" + let output = "extension Optional {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { + let input = "extension Array where Self.Element == Foo {}" + let output = "extension Array {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesArrayWithGenericElement() { + let input = "extension Array where Element == Foo {}" + let output = "extension Array> {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { + let input = "extension Dictionary where Key == Foo, Value == Bar {}" + let output = "extension Dictionary {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testRequiresAllGenericTypesToBeProvided() { + // No type provided for `Value`, so we can't use the angle bracket syntax + let input = "extension Dictionary where Key == Foo {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .genericExtensions, options: options) + } + + func testHandlesNestedCollectionTypes() { + let input = "extension Array where Element == [[Foo: Bar]] {}" + let output = "extension Array<[[Foo: Bar]]> {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testDoesntUpdateIneligibleConstraints() { + // This could potentially by `extension Optional` in a future language version + // but that syntax isn't implemented as of Swift 5.7 + let input = "extension Optional where Wrapped: Fooable {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .genericExtensions, options: options) + } + + func testPreservesOtherConstraintsInWhereClause() { + let input = "extension Collection where Element == String, Index == Int {}" + let output = "extension Collection where Index == Int {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options) + } + + func testSupportsUserProvidedGenericTypes() { + let input = """ + extension StateStore where State == FooState, Action == FooAction {} + extension LinkedList where Element == Foo {} + """ + let output = """ + extension StateStore {} + extension LinkedList {} + """ + + let options = FormatOptions( + genericTypes: "LinkedList;StateStore", + swiftVersion: "5.7" + ) + testFormatting(for: input, output, rule: .genericExtensions, options: options) + } + + func testSupportsMultilineUserProvidedGenericTypes() { + let input = """ + extension Reducer where + State == MyFeatureState, + Action == MyFeatureAction, + Environment == ApplicationEnvironment + {} + """ + let output = """ + extension Reducer {} + """ + + let options = FormatOptions( + genericTypes: "Reducer", + swiftVersion: "5.7" + ) + testFormatting(for: input, output, rule: .genericExtensions, options: options) + } +} diff --git a/Tests/Rules/HeaderFileNameTests.swift b/Tests/Rules/HeaderFileNameTests.swift new file mode 100644 index 00000000..f3644ce9 --- /dev/null +++ b/Tests/Rules/HeaderFileNameTests.swift @@ -0,0 +1,27 @@ +// +// HeaderFileNameTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 5/3/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HeaderFileNameTests: XCTestCase { + func testHeaderFileNameReplaced() { + let input = """ + // MyFile.swift + + let foo = bar + """ + let output = """ + // YourFile.swift + + let foo = bar + """ + let options = FormatOptions(fileInfo: FileInfo(filePath: "~/YourFile.swift")) + testFormatting(for: input, output, rule: .headerFileName, options: options) + } +} diff --git a/Tests/Rules/HoistAwaitTests.swift b/Tests/Rules/HoistAwaitTests.swift new file mode 100644 index 00000000..b9636f2f --- /dev/null +++ b/Tests/Rules/HoistAwaitTests.swift @@ -0,0 +1,234 @@ +// +// HoistAwaitTests.swift +// SwiftFormatTests +// +// Created by Facundo Menzella on 2/9/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HoistAwaitTests: XCTestCase { + func testHoistAwait() { + let input = "greet(await name, await surname)" + let output = "await greet(name, surname)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitInsideIf() { + let input = "if !(await isSomething()) {}" + let output = "if await !(isSomething()) {}" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), + exclude: [.redundantParens]) + } + + func testHoistAwaitInsideArgument() { + let input = """ + array.append(contentsOf: try await asyncFunction(param1: param1)) + """ + let output = """ + await array.append(contentsOf: try asyncFunction(param1: param1)) + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistAwaitInsideStringInterpolation() { + let input = "\"\\(replace(regex: await something()))\"" + let output = "await \"\\(replace(regex: something()))\"" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitInsideStringInterpolation2() { + let input = """ + "Hello \\(try await someValue())" + """ + let output = """ + await "Hello \\(try someValue())" + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testNoHoistAwaitInsideDo() { + let input = """ + do { + rg.box.seal(.fulfilled(await body(error))) + } + """ + let output = """ + do { + await rg.box.seal(.fulfilled(body(error))) + } + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testNoHoistAwaitInsideDoThrows() { + let input = """ + do throws(Foo) { + rg.box.seal(.fulfilled(await body(error))) + } + """ + let output = """ + do throws(Foo) { + await rg.box.seal(.fulfilled(body(error))) + } + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitInExpressionWithNoSpaces() { + let input = "let foo=bar(contentsOf:await baz())" + let output = "let foo=await bar(contentsOf:baz())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundOperators]) + } + + func testHoistAwaitInExpressionWithExcessSpaces() { + let input = "let foo = bar ( contentsOf: await baz() )" + let output = "let foo = await bar ( contentsOf: baz() )" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), + exclude: [.spaceAroundParens, .spaceInsideParens]) + } + + func testHoistAwaitWithReturn() { + let input = "return .enumCase(try await service.greet())" + let output = "return await .enumCase(try service.greet())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistDeeplyNestedAwaits() { + let input = "let foo = (bar: (5, (await quux(), 6)), baz: (7, quux: await quux()))" + let output = "let foo = await (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfClosure() { + let input = "let foo = { (await bar(), 5) }" + let output = "let foo = { await (bar(), 5) }" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfClosureWithArguments() { + let input = "let foo = { bar in (await baz(bar), 5) }" + let output = "let foo = { bar in await (baz(bar), 5) }" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfForCondition() { + let input = "for foo in bar(await baz()) {}" + let output = "for foo in await bar(baz()) {}" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfForIndex() { + let input = "for await foo in asyncSequence() {}" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitWithInitAssignment() { + let input = "let variable = String(try await asyncFunction())" + let output = "let variable = await String(try asyncFunction())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistAwaitWithAssignment() { + let input = "let variable = (try await asyncFunction())" + let output = "let variable = await (try asyncFunction())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistAwaitInRedundantScopePriorToNumber() { + let input = """ + let identifiersTypes = 1 + (try? await asyncFunction(param1: param1)) + """ + let output = """ + let identifiersTypes = 1 + await (try? asyncFunction(param1: param1)) + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitOnlyOne() { + let input = "greet(name, await surname)" + let output = "await greet(name, surname)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitRedundantAwait() { + let input = "await greet(await name, await surname)" + let output = "await greet(name, surname)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitDoesNothing() { + let input = "await greet(name, surname)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testNoHoistAwaitBeforeTry() { + let input = "try foo(await bar())" + let output = "try await foo(bar())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testNoHoistAwaitInCapturingFunction() { + let input = "foo(await bar)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) + } + + func testNoHoistSecondArgumentAwaitInCapturingFunction() { + let input = "foo(bar, await baz)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) + } + + func testHoistAwaitAfterOrdinaryOperator() { + let input = "let foo = bar + (await baz)" + let output = "let foo = await bar + (baz)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) + } + + func testHoistAwaitAfterUnknownOperator() { + let input = "let foo = bar ??? (await baz)" + let output = "let foo = await bar ??? (baz)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) + } + + func testNoHoistAwaitAfterCapturingOperator() { + let input = "let foo = await bar ??? (await baz)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(asyncCapturing: ["???"], swiftVersion: "5.5")) + } + + func testNoHoistAwaitInMacroArgument() { + let input = "#expect (await monitor.isAvailable == false)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundParens]) + } +} diff --git a/Tests/Rules/HoistPatternLetTests.swift b/Tests/Rules/HoistPatternLetTests.swift new file mode 100644 index 00000000..eeef50da --- /dev/null +++ b/Tests/Rules/HoistPatternLetTests.swift @@ -0,0 +1,423 @@ +// +// HoistPatternLetTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/6/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HoistPatternLetTests: XCTestCase { + // hoist = true + + func testHoistCaseLet() { + let input = "if case .foo(let bar, let baz) = quux {}" + let output = "if case let .foo(bar, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistLabelledCaseLet() { + let input = "if case .foo(bar: let bar, baz: let baz) = quux {}" + let output = "if case let .foo(bar: bar, baz: baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistCaseVar() { + let input = "if case .foo(var bar, var baz) = quux {}" + let output = "if case var .foo(bar, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNoHoistMixedCaseLetVar() { + let input = "if case .foo(let bar, var baz) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testNoHoistIfFirstArgSpecified() { + let input = "if case .foo(bar, let baz) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testNoHoistIfLastArgSpecified() { + let input = "if case .foo(let bar, baz) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testHoistIfArgIsNumericLiteral() { + let input = "if case .foo(5, let baz) = quux {}" + let output = "if case let .foo(5, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistIfArgIsEnumCaseLiteral() { + let input = "if case .foo(.bar, let baz) = quux {}" + let output = "if case let .foo(.bar, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistIfArgIsNamespacedEnumCaseLiteralInParens() { + let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" + let output = "switch foo {\ncase let (Foo.bar(baz)):\n}" + testFormatting(for: input, output, rule: .hoistPatternLet, exclude: [.redundantParens]) + } + + func testHoistIfFirstArgIsUnderscore() { + let input = "if case .foo(_, let baz) = quux {}" + let output = "if case let .foo(_, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistIfSecondArgIsUnderscore() { + let input = "if case .foo(let baz, _) = quux {}" + let output = "if case let .foo(baz, _) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNestedHoistLet() { + let input = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" + let output = "if case let (.foo(a, b), .bar(c, d)) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistCommaSeparatedSwitchCaseLets() { + let input = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" + let output = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" + testFormatting(for: input, output, rule: .hoistPatternLet, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testHoistNewlineSeparatedSwitchCaseLets() { + let input = """ + switch foo { + case .foo(let bar), + .bar(let bar): + } + """ + + let output = """ + switch foo { + case let .foo(bar), + let .bar(bar): + } + """ + + testFormatting(for: input, output, rule: .hoistPatternLet, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testHoistCatchLet() { + let input = "do {} catch Foo.foo(bar: let bar) {}" + let output = "do {} catch let Foo.foo(bar: bar) {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNoNestedHoistLetWithSpecifiedArgs() { + let input = "if case (.foo(let a, b), .bar(let c, d)) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testNoHoistClosureVariables() { + let input = "foo({ let bar = 5 })" + testFormatting(for: input, rule: .hoistPatternLet, exclude: [.trailingClosures]) + } + + // TODO: this should actually hoist the let, but that's tricky to implement without + // breaking the `testNoOverHoistSwitchCaseWithNestedParens` case + func testHoistSwitchCaseWithNestedParens() { + let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), Foo.bar): break\n}" + testFormatting(for: input, rule: .hoistPatternLet, + exclude: [.blankLineAfterImports]) + } + + // TODO: this could actually hoist the let by one level, but that's tricky to implement + func testNoOverHoistSwitchCaseWithNestedParens() { + let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), bar): break\n}" + testFormatting(for: input, rule: .hoistPatternLet, + exclude: [.blankLineAfterImports]) + } + + func testNoHoistLetWithEmptArg() { + let input = "if .foo(let _) = bar {}" + testFormatting(for: input, rule: .hoistPatternLet, + exclude: [.redundantLet, .redundantPattern]) + } + + func testHoistLetWithNoSpaceAfterCase() { + let input = "switch x { case.some(let y): return y }" + let output = "switch x { case let .some(y): return y }" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistWrappedGuardCaseLet() { + let input = """ + guard case Foo + .bar(let baz) + else { + return + } + """ + let output = """ + guard case let Foo + .bar(baz) + else { + return + } + """ + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNoHoistCaseLetContainingGenerics() { + // Hoisting in this case causes a compilation error as-of Swift 5.3 + // See: https://github.com/nicklockwood/SwiftFormat/issues/768 + let input = "if case .some(Optional.some(let foo)) = bar else {}" + testFormatting(for: input, rule: .hoistPatternLet, exclude: [.typeSugar]) + } + + // hoist = false + + func testUnhoistCaseLet() { + let input = "if case let .foo(bar, baz) = quux {}" + let output = "if case .foo(let bar, let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseLetDictionaryTuple() { + let input = """ + switch (a, b) { + case let (c as [String: Any], d as [String: Any]): + break + } + """ + let output = """ + switch (a, b) { + case (let c as [String: Any], let d as [String: Any]): + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistLabelledCaseLet() { + let input = "if case let .foo(bar: bar, baz: baz) = quux {}" + let output = "if case .foo(bar: let bar, baz: let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseVar() { + let input = "if case var .foo(bar, baz) = quux {}" + let output = "if case .foo(var bar, var baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistGuardCaseLetFollowedByFunction() { + let input = """ + guard case let foo as Foo = bar else { return } + foo.bar(foo: bar) + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options, + exclude: [.wrapConditionalBodies]) + } + + func testNoUnhoistSwitchCaseLetFollowedByWhere() { + let input = """ + switch foo { + case let bar? where bar >= baz(quux): + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistSwitchCaseLetFollowedByAs() { + let input = """ + switch foo { + case let bar as (String, String): + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testUnhoistSingleCaseLet() { + let input = "if case let .foo(bar) = quux {}" + let output = "if case .foo(let bar) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistIfArgIsEnumCaseLiteral() { + let input = "if case let .foo(.bar, baz) = quux {}" + let output = "if case .foo(.bar, let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistIfArgIsEnumCaseLiteralInParens() { + let input = "switch foo {\ncase let (.bar(baz)):\n}" + let output = "switch foo {\ncase (.bar(let baz)):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.redundantParens]) + } + + func testUnhoistIfArgIsNamespacedEnumCaseLiteral() { + let input = "switch foo {\ncase let Foo.bar(baz):\n}" + let output = "switch foo {\ncase Foo.bar(let baz):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { + let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" + let output = "switch foo {\ncase (Foo.bar(let baz)):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.redundantParens]) + } + + func testUnhoistIfArgIsUnderscore() { + let input = "if case let .foo(_, baz) = quux {}" + let output = "if case .foo(_, let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testNestedUnhoistLet() { + let input = "if case let (.foo(a, b), .bar(c, d)) = quux {}" + let output = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCommaSeparatedSwitchCaseLets() { + let input = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" + let output = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testUnhoistCommaSeparatedSwitchCaseLets2() { + let input = "switch foo {\ncase let Foo.foo(bar), let Foo.bar(bar):\n}" + let output = "switch foo {\ncase Foo.foo(let bar), Foo.bar(let bar):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testUnhoistCatchLet() { + let input = "do {} catch let Foo.foo(bar: bar) {}" + let output = "do {} catch Foo.foo(bar: let bar) {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistTupleLet() { + let input = "let (bar, baz) = quux()" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistIfLetTuple() { + let input = "if let x = y, let (_, a) = z {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistIfCaseFollowedByLetTuple() { + let input = "if case .foo = bar, let (foo, bar) = baz {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { + let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options, + exclude: [.redundantParens]) + } + + func testNoDeleteCommentWhenUnhoistingWrappedLet() { + let input = """ + switch foo { + case /* next */ let .bar(bar): + } + """ + + let output = """ + switch foo { + case /* next */ .bar(let bar): + } + """ + + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, + options: options, exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testMultilineGuardLet() { + let input = """ + guard + let first = response?.first, + let last = response?.last, + case .foo(token: let foo, provider: let bar) = first, + case .foo(token: let baz, provider: let quux) = last + else { + return + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseWithNilValue() { + let input = """ + switch (foo, bar) { + case let (.some(unwrappedFoo), nil): + print(unwrappedFoo) + default: + break + } + """ + let output = """ + switch (foo, bar) { + case (.some(let unwrappedFoo), nil): + print(unwrappedFoo) + default: + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseWithBoolValue() { + let input = """ + switch (foo, bar) { + case let (.some(unwrappedFoo), false): + print(unwrappedFoo) + default: + break + } + """ + let output = """ + switch (foo, bar) { + case (.some(let unwrappedFoo), false): + print(unwrappedFoo) + default: + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } +} diff --git a/Tests/Rules/HoistTryTests.swift b/Tests/Rules/HoistTryTests.swift new file mode 100644 index 00000000..57dbfee6 --- /dev/null +++ b/Tests/Rules/HoistTryTests.swift @@ -0,0 +1,440 @@ +// +// HoistTryTests.swift +// SwiftFormatTests +// +// Created by Facundo Menzella on 2/25/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HoistTryTests: XCTestCase { + func testHoistTry() { + let input = "greet(try name(), try surname())" + let output = "try greet(name(), surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryWithOptionalTry() { + let input = "greet(try name(), try? surname())" + let output = "try greet(name(), try? surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation() { + let input = "\"\\(replace(regex: try something()))\"" + let output = "try \"\\(replace(regex: something()))\"" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation2() { + let input = """ + "Hello \\(try await someValue())" + """ + let output = """ + try "Hello \\(await someValue())" + """ + testFormatting(for: input, output, rule: .hoistTry, + options: FormatOptions(swiftVersion: "5.5"), + exclude: [.hoistAwait]) + } + + func testHoistTryInsideStringInterpolation3() { + let input = """ + let text = "\"" + abc + \\(try bar()) + xyz + "\"" + """ + let output = """ + let text = try "\"" + abc + \\(bar()) + xyz + "\"" + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation4() { + let input = """ + let str = "&enrolments[\\(index)][userid]=\\(try Foo.tryMe())" + """ + let output = """ + let str = try "&enrolments[\\(index)][userid]=\\(Foo.tryMe())" + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation5() { + let input = """ + return str + + "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + + "&enrolments[\\(index)][userid]=\\(try user.requireMoodleID())" + """ + let output = """ + return try str + + "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + + "&enrolments[\\(index)][userid]=\\(user.requireMoodleID())" + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation6() { + let input = #""" + """ + let \(object.varName) = + \(tripleQuote) + \(try encode(object.object)) + \(tripleQuote) + """ + """# + let output = #""" + try """ + let \(object.varName) = + \(tripleQuote) + \(encode(object.object)) + \(tripleQuote) + """ + """# + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideArgument() { + let input = """ + array.append(contentsOf: try await asyncFunction(param1: param1)) + """ + let output = """ + try array.append(contentsOf: await asyncFunction(param1: param1)) + """ + testFormatting(for: input, output, rule: .hoistTry, exclude: [.hoistAwait]) + } + + func testNoHoistTryInsideXCTAssert() { + let input = "XCTAssertFalse(try foo())" + testFormatting(for: input, rule: .hoistTry) + } + + func testNoMergeTrysInsideXCTAssert() { + let input = "XCTAssertEqual(try foo(), try bar())" + testFormatting(for: input, rule: .hoistTry) + } + + func testNoHoistTryInsideDo() { + let input = "do { rg.box.seal(.fulfilled(try body(error))) }" + let output = "do { try rg.box.seal(.fulfilled(body(error))) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryInsideDoThrows() { + let input = "do throws(Foo) { rg.box.seal(.fulfilled(try body(error))) }" + let output = "do throws(Foo) { try rg.box.seal(.fulfilled(body(error))) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryInsideMultilineDo() { + let input = """ + do { + rg.box.seal(.fulfilled(try body(error))) + } + """ + let output = """ + do { + try rg.box.seal(.fulfilled(body(error))) + } + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistedTryPlacedBeforeAwait() { + let input = "let foo = await bar(contentsOf: try baz())" + let output = "let foo = try await bar(contentsOf: baz())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInExpressionWithNoSpaces() { + let input = "let foo=bar(contentsOf:try baz())" + let output = "let foo=try bar(contentsOf:baz())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.spaceAroundOperators]) + } + + func testHoistTryInExpressionWithExcessSpaces() { + let input = "let foo = bar ( contentsOf: try baz() )" + let output = "let foo = try bar ( contentsOf: baz() )" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.spaceAroundParens, .spaceInsideParens]) + } + + func testHoistTryWithReturn() { + let input = "return .enumCase(try await service.greet())" + let output = "return try .enumCase(await service.greet())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.hoistAwait]) + } + + func testHoistDeeplyNestedTrys() { + let input = "let foo = (bar: (5, (try quux(), 6)), baz: (7, quux: try quux()))" + let output = "let foo = try (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testTryNotHoistedOutOfClosure() { + let input = "let foo = { (try bar(), 5) }" + let output = "let foo = { try (bar(), 5) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testTryNotHoistedOutOfClosureWithArguments() { + let input = "let foo = { bar in (try baz(bar), 5) }" + let output = "let foo = { bar in try (baz(bar), 5) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testTryNotHoistedOutOfForCondition() { + let input = "for foo in bar(try baz()) {}" + let output = "for foo in try bar(baz()) {}" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryWithInitAssignment() { + let input = "let variable = String(try await asyncFunction())" + let output = "let variable = try String(await asyncFunction())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.hoistAwait]) + } + + func testHoistTryWithAssignment() { + let input = "let variable = (try await asyncFunction())" + let output = "let variable = try (await asyncFunction())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.hoistAwait]) + } + + func testHoistTryOnlyOne() { + let input = "greet(name, try surname())" + let output = "try greet(name, surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryRedundantTry() { + let input = "try greet(try name(), try surname())" + let output = "try greet(name(), surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryWithAwaitOnDifferentStatement() { + let input = """ + let asyncVariable = try await performSomething() + return Foo(param1: try param1()) + """ + let output = """ + let asyncVariable = try await performSomething() + return try Foo(param1: param1()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryDoubleParens() { + let input = """ + array.append((value: try compute())) + """ + let output = """ + try array.append((value: compute())) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryDoesNothing() { + let input = "try greet(name, surname)" + testFormatting(for: input, rule: .hoistTry) + } + + func testHoistOptionalTryDoesNothing() { + let input = "try? greet(name, surname)" + testFormatting(for: input, rule: .hoistTry) + } + + func testHoistedTryOnLineBeginningWithInfixDot() { + let input = """ + let foo = bar() + .baz(try quux()) + """ + let output = """ + let foo = try bar() + .baz(quux()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistedTryOnLineBeginningWithInfixPlus() { + let input = """ + let foo = bar() + + baz(try quux()) + """ + let output = """ + let foo = try bar() + + baz(quux()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistedTryOnLineBeginningWithPrefixOperator() { + let input = """ + foo() + !bar(try quux()) + """ + let output = """ + foo() + try !bar(quux()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryIntoPreviousLineEndingWithPostfixOperator() { + let input = """ + let foo = bar! + (try baz(), quux()).foo() + """ + let output = """ + let foo = bar! + try (baz(), quux()).foo() + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryInCapturingFunction() { + let input = "foo(try bar)" + testFormatting(for: input, rule: .hoistTry, + options: FormatOptions(throwCapturing: ["foo"])) + } + + func testNoHoistSecondArgumentTryInCapturingFunction() { + let input = "foo(bar, try baz)" + testFormatting(for: input, rule: .hoistTry, + options: FormatOptions(throwCapturing: ["foo"])) + } + + func testNoHoistFailToTerminate() { + let input = """ + return ManyInitExample( + a: try Example(string: try throwingExample()), + b: try throwingExample(), + c: try throwingExample(), + d: try throwingExample(), + e: try throwingExample(), + f: try throwingExample(), + g: try throwingExample(), + h: try throwingExample(), + i: try throwingExample() + ) + """ + let output = """ + return try ManyInitExample( + a: Example(string: throwingExample()), + b: throwingExample(), + c: throwingExample(), + d: throwingExample(), + e: throwingExample(), + f: throwingExample(), + g: throwingExample(), + h: throwingExample(), + i: throwingExample() + ) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideOptionalFunction() { + let input = "foo?(try bar())" + let output = "try foo?(bar())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryAfterOptionalTry() { + let input = "let foo = try? bar(try baz())" + testFormatting(for: input, rule: .hoistTry) + } + + func testHoistTryInsideOptionalSubscript() { + let input = "foo?[try bar()]" + let output = "try foo?[bar()]" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterGenericType() { + let input = "let foo = Tree.Foo(bar: try baz())" + let output = "let foo = try Tree.Foo(bar: baz())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterArrayLiteral() { + let input = "if [.first, .second].contains(try foo()) {}" + let output = "if try [.first, .second].contains(foo()) {}" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterSubscript() { + let input = "if foo[5].bar(try baz()) {}" + let output = "if try foo[5].bar(baz()) {}" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideGenericInit() { + let input = """ + return Target( + file: try parseFile(path: $0) + ) + """ + let output = """ + return try Target( + file: parseFile(path: $0) + ) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideArrayClosure() { + let input = "foo[bar](try parseFile(path: $0))" + let output = "try foo[bar](parseFile(path: $0))" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterString() { + let input = """ + let json = "{}" + + someFunction(try parse(json), "someKey") + """ + let output = """ + let json = "{}" + + try someFunction(parse(json), "someKey") + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterMultilineString() { + let input = #""" + let json = """ + { + "foo": "bar" + } + """ + + someFunction(try parse(json), "someKey") + """# + let output = #""" + let json = """ + { + "foo": "bar" + } + """ + + try someFunction(parse(json), "someKey") + """# + testFormatting(for: input, output, rule: .hoistTry) + } +} diff --git a/Tests/RulesTests+Indentation.swift b/Tests/Rules/IndentTests.swift similarity index 77% rename from Tests/RulesTests+Indentation.swift rename to Tests/Rules/IndentTests.swift index f25f94b3..420fb615 100644 --- a/Tests/RulesTests+Indentation.swift +++ b/Tests/Rules/IndentTests.swift @@ -1,27 +1,25 @@ // -// RulesTests+Indentation.swift +// IndentTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class IndentTests: RulesTests { - // MARK: - indent - +class IndentTests: XCTestCase { func testReduceIndentAtStartOfFile() { let input = " foo()" let output = "foo()" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testReduceIndentAtEndOfFile() { let input = "foo()\n bar()" let output = "foo()\nbar()" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent parens @@ -29,55 +27,55 @@ class IndentTests: RulesTests { func testSimpleScope() { let input = "foo(\nbar\n)" let output = "foo(\n bar\n)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedScope() { let input = "foo(\nbar {\n}\n)" let output = "foo(\n bar {\n }\n)" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.emptyBraces]) } func testNestedScopeOnSameLine() { let input = "foo(bar(\nbaz\n))" let output = "foo(bar(\n baz\n))" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedScopeOnSameLine2() { let input = "foo(bar(in:\nbaz))" let output = "foo(bar(in:\n baz))" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentNestedArrayLiteral() { let input = "foo(bar: [\n.baz,\n])" let output = "foo(bar: [\n .baz,\n])" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testClosingScopeAfterContent() { let input = "foo(\nbar\n)" let output = "foo(\n bar\n)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testClosingNestedScopeAfterContent() { let input = "foo(bar(\nbaz\n))" let output = "foo(bar(\n baz\n))" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedFunctionArguments() { let input = "foo(\nbar,\nbaz\n)" let output = "foo(\n bar,\n baz\n)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testFunctionArgumentsWrappedAfterFirst() { let input = "func foo(bar: Int,\nbaz: Int)" let output = "func foo(bar: Int,\n baz: Int)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentPreservedForNestedWrappedParameters() { @@ -88,7 +86,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentPreservedForNestedWrappedParameters2() { @@ -99,7 +97,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm)) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentPreservedForNestedWrappedParameters3() { @@ -112,7 +110,7 @@ class IndentTests: RulesTests { ) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentTrailingClosureInParensContainingUnwrappedArguments() { @@ -121,7 +119,7 @@ class IndentTests: RulesTests { quux(foo, bar) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentTrailingClosureInParensContainingWrappedArguments() { @@ -131,7 +129,7 @@ class IndentTests: RulesTests { bar) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentTrailingClosureInParensContainingWrappedArguments2() { @@ -143,7 +141,7 @@ class IndentTests: RulesTests { ) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentImbalancedNestedClosingParens() { @@ -153,7 +151,7 @@ class IndentTests: RulesTests { baz: quux )) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedCallAfterClosingParen() { @@ -166,7 +164,7 @@ class IndentTests: RulesTests { View() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedCallAfterClosingParen2() { @@ -185,14 +183,14 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent modifiers func testNoIndentWrappedModifiersForProtocol() { let input = "@objc\nprivate\nprotocol Foo {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent braces @@ -200,37 +198,37 @@ class IndentTests: RulesTests { func testElseClauseIndenting() { let input = "if x {\nbar\n} else {\nbaz\n}" let output = "if x {\n bar\n} else {\n baz\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNoIndentBlankLines() { let input = "{\n\n// foo\n}" let output = "{\n\n // foo\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["blankLinesAtStartOfScope"]) + testFormatting(for: input, output, rule: .indent, exclude: [.blankLinesAtStartOfScope]) } func testNestedBraces() { let input = "({\n// foo\n}, {\n// bar\n})" let output = "({\n // foo\n}, {\n // bar\n})" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testBraceIndentAfterComment() { let input = "if foo { // comment\nbar\n}" let output = "if foo { // comment\n bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testBraceIndentAfterClosingScope() { let input = "foo(bar(baz), {\nquux\nbleem\n})" let output = "foo(bar(baz), {\n quux\n bleem\n})" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .indent, exclude: [.trailingClosures]) } func testBraceIndentAfterLineWithParens() { let input = "({\nfoo()\nbar\n})" let output = "({\n foo()\n bar\n})" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testUnindentClosingParenAroundBraces() { @@ -244,7 +242,7 @@ class IndentTests: RulesTests { self.bar() }) """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentDoubleParenthesizedClosures() { @@ -255,7 +253,7 @@ class IndentTests: RulesTests { self.baz() })) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentUnbalancedBraces() { @@ -265,7 +263,7 @@ class IndentTests: RulesTests { .baz($0) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentClosureArguments() { @@ -285,7 +283,7 @@ class IndentTests: RulesTests { print(baz) }) """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureArguments2() { @@ -298,7 +296,7 @@ class IndentTests: RulesTests { } ) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapArguments"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapArguments]) } func testIndentWrappedClosureParameters() { @@ -310,7 +308,7 @@ class IndentTests: RulesTests { print(bar + baz) } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedClosureCaptureList() { @@ -323,7 +321,7 @@ class IndentTests: RulesTests { _ = topView } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // TODO: add `unwrap` rule to improve this case @@ -346,7 +344,7 @@ class IndentTests: RulesTests { return x + y } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: [.propertyType]) } func testIndentWrappedClosureCaptureListWithUnwrappedParameters() { @@ -359,7 +357,7 @@ class IndentTests: RulesTests { _ = topView } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentTrailingClosureArgumentsAfterFunction() { @@ -373,7 +371,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentAllmanTrailingClosureArguments() { @@ -389,7 +387,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentAllmanTrailingClosureArguments2() { @@ -400,7 +398,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentAllmanTrailingClosureArgumentsAfterFunction() { @@ -422,8 +420,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["redundantReturn"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.redundantReturn]) } func testNoDoubleIndentClosureArguments() { @@ -433,7 +431,7 @@ class IndentTests: RulesTests { { quux } )) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLineAfterIndentedWrappedClosure() { @@ -447,8 +445,8 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["braces", "wrapMultilineStatementBraces", "redundantProperty"]) + testFormatting(for: input, rule: .indent, + exclude: [.braces, .wrapMultilineStatementBraces, .redundantProperty]) } func testIndentLineAfterIndentedInlineClosure() { @@ -460,7 +458,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .indent, exclude: [.redundantProperty]) } func testIndentLineAfterNonIndentedClosure() { @@ -473,7 +471,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .indent, exclude: [.redundantProperty]) } func testIndentMultilineStatementDoesntFailToTerminate() { @@ -484,7 +482,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent switch/case @@ -492,43 +490,43 @@ class IndentTests: RulesTests { func testSwitchCaseIndenting() { let input = "switch x {\ncase foo:\nbreak\ncase bar:\nbreak\ndefault:\nbreak\n}" let output = "switch x {\ncase foo:\n break\ncase bar:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testSwitchWrappedCaseIndenting() { let input = "switch x {\ncase foo,\nbar,\n baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase foo,\n bar,\n baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchWrappedEnumCaseIndenting() { let input = "switch x {\ncase .foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase .foo,\n .bar,\n .baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchWrappedEnumCaseIndentingVariant2() { let input = "switch x {\ncase\n.foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase\n .foo,\n .bar,\n .baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchWrappedEnumCaseIsIndenting() { let input = "switch x {\ncase is Foo.Type,\n is Bar.Type:\n break\ndefault:\n break\n}" let output = "switch x {\ncase is Foo.Type,\n is Bar.Type:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchCaseIsDictionaryIndenting() { let input = "switch x {\ncase foo is [Key: Value]:\nfallthrough\ndefault:\nbreak\n}" let output = "switch x {\ncase foo is [Key: Value]:\n fallthrough\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testEnumCaseIndenting() { let input = "enum Foo {\ncase Bar\ncase Baz\n}" let output = "enum Foo {\n case Bar\n case Baz\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testEnumCaseIndentingCommas() { @@ -539,29 +537,29 @@ class IndentTests: RulesTests { Baz } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["wrapEnumCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.wrapEnumCases]) } func testGenericEnumCaseIndenting() { let input = "enum Foo {\ncase Bar\ncase Baz\n}" let output = "enum Foo {\n case Bar\n case Baz\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentSwitchAfterRangeCase() { let input = "switch x {\ncase 0 ..< 2:\n switch y {\n default:\n break\n }\ndefault:\n break\n}" - testFormatting(for: input, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentEnumDeclarationInsideSwitchCase() { let input = "switch x {\ncase y:\nenum Foo {\ncase z\n}\nbar()\ndefault: break\n}" let output = "switch x {\ncase y:\n enum Foo {\n case z\n }\n bar()\ndefault: break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentEnumCaseBodyAfterWhereClause() { let input = "switch foo {\ncase _ where baz < quux:\n print(1)\n print(2)\ndefault:\n break\n}" - testFormatting(for: input, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentSwitchCaseCommentsCorrectly() { @@ -587,7 +585,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentMultilineSwitchCaseCommentsCorrectly() { @@ -619,7 +617,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentEnumCaseComment() { @@ -635,26 +633,26 @@ class IndentTests: RulesTests { case bar } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentMultipleSingleLineSwitchCaseCommentsCorrectly() { let input = "switch x {\n// comment 1\n// comment 2\ncase y:\n// comment\nbreak\n}" let output = "switch x {\n// comment 1\n// comment 2\ncase y:\n // comment\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentIfCase() { let input = "{\nif case let .foo(msg) = error {}\n}" let output = "{\n if case let .foo(msg) = error {}\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentGuardCase() { let input = "{\nguard case .Foo = error else {}\n}" let output = "{\n guard case .Foo = error else {}\n}" - testFormatting(for: input, output, rule: FormatRules.indent, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, output, rule: .indent, + exclude: [.wrapConditionalBodies]) } func testIndentIfElse() { @@ -663,7 +661,7 @@ class IndentTests: RulesTests { } else if let bar = baz, let baz = quux {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNestedIndentIfElse() { @@ -676,7 +674,7 @@ class IndentTests: RulesTests { let baz = quux {} } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIfCaseLet() { @@ -684,7 +682,7 @@ class IndentTests: RulesTests { if case let foo = foo, let bar = bar {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultipleIfLet() { @@ -692,7 +690,7 @@ class IndentTests: RulesTests { if let foo = foo, let bar = bar, let baz = baz {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedConditionAlignsWithParen() { @@ -706,7 +704,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedConditionAlignsWithParen2() { @@ -720,7 +718,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentUnknownDefault() { @@ -740,7 +738,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentUnknownDefaultOnOwnLine() { @@ -762,7 +760,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentUnknownCase() { @@ -782,7 +780,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentUnknownCaseOnOwnLine() { @@ -804,7 +802,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedClassDeclaration() { @@ -814,8 +812,8 @@ class IndentTests: RulesTests { init() {} } """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, + exclude: [.wrapMultilineStatementBraces]) } func testWrappedClassDeclarationLikeXcode() { @@ -832,7 +830,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testWrappedClassDeclarationWithBracesOnSameLineLikeXcode() { @@ -841,7 +839,7 @@ class IndentTests: RulesTests { Baz {} """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedClassDeclarationWithBraceOnNextLineLikeXcode() { @@ -853,7 +851,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedClassWhereDeclarationLikeXcode() { @@ -870,7 +868,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentSwitchCaseDo() { @@ -881,7 +879,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indentCase = true @@ -890,27 +888,27 @@ class IndentTests: RulesTests { let input = "switch x {\ncase foo:\nbreak\ncase bar:\nbreak\ndefault:\nbreak\n}" let output = "switch x {\n case foo:\n break\n case bar:\n break\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchWrappedEnumCaseWithIndentCaseTrue() { let input = "switch x {\ncase .foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\n case .foo,\n .bar,\n .baz:\n break\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } func testIndentMultilineSwitchCaseCommentsWithIndentCaseTrue() { let input = "switch x {\n/*\n * comment\n */\ncase y:\nbreak\n/*\n * comment\n */\ndefault:\nbreak\n}" let output = "switch x {\n /*\n * comment\n */\n case y:\n break\n /*\n * comment\n */\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testNoMangleLabelWhenIndentCaseTrue() { let input = "foo: while true {\n break foo\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentMultipleSingleLineSwitchCaseCommentsWithCommentsIgnoredCorrectlyWhenIndentCaseTrue() { @@ -923,7 +921,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, indentComments: false) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentUnknownDefaultCorrectlyWhenIndentCaseTrue() { @@ -944,7 +942,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentUnknownCaseCorrectlyWhenIndentCaseTrue() { @@ -965,7 +963,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentSwitchCaseDoWhenIndentCaseTrue() { @@ -977,7 +975,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent wrapped lines @@ -985,66 +983,66 @@ class IndentTests: RulesTests { func testWrappedLineAfterOperator() { let input = "if x {\nlet y = foo +\nbar\n}" let output = "if x {\n let y = foo +\n bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterComma() { let input = "let a = b,\nb = c" let output = "let a = b,\n b = c" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedBeforeComma() { let input = "let a = b\n, b = c" let output = "let a = b\n , b = c" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["leadingDelimiters"]) + testFormatting(for: input, output, rule: .indent, exclude: [.leadingDelimiters]) } func testWrappedLineAfterCommaInsideArray() { let input = "[\nfoo,\nbar,\n]" let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeCommaInsideArray() { let input = "[\nfoo\n, bar,\n]" let output = "[\n foo\n , bar,\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, - exclude: ["leadingDelimiters"]) + testFormatting(for: input, output, rule: .indent, options: options, + exclude: [.leadingDelimiters]) } func testWrappedLineAfterCommaInsideInlineArray() { let input = "[foo,\nbar]" let output = "[foo,\n bar]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testWrappedLineBeforeCommaInsideInlineArray() { let input = "[foo\n, bar]" let output = "[foo\n , bar]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, - exclude: ["leadingDelimiters"]) + testFormatting(for: input, output, rule: .indent, options: options, + exclude: [.leadingDelimiters]) } func testWrappedLineAfterColonInFunction() { let input = "func foo(bar:\nbaz)" let output = "func foo(bar:\n baz)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNoDoubleIndentOfWrapAfterAsAfterOpenScope() { let input = "(foo as\nBar)" let output = "(foo as\n Bar)" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testNoDoubleIndentOfWrapBeforeAsAfterOpenScope() { let input = "(foo\nas Bar)" let output = "(foo\n as Bar)" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testDoubleIndentWhenScopesSeparatedByWrap() { @@ -1060,93 +1058,93 @@ class IndentTests: RulesTests { baz }) """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testNoDoubleIndentWhenScopesSeparatedByWrap() { let input = "(foo\nas Bar {\nbaz\n}\n)" let output = "(foo\n as Bar {\n baz\n }\n)" - testFormatting(for: input, output, rule: FormatRules.indent, - exclude: ["wrapArguments", "redundantParens"]) + testFormatting(for: input, output, rule: .indent, + exclude: [.wrapArguments, .redundantParens]) } func testNoPermanentReductionInScopeAfterWrap() { let input = "{ foo\nas Bar\nlet baz = 5\n}" let output = "{ foo\n as Bar\n let baz = 5\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeOperator() { let input = "if x {\nlet y = foo\n+ bar\n}" let output = "if x {\n let y = foo\n + bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeIsOperator() { let input = "if x {\nlet y = foo\nis Bar\n}" let output = "if x {\n let y = foo\n is Bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterForKeyword() { let input = "for\ni in range {}" let output = "for\n i in range {}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterInKeyword() { let input = "for i in\nrange {}" let output = "for i in\n range {}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterDot() { let input = "let foo = bar.\nbaz" let output = "let foo = bar.\n baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeDot() { let input = "let foo = bar\n.baz" let output = "let foo = bar\n .baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeWhere() { let input = "let foo = bar\nwhere foo == baz" let output = "let foo = bar\n where foo == baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterWhere() { let input = "let foo = bar where\nfoo == baz" let output = "let foo = bar where\n foo == baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeGuardElse() { let input = "guard let foo = bar\nelse { return }" - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, + exclude: [.wrapConditionalBodies]) } func testWrappedLineAfterGuardElse() { // Don't indent because this case is handled by braces rule let input = "guard let foo = bar else\n{ return }" - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["elseOnSameLine", "wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, + exclude: [.elseOnSameLine, .wrapConditionalBodies]) } func testWrappedLineAfterComment() { let input = "foo = bar && // comment\nbaz" let output = "foo = bar && // comment\n baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineInClosure() { let input = "forEach { item in\nprint(item)\n}" let output = "forEach { item in\n print(item)\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedGuardInClosure() { @@ -1156,71 +1154,71 @@ class IndentTests: RulesTests { let bar = bar else { break } } """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["wrapMultilineStatementBraces", "wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, + exclude: [.wrapMultilineStatementBraces, .wrapConditionalBodies]) } func testConsecutiveWraps() { let input = "let a = b +\nc +\nd" let output = "let a = b +\n c +\n d" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrapReset() { let input = "let a = b +\nc +\nd\nlet a = b +\nc +\nd" let output = "let a = b +\n c +\n d\nlet a = b +\n c +\n d" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentElseAfterComment() { let input = "if x {}\n// comment\nelse {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLinesWithComments() { let input = "let foo = bar ||\n // baz||\nquux" let output = "let foo = bar ||\n // baz||\n quux" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNoIndentAfterAssignOperatorToVariable() { let input = "let greaterThan = >\nlet lessThan = <" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentAfterDefaultAsIdentifier() { let input = "let foo = FileManager.default\n/// Comment\nlet bar = 0" - testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: [.propertyType]) } func testIndentClosureStartingOnIndentedLine() { let input = "foo\n.bar {\nbaz()\n}" let output = "foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInVar() { let input = "var foo = foo\n.bar {\nbaz()\n}" let output = "var foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInLet() { let input = "let foo = foo\n.bar {\nbaz()\n}" let output = "let foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInTypedVar() { let input = "var: Int foo = foo\n.bar {\nbaz()\n}" let output = "var: Int foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInTypedLet() { let input = "let: Int foo = foo\n.bar {\nbaz()\n}" let output = "let: Int foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedWrappedIfIndents() { @@ -1234,25 +1232,25 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["andOperator", "wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.andOperator, .wrapMultilineStatementBraces]) } func testWrappedEnumThatLooksLikeIf() { let input = "foo &&\n bar.if {\nfoo()\n}" let output = "foo &&\n bar.if {\n foo()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndents() { let input = "foo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "foo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterIfCondition() { let input = "if foo {\nbar()\n.baz()\n}\n\nfoo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "if foo {\n bar()\n .baz()\n}\n\nfoo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterIfCondition2() { @@ -1282,19 +1280,19 @@ class IndentTests: RulesTests { baz() } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterVarDeclaration() { let input = "var foo: Int\nfoo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "var foo: Int\nfoo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterLetDeclaration() { let input = "let foo: Int\nfoo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "let foo: Int\nfoo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsSeparatedByComments() { @@ -1312,8 +1310,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["blankLinesBetweenScopes"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.blankLinesBetweenScopes]) } func testChainedFunctionIndents() { @@ -1323,7 +1321,7 @@ class IndentTests: RulesTests { }) .buttonStyle(bar()) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testChainedFunctionIndentWithXcodeIndentation() { @@ -1334,7 +1332,7 @@ class IndentTests: RulesTests { .buttonStyle(bar()) """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedClosureIndentAfterAssignment() { @@ -1344,7 +1342,7 @@ class IndentTests: RulesTests { print("baz") } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testChainedFunctionsInPropertySetter() { @@ -1358,7 +1356,7 @@ class IndentTests: RulesTests { .baz()! .quux """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsInPropertySetterOnNewLine() { @@ -1374,89 +1372,89 @@ class IndentTests: RulesTests { .baz()! .quux """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsInsideIf() { let input = "if foo {\nreturn bar()\n.baz()\n}" let output = "if foo {\n return bar()\n .baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsInsideForLoop() { let input = "for x in y {\nfoo\n.bar {\nbaz()\n}\n.quux()\n}" let output = "for x in y {\n foo\n .bar {\n baz()\n }\n .quux()\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsAfterAnIfStatement() { let input = "if foo {}\nbar\n.baz {\n}\n.quux()" let output = "if foo {}\nbar\n .baz {\n }\n .quux()" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.emptyBraces]) } func testIndentInsideWrappedIfStatementWithClosureCondition() { let input = "if foo({ 1 }) ||\nbar {\nbaz()\n}" let output = "if foo({ 1 }) ||\n bar {\n baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentInsideWrappedClassDefinition() { let input = "class Foo\n: Bar {\nbaz()\n}" let output = "class Foo\n : Bar {\n baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent, - exclude: ["leadingDelimiters", "wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, + exclude: [.leadingDelimiters, .wrapMultilineStatementBraces]) } func testIndentInsideWrappedProtocolDefinition() { let input = "protocol Foo\n: Bar, Baz {\nbaz()\n}" let output = "protocol Foo\n : Bar, Baz {\n baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent, - exclude: ["leadingDelimiters", "wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, + exclude: [.leadingDelimiters, .wrapMultilineStatementBraces]) } func testIndentInsideWrappedVarStatement() { let input = "var Foo:\nBar {\nreturn 5\n}" let output = "var Foo:\n Bar {\n return 5\n}" - testFormatting(for: input, output, rule: FormatRules.indent, - exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, + exclude: [.wrapMultilineStatementBraces]) } func testNoIndentAfterOperatorDeclaration() { let input = "infix operator ?=\nfunc ?= (lhs _: Int, rhs _: Int) -> Bool {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentAfterChevronOperatorDeclaration() { let input = "infix operator =<<\nfunc =<< (lhs _: T, rhs _: T) -> T {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedStringDictionaryKeysAndValues() { let input = "[\n\"foo\":\n\"bar\",\n\"baz\":\n\"quux\",\n]" let output = "[\n \"foo\":\n \"bar\",\n \"baz\":\n \"quux\",\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentWrappedEnumDictionaryKeysAndValues() { let input = "[\n.foo:\n.bar,\n.baz:\n.quux,\n]" let output = "[\n .foo:\n .bar,\n .baz:\n .quux,\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentWrappedFunctionArgument() { let input = "foobar(baz: a &&\nb)" let output = "foobar(baz: a &&\n b)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentWrappedFunctionClosureArgument() { let input = "foobar(baz: { a &&\nb })" let output = "foobar(baz: { a &&\n b })" - testFormatting(for: input, output, rule: FormatRules.indent, - exclude: ["trailingClosures", "braces"]) + testFormatting(for: input, output, rule: .indent, + exclude: [.trailingClosures, .braces]) } func testIndentWrappedFunctionWithClosureArgument() { @@ -1467,12 +1465,12 @@ class IndentTests: RulesTests { baz: baz) """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentClassDeclarationContainingComment() { let input = "class Foo: Bar,\n // Comment\n Baz {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLineAfterTypeAttribute() { @@ -1480,7 +1478,7 @@ class IndentTests: RulesTests { let f: @convention(swift) (Int) -> Int = { x in x } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLineAfterTypeAttribute2() { @@ -1488,7 +1486,7 @@ class IndentTests: RulesTests { func foo(_: @escaping (Int) -> Int) {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLineAfterNonTypeAttribute() { @@ -1496,7 +1494,7 @@ class IndentTests: RulesTests { @discardableResult func foo() -> Int { 5 } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedClosureAfterSwitch() { @@ -1510,7 +1508,7 @@ class IndentTests: RulesTests { // baz } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testSingleIndentTrailingClosureBody() { @@ -1526,8 +1524,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .balanced) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.wrapConditionalBodies]) } func testSingleIndentTrailingClosureBody2() { @@ -1541,8 +1539,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["wrapConditionalBodies", "wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces]) } func testDoubleIndentTrailingClosureBody() { @@ -1557,8 +1555,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["wrapConditionalBodies", "wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces]) } func testDoubleIndentTrailingClosureBody2() { @@ -1573,8 +1571,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.wrapMultilineStatementBraces]) } func testNoDoubleIndentTrailingClosureBodyIfLineStartsWithClosingBrace() { @@ -1586,7 +1584,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testSingleIndentTrailingClosureBodyThatStartsOnFollowingLine() { @@ -1602,8 +1600,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["braces", "wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.braces, .wrapConditionalBodies]) } func testSingleIndentTrailingClosureBodyOfShortMethod() { @@ -1614,8 +1612,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.wrapConditionalBodies]) } func testNoDoubleIndentInInsideClosure() { @@ -1625,8 +1623,8 @@ class IndentTests: RulesTests { baz }) """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .indent, + exclude: [.trailingClosures]) } func testNoDoubleIndentInInsideClosure2() { @@ -1637,7 +1635,7 @@ class IndentTests: RulesTests { print("and a trailing closure") } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure3() { @@ -1647,7 +1645,7 @@ class IndentTests: RulesTests { self?.bar() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure4() { @@ -1657,7 +1655,7 @@ class IndentTests: RulesTests { self?.bar(baz) } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure5() { @@ -1668,7 +1666,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure6() { @@ -1679,7 +1677,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentForInInsideFunction() { @@ -1690,7 +1688,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoUnindentTrailingClosure() { @@ -1717,8 +1715,8 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["wrapArguments", "wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, + exclude: [.wrapArguments, .wrapMultilineStatementBraces]) } func testIndentChainedPropertiesAfterFunctionCall() { @@ -1729,7 +1727,7 @@ class IndentTests: RulesTests { .bar .baz """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: [.propertyType]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation() { @@ -1741,7 +1739,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentChainedPropertiesAfterFunctionCall2() { @@ -1752,8 +1750,8 @@ class IndentTests: RulesTests { .bar .baz """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["trailingClosures", "propertyType"]) + testFormatting(for: input, rule: .indent, + exclude: [.trailingClosures, .propertyType]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation2() { @@ -1765,8 +1763,8 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["trailingClosures", "propertyType"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.trailingClosures, .propertyType]) } func testIndentChainedMethodsAfterTrailingClosure() { @@ -1779,7 +1777,7 @@ class IndentTests: RulesTests { .baz() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedMethodsAfterTrailingClosureWithXcodeIndentation() { @@ -1793,7 +1791,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentChainedMethodsAfterWrappedMethodAfterTrailingClosure() { @@ -1807,7 +1805,7 @@ class IndentTests: RulesTests { .baz() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedMethodsAfterWrappedMethodAfterTrailingClosureWithXcodeIndentation() { @@ -1822,7 +1820,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testChainedFunctionOnNewLineWithXcodeIndentation() { @@ -1837,7 +1835,7 @@ class IndentTests: RulesTests { .quux """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedFunctionOnNewLineWithXcodeIndentation2() { @@ -1851,7 +1849,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testCommentSeparatedChainedFunctionAfterBraceWithXcodeIndentation() { @@ -1865,7 +1863,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testChainedFunctionsInPropertySetterOnNewLineWithXcodeIndentation() { @@ -1882,7 +1880,7 @@ class IndentTests: RulesTests { .quux """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedFunctionsInFunctionWithReturnOnNewLineWithXcodeIndentation() { @@ -1903,7 +1901,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedFunctionInGuardIndentation() { @@ -1914,8 +1912,8 @@ class IndentTests: RulesTests { .baz else { return } """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, + exclude: [.wrapConditionalBodies]) } func testChainedFunctionInGuardWithXcodeIndentation() { @@ -1934,8 +1932,8 @@ class IndentTests: RulesTests { else { return } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, - options: options, exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, output, rule: .indent, + options: options, exclude: [.wrapConditionalBodies]) } func testChainedFunctionInGuardIndentation2() { @@ -1949,8 +1947,8 @@ class IndentTests: RulesTests { yetAnotherBool else { return } """ - testFormatting(for: input, rule: FormatRules.indent, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, + exclude: [.wrapConditionalBodies]) } func testChainedFunctionInGuardWithXcodeIndentation2() { @@ -1976,8 +1974,8 @@ class IndentTests: RulesTests { else { return } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, - options: options, exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, output, rule: .indent, + options: options, exclude: [.wrapConditionalBodies]) } func testWrappedChainedFunctionsWithNestedScopeIndent() { @@ -1994,7 +1992,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testConditionalInitArgumentIndentAfterBrace() { @@ -2019,7 +2017,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testConditionalInitArgumentIndentAfterBraceNoIndent() { @@ -2045,7 +2043,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionIndent() { @@ -2078,7 +2076,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .indent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionIndent2() { @@ -2111,7 +2109,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .indent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionWithIfdefNoIndent() { @@ -2144,7 +2142,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionWithIfdefOutdent() { @@ -2177,7 +2175,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedOrOperatorsInFunctionWithReturnOnNewLine() { @@ -2197,7 +2195,7 @@ class IndentTests: RulesTests { lhs == rhs } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedSingleLineClosureOnNewLine() { @@ -2207,7 +2205,7 @@ class IndentTests: RulesTests { { print("foo") } } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: [.braces]) } func testWrappedMultilineClosureOnNewLine() { @@ -2219,7 +2217,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: [.braces]) } func testWrappedMultilineClosureOnNewLineWithAllmanBraces() { @@ -2232,8 +2230,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["braces"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.braces]) } func testIndentChainedPropertiesAfterMultilineStringXcode() { @@ -2245,7 +2243,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedExpressionIndentAfterTryInClosure() { @@ -2256,7 +2254,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentTryAfterCommaInCollection() { @@ -2268,7 +2266,7 @@ class IndentTests: RulesTests { viewModel.snake, ] """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["hoistTry"]) + testFormatting(for: input, rule: .indent, exclude: [.hoistTry]) } func testIndentChainedFunctionAfterTryInParens() { @@ -2281,7 +2279,7 @@ class IndentTests: RulesTests { ) ?? [] } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLabelledTrailingClosure() { @@ -2296,7 +2294,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLinewrappedMultipleTrailingClosures() { @@ -2308,7 +2306,7 @@ class IndentTests: RulesTests { context.completeTransition(finished) } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLinewrappedMultipleTrailingClosures2() { @@ -2322,7 +2320,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent comments @@ -2330,19 +2328,19 @@ class IndentTests: RulesTests { func testCommentIndenting() { let input = "/* foo\nbar */" let output = "/* foo\n bar */" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testCommentIndentingWithTrailingClose() { let input = "/*\nfoo\n*/" let output = "/*\n foo\n */" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testCommentIndentingWithTrailingClose2() { let input = "/* foo\n*/" let output = "/* foo\n */" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedCommentIndenting() { @@ -2356,7 +2354,7 @@ class IndentTests: RulesTests { } */ """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNestedCommentIndenting2() { @@ -2380,17 +2378,17 @@ class IndentTests: RulesTests { ``` */ """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testCommentedCodeBlocksNotIndented() { let input = "func foo() {\n// var foo: Int\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testBlankCodeCommentBlockLinesNotIndented() { let input = "func foo() {\n//\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testCommentedCodeAfterBracketNotIndented() { @@ -2400,7 +2398,7 @@ class IndentTests: RulesTests { second, ] """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testCommentedCodeAfterBracketNotIndented2() { @@ -2409,7 +2407,7 @@ class IndentTests: RulesTests { // second, third] """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // TODO: maybe need special case handling for this? @@ -2424,25 +2422,25 @@ class IndentTests: RulesTests { // comment // block """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent multiline strings func testSimpleMultilineString() { let input = "\"\"\"\n hello\n world\n\"\"\"" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIndentedSimpleMultilineString() { let input = "{\n\"\"\"\n hello\n world\n \"\"\"\n}" let output = "{\n \"\"\"\n hello\n world\n \"\"\"\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testMultilineStringWithEscapedLinebreak() { let input = "\"\"\"\n hello \\n world\n\"\"\"" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringWrappedAfter() { @@ -2452,7 +2450,7 @@ class IndentTests: RulesTests { baz \"\"") """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringInNestedCalls() { @@ -2461,7 +2459,7 @@ class IndentTests: RulesTests { baz \"\"")) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringInFunctionWithfollowingArgument() { @@ -2470,7 +2468,7 @@ class IndentTests: RulesTests { baz \"\"", quux: 5)) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testReduceIndentForMultilineString() { @@ -2490,7 +2488,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testReduceIndentForMultilineString2() { @@ -2504,7 +2502,7 @@ class IndentTests: RulesTests { bar \"\"") """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentMultilineStringWithMultilineInterpolation() { @@ -2519,7 +2517,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringWithMultilineNestedInterpolation() { @@ -2536,7 +2534,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringWithMultilineNestedInterpolation2() { @@ -2554,7 +2552,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indentStrings = true @@ -2579,7 +2577,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testNoIndentMultilineStringWithOmittedReturn() { @@ -2593,7 +2591,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentMultilineStringOnOwnLineInMethodCall() { @@ -2607,7 +2605,7 @@ class IndentTests: RulesTests { ) """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentMultilineStringInMethodCall() { @@ -2624,7 +2622,7 @@ class IndentTests: RulesTests { """) """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentMultilineStringAtTopLevel() { @@ -2647,7 +2645,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indent: " ", indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentMultilineStringWithBlankLine() { @@ -2667,7 +2665,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentMultilineStringPreservesBlankLines() { @@ -2679,7 +2677,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testUnindentMultilineStringAtTopLevel() { @@ -2702,7 +2700,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indent: " ", indentStrings: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentUnderIndentedMultilineStringPreservesBlankLineIndent() { @@ -2729,7 +2727,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(truncateBlankLines: false) - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, options: options) } @@ -2757,7 +2755,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(truncateBlankLines: false) - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, options: options) } @@ -2766,7 +2764,7 @@ class IndentTests: RulesTests { func testIndentIndentedSimpleRawMultilineString() { let input = "{\n##\"\"\"\n hello\n world\n \"\"\"##\n}" let output = "{\n ##\"\"\"\n hello\n world\n \"\"\"##\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent multiline regex literals @@ -2779,7 +2777,7 @@ class IndentTests: RulesTests { (baz?) /# """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoMisindentCasePath() { @@ -2789,7 +2787,7 @@ class IndentTests: RulesTests { environment: {} ) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent #if/#else/#elseif/#endif @@ -2889,7 +2887,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent #if/#else/#elseif/#endif (mode: indent) @@ -2897,82 +2895,82 @@ class IndentTests: RulesTests { func testIfEndifIndenting() { let input = "#if x\n// foo\n#endif" let output = "#if x\n // foo\n#endif" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentedIfEndifIndenting() { let input = "{\n#if x\n// foo\nfoo()\n#endif\n}" let output = "{\n #if x\n // foo\n foo()\n #endif\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIfElseEndifIndenting() { let input = "#if x\n // foo\nfoo()\n#else\n // bar\n#endif" let output = "#if x\n // foo\n foo()\n#else\n // bar\n#endif" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testEnumIfCaseEndifIndenting() { let input = "enum Foo {\ncase bar\n#if x\ncase baz\n#endif\n}" let output = "enum Foo {\n case bar\n #if x\n case baz\n #endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let output = "switch foo {\ncase .bar: break\n#if x\n case .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting2() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let output = "switch foo {\n case .bar: break\n #if x\n case .baz: break\n #endif\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting3() { let input = "switch foo {\n#if x\ncase .bar: break\ncase .baz: break\n#endif\n}" let output = "switch foo {\n#if x\n case .bar: break\n case .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting4() { let input = "switch foo {\n#if x\ncase .bar:\nbreak\ncase .baz:\nbreak\n#endif\n}" let output = "switch foo {\n #if x\n case .bar:\n break\n case .baz:\n break\n #endif\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseElseCaseEndifIndenting() { let input = "switch foo {\n#if x\ncase .bar: break\n#else\ncase .baz: break\n#endif\n}" let output = "switch foo {\n#if x\n case .bar: break\n#else\n case .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseElseCaseEndifIndenting2() { let input = "switch foo {\n#if x\ncase .bar: break\n#else\ncase .baz: break\n#endif\n}" let output = "switch foo {\n #if x\n case .bar: break\n #else\n case .baz: break\n #endif\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfEndifInsideCaseIndenting() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\ncase .bar:\n #if x\n bar()\n #endif\n baz()\ncase .baz: break\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testSwitchIfEndifInsideCaseIndenting2() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\n case .bar:\n #if x\n bar()\n #endif\n baz()\n case .baz: break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testIfUnknownCaseEndifIndenting() { @@ -2985,7 +2983,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: false, ifdefIndent: .indent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfUnknownCaseEndifIndenting2() { @@ -2998,7 +2996,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .indent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumIndenting() { @@ -3010,7 +3008,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIfEndifInsideEnumWithTrailingCommentIndenting() { @@ -3022,7 +3020,7 @@ class IndentTests: RulesTests { #endif // ends } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentCommentBeforeIfdefAroundCase() { @@ -3043,7 +3041,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentCommentedCodeBeforeIfdefAroundCase() { @@ -3057,7 +3055,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentIfdefFollowedByCommentAroundCase() { @@ -3076,7 +3074,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIfDefPostfixMemberSyntax() { @@ -3108,7 +3106,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentIfDefPostfixMemberSyntax2() { @@ -3123,7 +3121,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentDotExpressionInsideIfdef() { @@ -3140,7 +3138,7 @@ class IndentTests: RulesTests { #endif }() """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent #if/#else/#elseif/#endif (mode: noindent) @@ -3148,33 +3146,33 @@ class IndentTests: RulesTests { func testIfEndifNoIndenting() { let input = "#if x\n// foo\n#endif" let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfEndifNoIndenting() { let input = "{\n#if x\n// foo\n#endif\n}" let output = "{\n #if x\n // foo\n #endif\n}" let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfElseEndifNoIndenting() { let input = "#if x\n// foo\n#else\n// bar\n#endif" let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfCaseEndifNoIndenting() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfCaseEndifNoIndenting2() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let output = "switch foo {\n case .bar: break\n #if x\n case .baz: break\n #endif\n}" let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfUnknownCaseEndifNoIndenting() { @@ -3187,7 +3185,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfUnknownCaseEndifNoIndenting2() { @@ -3200,21 +3198,21 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideCaseNoIndenting() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\ncase .bar:\n #if x\n bar()\n #endif\n baz()\ncase .baz: break\n}" let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testIfEndifInsideCaseNoIndenting2() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\n case .bar:\n #if x\n bar()\n #endif\n baz()\n case .baz: break\n}" let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testSwitchCaseInIfEndif() { @@ -3236,7 +3234,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .indent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testSwitchCaseInIfEndifNoIndenting() { @@ -3258,7 +3256,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumNoIndenting() { @@ -3271,7 +3269,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumWithTrailingCommentNoIndenting() { @@ -3284,7 +3282,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxNoIndenting() { @@ -3303,7 +3301,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxNoIndenting2() { @@ -3321,7 +3319,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxNoIndenting3() { @@ -3339,7 +3337,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentDotInitInsideIfdef() { @@ -3355,7 +3353,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentDotInitInsideIfdef2() { @@ -3369,7 +3367,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent #if/#else/#elseif/#endif (mode: outdent) @@ -3377,60 +3375,60 @@ class IndentTests: RulesTests { func testIfEndifOutdenting() { let input = "#if x\n// foo\n#endif" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfEndifOutdenting() { let input = "{\n#if x\n// foo\n#endif\n}" let output = "{\n#if x\n // foo\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfElseEndifOutdenting() { let input = "#if x\n// foo\n#else\n// bar\n#endif" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfElseEndifOutdenting() { let input = "{\n#if x\n// foo\nfoo()\n#else\n// bar\n#endif\n}" let output = "{\n#if x\n // foo\n foo()\n#else\n // bar\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfElseifEndifOutdenting() { let input = "#if x\n// foo\n#elseif y\n// bar\n#endif" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfElseifEndifOutdenting() { let input = "{\n#if x\n// foo\nfoo()\n#elseif y\n// bar\n#endif\n}" let output = "{\n#if x\n // foo\n foo()\n#elseif y\n // bar\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testNestedIndentedIfElseifEndifOutdenting() { let input = "{\n#if x\n#if y\n// foo\nfoo()\n#elseif y\n// bar\n#endif\n#endif\n}" let output = "{\n#if x\n#if y\n // foo\n foo()\n#elseif y\n // bar\n#endif\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testDoubleNestedIndentedIfElseifEndifOutdenting() { let input = "{\n#if x\n#if y\n#if z\n// foo\nfoo()\n#elseif y\n// bar\n#endif\n#endif\n#endif\n}" let output = "{\n#if x\n#if y\n#if z\n // foo\n foo()\n#elseif y\n // bar\n#endif\n#endif\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfCaseEndifOutdenting() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumOutdenting() { @@ -3443,7 +3441,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumWithTrailingCommentOutdenting() { @@ -3456,7 +3454,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxOutdenting() { @@ -3475,7 +3473,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxOutdenting2() { @@ -3493,7 +3491,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxOutdenting3() { @@ -3511,50 +3509,50 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent expression after return func testIndentIdentifierAfterReturn() { let input = "if foo {\n return\n bar\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentEnumValueAfterReturn() { let input = "if foo {\n return\n .bar\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineExpressionAfterReturn() { let input = "if foo {\n return\n bar +\n baz\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentClosingBraceAfterReturn() { let input = "if foo {\n return\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentCaseAfterReturn() { let input = "switch foo {\ncase bar:\n return\ncase baz:\n return\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentCaseAfterWhere() { let input = "switch foo {\ncase bar\nwhere baz:\nreturn\ndefault:\nreturn\n}" let output = "switch foo {\ncase bar\n where baz:\n return\ndefault:\n return\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testDontIndentIfAfterReturn() { let input = "if foo {\n return\n if bar {}\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentFuncAfterReturn() { let input = "if foo {\n return\n func bar() {}\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent fragments @@ -3563,41 +3561,41 @@ class IndentTests: RulesTests { let input = " func foo() {\nbar()\n}" let output = " func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentFragmentAfterBlankLines() { let input = "\n\n func foo() {\nbar()\n}" let output = "\n\n func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testUnterminatedFragment() { let input = "class Foo {\n\n func foo() {\nbar()\n}" let output = "class Foo {\n\n func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, - exclude: ["blankLinesAtStartOfScope"]) + testFormatting(for: input, output, rule: .indent, options: options, + exclude: [.blankLinesAtStartOfScope]) } func testOverTerminatedFragment() { let input = " func foo() {\nbar()\n}\n\n}" let output = " func foo() {\n bar()\n }\n\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testDontCorruptPartialFragment() { let input = " } foo {\n bar\n }\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testDontCorruptPartialFragment2() { let input = " return completionHandler(nil)\n }\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testDontCorruptPartialFragment3() { @@ -3607,7 +3605,7 @@ class IndentTests: RulesTests { foo2: bar3 """ let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent with tabs @@ -3618,7 +3616,7 @@ class IndentTests: RulesTests { baz: Int) """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testTabIndentWrappedTupleWithoutSmartTabs() { @@ -3631,7 +3629,7 @@ class IndentTests: RulesTests { \t\t\t\t\t baz: Int) """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testTabIndentCaseWithSmartTabs() { @@ -3650,7 +3648,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } func testTabIndentCaseWithoutSmartTabs() { @@ -3669,7 +3667,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } func testTabIndentCaseWithoutSmartTabs2() { @@ -3689,7 +3687,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(indent: "\t", indentCase: true, tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } // indent blank lines @@ -3702,10 +3700,9 @@ class IndentTests: RulesTests { \tquux() } """ - let rules = [FormatRules.indent, FormatRules.trailingSpace] let options = FormatOptions(indent: "\t", truncateBlankLines: true, tabWidth: 2) - XCTAssertEqual(try lint(input, rules: rules, options: options), [ - Formatter.Change(line: 3, rule: FormatRules.trailingSpace, filePath: nil), + XCTAssertEqual(try lint(input, rules: [.indent, .trailingSpace], options: options), [ + Formatter.Change(line: 3, rule: .trailingSpace, filePath: nil), ]) } @@ -3719,8 +3716,8 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", truncateBlankLines: false, tabWidth: 2) - testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["consecutiveBlankLines", "wrapConditionalBodies"]) + testFormatting(for: input, rule: .indent, options: options, + exclude: [.consecutiveBlankLines, .wrapConditionalBodies]) } // async @@ -3733,7 +3730,7 @@ class IndentTests: RulesTests { async throws -> String {} """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testAsyncTypedThrowsNotUnindented() { @@ -3744,7 +3741,7 @@ class IndentTests: RulesTests { async throws(Foo) -> String {} """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentAsyncLet() { @@ -3760,7 +3757,7 @@ class IndentTests: RulesTests { async let baz = quux() } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentAsyncLetAfterLet() { @@ -3770,7 +3767,7 @@ class IndentTests: RulesTests { async let foo = bar() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentAsyncLetAfterBrace() { @@ -3783,7 +3780,7 @@ class IndentTests: RulesTests { async let foo = bar() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testAsyncFunctionArgumentLabelNotIndented() { @@ -3794,7 +3791,7 @@ class IndentTests: RulesTests { -> String {} """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentIfExpressionAssignmentOnNextLine() { @@ -3836,7 +3833,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentIfExpressionAssignmentOnSameLine() { @@ -3854,7 +3851,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapMultilineConditionalAssignment]) } func testIndentSwitchExpressionAssignment() { @@ -3878,7 +3875,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentSwitchExpressionAssignmentInNestedScope() { @@ -3914,7 +3911,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantProperty"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantProperty]) } func testIndentNestedSwitchExpressionAssignment() { @@ -3948,7 +3945,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentSwitchExpressionAssignmentWithComments() { @@ -3982,7 +3979,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentIfExpressionWithSingleComment() { @@ -3998,7 +3995,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIfExpressionWithComments() { @@ -4016,7 +4013,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentMultilineIfExpression() { @@ -4037,7 +4034,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: [.braces]) } func testIndentNestedIfExpressionWithComments() { @@ -4062,7 +4059,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentIfExpressionWithMultilineComments() { @@ -4080,7 +4077,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testSE0380Example() { @@ -4094,6 +4091,23 @@ class IndentTests: RulesTests { print(bullet) """ let options = FormatOptions() - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["wrapConditionalBodies", "andOperator", "redundantParens"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.wrapConditionalBodies, .andOperator, .redundantParens]) + } + + func testWrappedTernaryOperatorIndentsChainedCalls() { + let input = """ + let ternary = condition + ? values + .map { $0.bar } + .filter { $0.hasFoo } + .last + : other.values + .compactMap { $0 } + .first? + .with(property: updatedValue) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, rule: .indent, options: options) } } diff --git a/Tests/Rules/InitCoderUnavailableTests.swift b/Tests/Rules/InitCoderUnavailableTests.swift new file mode 100644 index 00000000..d26304a6 --- /dev/null +++ b/Tests/Rules/InitCoderUnavailableTests.swift @@ -0,0 +1,143 @@ +// +// InitCoderUnavailableTests.swift +// SwiftFormatTests +// +// Created by Facundo Menzella on 8/20/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class InitCoderUnavailableTests: XCTestCase { + func testInitCoderUnavailableEmptyFunction() { + let input = """ + struct A: UIView { + required init?(coder aDecoder: NSCoder) {} + } + """ + let output = """ + struct A: UIView { + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) {} + } + """ + testFormatting(for: input, output, rule: .initCoderUnavailable, + exclude: [.unusedArguments]) + } + + func testInitCoderUnavailableFatalErrorNilDisabled() { + let input = """ + extension Module { + final class A: UIView { + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + } + """ + let output = """ + extension Module { + final class A: UIView { + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + } + """ + let options = FormatOptions(initCoderNil: false) + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) + } + + func testInitCoderUnavailableFatalErrorNilEnabled() { + let input = """ + extension Module { + final class A: UIView { + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + } + """ + let output = """ + extension Module { + final class A: UIView { + @available(*, unavailable) + required init?(coder _: NSCoder) { + nil + } + } + } + """ + let options = FormatOptions(initCoderNil: true) + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) + } + + func testInitCoderUnavailableAlreadyPresent() { + let input = """ + extension Module { + final class A: UIView { + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError() + } + } + } + """ + testFormatting(for: input, rule: .initCoderUnavailable) + } + + func testInitCoderUnavailableImplemented() { + let input = """ + extension Module { + final class A: UIView { + required init?(coder aCoder: NSCoder) { + aCoder.doSomething() + } + } + } + """ + testFormatting(for: input, rule: .initCoderUnavailable) + } + + func testPublicInitCoderUnavailable() { + let input = """ + class Foo: UIView { + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + """ + let output = """ + class Foo: UIView { + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + """ + testFormatting(for: input, output, rule: .initCoderUnavailable) + } + + func testPublicInitCoderUnavailable2() { + let input = """ + class Foo: UIView { + required public init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + """ + let output = """ + class Foo: UIView { + @available(*, unavailable) + required public init?(coder _: NSCoder) { + nil + } + } + """ + let options = FormatOptions(initCoderNil: true) + testFormatting(for: input, output, rule: .initCoderUnavailable, + options: options, exclude: [.modifierOrder]) + } +} diff --git a/Tests/Rules/IsEmptyTests.swift b/Tests/Rules/IsEmptyTests.swift new file mode 100644 index 00000000..0fa41f13 --- /dev/null +++ b/Tests/Rules/IsEmptyTests.swift @@ -0,0 +1,159 @@ +// +// IsEmptyTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/15/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class IsEmptyTests: XCTestCase { + // count == 0 + + func testCountEqualsZero() { + let input = "if foo.count == 0 {}" + let output = "if foo.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testFunctionCountEqualsZero() { + let input = "if foo().count == 0 {}" + let output = "if foo().isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testExpressionCountEqualsZero() { + let input = "if foo || bar.count == 0 {}" + let output = "if foo || bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfCountEqualsZero() { + let input = "if foo, bar.count == 0 {}" + let output = "if foo, bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testOptionalCountEqualsZero() { + let input = "if foo?.count == 0 {}" + let output = "if foo?.isEmpty == true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testOptionalChainCountEqualsZero() { + let input = "if foo?.bar.count == 0 {}" + let output = "if foo?.bar.isEmpty == true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfOptionalCountEqualsZero() { + let input = "if foo, bar?.count == 0 {}" + let output = "if foo, bar?.isEmpty == true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testTernaryCountEqualsZero() { + let input = "foo ? bar.count == 0 : baz.count == 0" + let output = "foo ? bar.isEmpty : baz.isEmpty" + testFormatting(for: input, output, rule: .isEmpty) + } + + // count != 0 + + func testCountNotEqualToZero() { + let input = "if foo.count != 0 {}" + let output = "if !foo.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testFunctionCountNotEqualToZero() { + let input = "if foo().count != 0 {}" + let output = "if !foo().isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testExpressionCountNotEqualToZero() { + let input = "if foo || bar.count != 0 {}" + let output = "if foo || !bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfCountNotEqualToZero() { + let input = "if foo, bar.count != 0 {}" + let output = "if foo, !bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + // count > 0 + + func testCountGreaterThanZero() { + let input = "if foo.count > 0 {}" + let output = "if !foo.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountExpressionGreaterThanZero() { + let input = "if a.count - b.count > 0 {}" + testFormatting(for: input, rule: .isEmpty) + } + + // optional count + + func testOptionalCountNotEqualToZero() { + let input = "if foo?.count != 0 {}" // nil evaluates to true + let output = "if foo?.isEmpty != true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testOptionalChainCountNotEqualToZero() { + let input = "if foo?.bar.count != 0 {}" // nil evaluates to true + let output = "if foo?.bar.isEmpty != true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfOptionalCountNotEqualToZero() { + let input = "if foo, bar?.count != 0 {}" + let output = "if foo, bar?.isEmpty != true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + // edge cases + + func testTernaryCountNotEqualToZero() { + let input = "foo ? bar.count != 0 : baz.count != 0" + let output = "foo ? !bar.isEmpty : !baz.isEmpty" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountEqualsZeroAfterOptionalOnPreviousLine() { + let input = "_ = foo?.bar\nbar.count == 0 ? baz() : quux()" + let output = "_ = foo?.bar\nbar.isEmpty ? baz() : quux()" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountEqualsZeroAfterOptionalCallOnPreviousLine() { + let input = "foo?.bar()\nbar.count == 0 ? baz() : quux()" + let output = "foo?.bar()\nbar.isEmpty ? baz() : quux()" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountEqualsZeroAfterTrailingCommentOnPreviousLine() { + let input = "foo?.bar() // foobar\nbar.count == 0 ? baz() : quux()" + let output = "foo?.bar() // foobar\nbar.isEmpty ? baz() : quux()" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountGreaterThanZeroAfterOpenParen() { + let input = "foo(bar.count > 0)" + let output = "foo(!bar.isEmpty)" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountGreaterThanZeroAfterArgumentLabel() { + let input = "foo(bar: baz.count > 0)" + let output = "foo(bar: !baz.isEmpty)" + testFormatting(for: input, output, rule: .isEmpty) + } +} diff --git a/Tests/Rules/LeadingDelimitersTests.swift b/Tests/Rules/LeadingDelimitersTests.swift new file mode 100644 index 00000000..49914798 --- /dev/null +++ b/Tests/Rules/LeadingDelimitersTests.swift @@ -0,0 +1,48 @@ +// +// LeadingDelimitersTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/11/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class LeadingDelimitersTests: XCTestCase { + func testLeadingCommaMovedToPreviousLine() { + let input = """ + let foo = 5 + , bar = 6 + """ + let output = """ + let foo = 5, + bar = 6 + """ + testFormatting(for: input, output, rule: .leadingDelimiters) + } + + func testLeadingColonFollowedByCommentMovedToPreviousLine() { + let input = """ + let foo + : /* string */ String + """ + let output = """ + let foo: + /* string */ String + """ + testFormatting(for: input, output, rule: .leadingDelimiters) + } + + func testCommaMovedBeforeCommentIfLineEndsInComment() { + let input = """ + let foo = 5 // first + , bar = 6 + """ + let output = """ + let foo = 5, // first + bar = 6 + """ + testFormatting(for: input, output, rule: .leadingDelimiters) + } +} diff --git a/Tests/Rules/LinebreakAtEndOfFileTests.swift b/Tests/Rules/LinebreakAtEndOfFileTests.swift new file mode 100644 index 00000000..8fadc0e8 --- /dev/null +++ b/Tests/Rules/LinebreakAtEndOfFileTests.swift @@ -0,0 +1,24 @@ +// +// LinebreakAtEndOfFileTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class LinebreakAtEndOfFileTests: XCTestCase { + func testLinebreakAtEndOfFile() { + let input = "foo\nbar" + let output = "foo\nbar\n" + testFormatting(for: input, output, rule: .linebreakAtEndOfFile) + } + + func testNoLinebreakAtEndOfFragment() { + let input = "foo\nbar" + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .linebreakAtEndOfFile, options: options) + } +} diff --git a/Tests/Rules/LinebreaksTests.swift b/Tests/Rules/LinebreaksTests.swift new file mode 100644 index 00000000..4bfaefb5 --- /dev/null +++ b/Tests/Rules/LinebreaksTests.swift @@ -0,0 +1,36 @@ +// +// LinebreaksTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/25/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class LinebreaksTests: XCTestCase { + func testCarriageReturn() { + let input = "foo\rbar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } + + func testCarriageReturnLinefeed() { + let input = "foo\r\nbar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } + + func testVerticalTab() { + let input = "foo\u{000B}bar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } + + func testFormfeed() { + let input = "foo\u{000C}bar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } +} diff --git a/Tests/Rules/MarkTypesTests.swift b/Tests/Rules/MarkTypesTests.swift new file mode 100644 index 00000000..dc97df45 --- /dev/null +++ b/Tests/Rules/MarkTypesTests.swift @@ -0,0 +1,838 @@ +// +// MarkTypesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 9/27/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class MarkTypesTests: XCTestCase { + func testAddsMarkBeforeTypes() { + let input = """ + struct Foo {} + class Bar {} + enum Baz {} + protocol Quux {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + + // MARK: - Bar + + class Bar {} + + // MARK: - Baz + + enum Baz {} + + // MARK: - Quux + + protocol Quux {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testDoesntAddMarkBeforeStructWithExistingMark() { + let input = """ + // MARK: - Foo + + struct Foo {} + extension Foo {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testCorrectsTypoInTypeMark() { + let input = """ + // mark: foo + + struct Foo {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testUpdatesMarkAfterTypeIsRenamed() { + let input = """ + // MARK: - FooBarControllerFactory + + struct FooBarControllerBuilder {} + extension FooBarControllerBuilder {} + """ + + let output = """ + // MARK: - FooBarControllerBuilder + + struct FooBarControllerBuilder {} + extension FooBarControllerBuilder {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testAddsMarkBeforeTypeWithDocComment() { + let input = """ + /// This is a doc comment with several + /// lines of prose at the start + /// - And then, after the prose, + /// - a few bullet points just for fun + actor Foo {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + + /// This is a doc comment with several + /// lines of prose at the start + /// - And then, after the prose, + /// - a few bullet points just for fun + actor Foo {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testCustomTypeMark() { + let input = """ + struct Foo {} + extension Foo {} + """ + + let output = """ + // TYPE DEFINITION: Foo + + struct Foo {} + extension Foo {} + """ + + testFormatting( + for: input, output, rule: .markTypes, + options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t") + ) + } + + func testDoesNothingForExtensionWithoutProtocolConformance() { + let input = """ + extension Foo {} + extension Foo {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func preservesExistingCommentForExtensionWithNoConformances() { + let input = """ + // MARK: Description of extension + + extension Foo {} + extension Foo {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testAddsMarkCommentForExtensionWithConformance() { + let input = """ + extension Foo: BarProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testUpdatesExtensionMarkToCorrectMark() { + let input = """ + // MARK: - BarProtocol + + extension Foo: BarProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testAddsMarkCommentForExtensionWithMultipleConformances() { + let input = """ + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol, BazProtocol + + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testUpdatesMarkCommentWithCorrectConformances() { + let input = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol, BazProtocol + + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testCustomExtensionMarkComment() { + let input = """ + struct Foo {} + extension Foo: BarProtocol {} + extension String: BarProtocol {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + + // EXTENSION: - BarProtocol + + extension Foo: BarProtocol {} + + // EXTENSION: - String: BarProtocol + + extension String: BarProtocol {} + """ + + testFormatting( + for: input, output, rule: .markTypes, + options: FormatOptions( + extensionMarkComment: "EXTENSION: - %t: %c", + groupedExtensionMarkComment: "EXTENSION: - %c" + ) + ) + } + + func testTypeAndExtensionMarksTogether() { + let input = """ + struct Foo {} + extension Foo: Bar {} + extension String: Bar {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + + // MARK: Bar + + extension Foo: Bar {} + + // MARK: - String + Bar + + extension String: Bar {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testFullyQualifiedTypeNames() { + let input = """ + extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} + extension MyModule.Foo {} + """ + + let output = """ + // MARK: - MyModule.Foo + MyModule.MyNamespace.BarProtocol, QuuxProtocol + + extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} + extension MyModule.Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testWhereClauseConformanceWithExactConstraint() { + let input = """ + extension Array: BarProtocol where Element == String {} + extension Array {} + """ + + let output = """ + // MARK: - Array + BarProtocol + + extension Array: BarProtocol where Element == String {} + extension Array {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testWhereClauseConformanceWithConformanceConstraint() { + let input = """ + extension Array: BarProtocol where Element: BarProtocol {} + extension Array {} + """ + + let output = """ + // MARK: - Array + BarProtocol + + extension Array: BarProtocol where Element: BarProtocol {} + extension Array {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testWhereClauseWithExactConstraint() { + let input = """ + extension Array where Element == String {} + extension Array {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testWhereClauseWithConformanceConstraint() { + let input = """ + // MARK: [BarProtocol] helpers + + extension Array where Element: BarProtocol {} + extension Rules {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testPlacesMarkAfterImports() { + let input = """ + import Foundation + import os + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + let output = """ + import Foundation + import os + + // MARK: - Rules + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testPlacesMarkAfterFileHeader() { + let input = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + let output = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + // MARK: - Rules + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testPlacesMarkAfterFileHeaderAndImports() { + let input = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + import Foundation + import os + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + let output = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + import Foundation + import os + + // MARK: - Rules + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testDoesNothingIfOnlyOneDeclaration() { + let input = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + import Foundation + import os + + /// All of SwiftFormat's Rule implementation + class Rules {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testMultipleExtensionsOfSameType() { + let input = """ + extension Foo: BarProtocol {} + extension Foo: QuuxProtocol {} + """ + + let output = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol {} + + // MARK: - Foo + QuuxProtocol + + extension Foo: QuuxProtocol {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testNeverMarkTypes() { + let input = """ + struct EmptyFoo {} + struct EmptyBar { } + struct EmptyBaz { + + } + struct Quux { + let foo = 1 + } + """ + + let options = FormatOptions(markTypes: .never) + testFormatting( + for: input, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testMarkTypesIfNotEmpty() { + let input = """ + struct EmptyFoo {} + struct EmptyBar { } + struct EmptyBaz { + + } + struct Quux { + let foo = 1 + } + """ + + let output = """ + struct EmptyFoo {} + struct EmptyBar { } + struct EmptyBaz { + + } + + // MARK: - Quux + + struct Quux { + let foo = 1 + } + """ + + let options = FormatOptions(markTypes: .ifNotEmpty) + testFormatting( + for: input, output, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testNeverMarkExtensions() { + let input = """ + extension EmptyFoo: FooProtocol {} + extension EmptyBar: BarProtocol { } + extension EmptyBaz: BazProtocol { + + } + extension Quux: QuuxProtocol { + let foo = 1 + } + """ + + let options = FormatOptions(markExtensions: .never) + testFormatting( + for: input, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testMarkExtensionsIfNotEmpty() { + let input = """ + extension EmptyFoo: FooProtocol {} + extension EmptyBar: BarProtocol { } + extension EmptyBaz: BazProtocol { + + } + extension Quux: QuuxProtocol { + let foo = 1 + } + """ + + let output = """ + extension EmptyFoo: FooProtocol {} + extension EmptyBar: BarProtocol { } + extension EmptyBaz: BazProtocol { + + } + + // MARK: - Quux + QuuxProtocol + + extension Quux: QuuxProtocol { + let foo = 1 + } + """ + + let options = FormatOptions(markExtensions: .ifNotEmpty) + testFormatting( + for: input, output, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testMarkExtensionsDisabled() { + let input = """ + extension Foo: FooProtocol {} + + // swiftformat:disable markTypes + + extension Bar: BarProtocol {} + + // swiftformat:enable markTypes + + extension Baz: BazProtocol {} + + extension Quux: QuuxProtocol {} + """ + + let output = """ + // MARK: - Foo + FooProtocol + + extension Foo: FooProtocol {} + + // swiftformat:disable markTypes + + extension Bar: BarProtocol {} + + // MARK: - Baz + BazProtocol + + // swiftformat:enable markTypes + + extension Baz: BazProtocol {} + + // MARK: - Quux + QuuxProtocol + + extension Quux: QuuxProtocol {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testExtensionMarkWithImportOfSameName() { + let input = """ + import MagazineLayout + + // MARK: - MagazineLayout + FooProtocol + + extension MagazineLayout: FooProtocol {} + + // MARK: - MagazineLayout + BarProtocol + + extension MagazineLayout: BarProtocol {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testDoesntUseGroupedMarkTemplateWhenSeparatedByOtherType() { + let input = """ + // MARK: - MyComponent + + class MyComponent {} + + // MARK: - MyComponentContent + + struct MyComponentContent {} + + // MARK: - MyComponent + ContentConfigurableView + + extension MyComponent: ContentConfigurableView {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testUsesGroupedMarkTemplateWhenSeparatedByExtensionOfSameType() { + let input = """ + // MARK: - MyComponent + + class MyComponent {} + + // MARK: Equatable + + extension MyComponent: Equatable {} + + // MARK: ContentConfigurableView + + extension MyComponent: ContentConfigurableView {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testDoesntUseGroupedMarkTemplateWhenSeparatedByExtensionOfOtherType() { + let input = """ + // MARK: - MyComponent + + class MyComponent {} + + // MARK: - OtherComponent + Equatable + + extension OtherComponent: Equatable {} + + // MARK: - MyComponent + ContentConfigurableView + + extension MyComponent: ContentConfigurableView {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testAddsMarkBeforeTypesWithNoBlankLineAfterMark() { + let input = """ + struct Foo {} + class Bar {} + enum Baz {} + protocol Quux {} + """ + + let output = """ + // MARK: - Foo + struct Foo {} + + // MARK: - Bar + class Bar {} + + // MARK: - Baz + enum Baz {} + + // MARK: - Quux + protocol Quux {} + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, output, rule: .markTypes, options: options) + } + + func testAddsMarkForTypeInExtension() { + let input = """ + enum Foo {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + let output = """ + // MARK: - Foo + + enum Foo {} + + // MARK: Foo.Bar + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testDoesntAddsMarkForMultipleTypesInExtension() { + let input = """ + enum Foo {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + + struct Quux { + let baaz: Baaz + } + } + """ + + let output = """ + // MARK: - Foo + + enum Foo {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + + struct Quux { + let baaz: Baaz + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testAddsMarkForTypeInExtensionNotFollowingTypeBeingExtended() { + let input = """ + struct Baaz {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + let output = """ + // MARK: - Baaz + + struct Baaz {} + + // MARK: - Foo.Bar + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testHandlesMultipleLayersOfExtensionNesting() { + let input = """ + enum Foo {} + + extension Foo { + enum Bar {} + } + + extension Foo { + extension Bar { + struct Baaz { + let quux: Quux + } + } + } + """ + + let output = """ + // MARK: - Foo + + enum Foo {} + + // MARK: Foo.Bar + + extension Foo { + enum Bar {} + } + + // MARK: Foo.Bar.Baaz + + extension Foo { + extension Bar { + struct Baaz { + let quux: Quux + } + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testMarkTypeLintReturnsErrorAsExpected() throws { + let input = """ + struct MyStruct {} + + extension MyStruct {} + """ + + // Initialize rule names + let _ = FormatRules.byName + let changes = try lint(input, rules: [.markTypes]) + XCTAssertEqual(changes, [ + .init(line: 1, rule: .markTypes, filePath: nil), + .init(line: 2, rule: .markTypes, filePath: nil), + ]) + } +} diff --git a/Tests/Rules/ModifierOrderTests.swift b/Tests/Rules/ModifierOrderTests.swift new file mode 100644 index 00000000..9cb0f10a --- /dev/null +++ b/Tests/Rules/ModifierOrderTests.swift @@ -0,0 +1,98 @@ +// +// ModifierOrderTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 7/28/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ModifierOrderTests: XCTestCase { + func testVarModifiersCorrected() { + let input = "unowned private static var foo" + let output = "private unowned static var foo" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testPrivateSetModifierNotMangled() { + let input = "private(set) public weak lazy var foo" + let output = "public private(set) lazy weak var foo" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testUnownedUnsafeModifierNotMangled() { + let input = "unowned(unsafe) lazy var foo" + let output = "lazy unowned(unsafe) var foo" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testPrivateRequiredStaticFuncModifiers() { + let input = "required static private func foo()" + let output = "private required static func foo()" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testPrivateConvenienceInit() { + let input = "convenience private init()" + let output = "private convenience init()" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testSpaceInModifiersLeftIntact() { + let input = "weak private(set) /* read-only */\npublic var" + let output = "public private(set) /* read-only */\nweak var" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testSpaceInModifiersLeftIntact2() { + let input = "nonisolated(unsafe) public var foo: String" + let output = "public nonisolated(unsafe) var foo: String" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testPrefixModifier() { + let input = "prefix public static func - (rhs: Foo) -> Foo" + let output = "public static prefix func - (rhs: Foo) -> Foo" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testModifierOrder() { + let input = "override public var foo: Int { 5 }" + let output = "public override var foo: Int { 5 }" + let options = FormatOptions(modifierOrder: ["public", "override"]) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testConsumingModifierOrder() { + let input = "consuming public func close()" + let output = "public consuming func close()" + let options = FormatOptions(modifierOrder: ["public", "consuming"]) + testFormatting(for: input, output, rule: .modifierOrder, options: options, exclude: [.noExplicitOwnership]) + } + + func testNoConfusePostfixIdentifierWithKeyword() { + let input = "var foo = .postfix\noverride init() {}" + testFormatting(for: input, rule: .modifierOrder) + } + + func testNoConfusePostfixIdentifierWithKeyword2() { + let input = "var foo = postfix\noverride init() {}" + testFormatting(for: input, rule: .modifierOrder) + } + + func testNoConfuseCaseWithModifier() { + let input = """ + enum Foo { + case strong + case weak + public init() {} + } + """ + testFormatting(for: input, rule: .modifierOrder) + } +} diff --git a/Tests/Rules/NoExplicitOwnershipTests.swift b/Tests/Rules/NoExplicitOwnershipTests.swift new file mode 100644 index 00000000..26639078 --- /dev/null +++ b/Tests/Rules/NoExplicitOwnershipTests.swift @@ -0,0 +1,64 @@ +// +// NoExplicitOwnershipTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 8/27/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class NoExplicitOwnershipTests: XCTestCase { + func testRemovesOwnershipKeywordsFromFunc() { + let input = """ + consuming func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} + borrowing func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} + """ + + let output = """ + func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} + func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} + """ + + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) + } + + func testRemovesOwnershipKeywordsFromClosure() { + let input = """ + foos.map { (foo: consuming Foo) in + foo.bar + } + + foos.map { (foo: borrowing Foo) in + foo.bar + } + """ + + let output = """ + foos.map { (foo: Foo) in + foo.bar + } + + foos.map { (foo: Foo) in + foo.bar + } + """ + + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) + } + + func testRemovesOwnershipKeywordsFromType() { + let input = """ + let consuming: (consuming Foo) -> Bar + let borrowing: (borrowing Foo) -> Bar + """ + + let output = """ + let consuming: (Foo) -> Bar + let borrowing: (Foo) -> Bar + """ + + testFormatting(for: input, output, rule: .noExplicitOwnership) + } +} diff --git a/Tests/Rules/NumberFormattingTests.swift b/Tests/Rules/NumberFormattingTests.swift new file mode 100644 index 00000000..fc4778e1 --- /dev/null +++ b/Tests/Rules/NumberFormattingTests.swift @@ -0,0 +1,203 @@ +// +// NumberFormattingTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/17/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class NumberFormattingTests: XCTestCase { + // hex case + + func testLowercaseLiteralConvertedToUpper() { + let input = "let foo = 0xabcd" + let output = "let foo = 0xABCD" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testMixedCaseLiteralConvertedToUpper() { + let input = "let foo = 0xaBcD" + let output = "let foo = 0xABCD" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testUppercaseLiteralConvertedToLower() { + let input = "let foo = 0xABCD" + let output = "let foo = 0xabcd" + let options = FormatOptions(uppercaseHex: false) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testPInExponentialNotConvertedToUpper() { + let input = "let foo = 0xaBcDp5" + let output = "let foo = 0xABCDp5" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testPInExponentialNotConvertedToLower() { + let input = "let foo = 0xaBcDP5" + let output = "let foo = 0xabcdP5" + let options = FormatOptions(uppercaseHex: false, uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // exponent case + + func testLowercaseExponent() { + let input = "let foo = 0.456E-5" + let output = "let foo = 0.456e-5" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testUppercaseExponent() { + let input = "let foo = 0.456e-5" + let output = "let foo = 0.456E-5" + let options = FormatOptions(uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testUppercaseHexExponent() { + let input = "let foo = 0xFF00p54" + let output = "let foo = 0xFF00P54" + let options = FormatOptions(uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testUppercaseGroupedHexExponent() { + let input = "let foo = 0xFF00_AABB_CCDDp54" + let output = "let foo = 0xFF00_AABB_CCDDP54" + let options = FormatOptions(uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // decimal grouping + + func testDefaultDecimalGrouping() { + let input = "let foo = 1234_56_78" + let output = "let foo = 12_345_678" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testIgnoreDecimalGrouping() { + let input = "let foo = 1234_5_678" + let options = FormatOptions(decimalGrouping: .ignore) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + func testNoDecimalGrouping() { + let input = "let foo = 1234_5_678" + let output = "let foo = 12345678" + let options = FormatOptions(decimalGrouping: .none) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testDecimalGroupingThousands() { + let input = "let foo = 1234" + let output = "let foo = 1_234" + let options = FormatOptions(decimalGrouping: .group(3, 3)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testExponentialGrouping() { + let input = "let foo = 1234e5678" + let output = "let foo = 1_234e5678" + let options = FormatOptions(decimalGrouping: .group(3, 3)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testZeroGrouping() { + let input = "let foo = 1234" + let options = FormatOptions(decimalGrouping: .group(0, 0)) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + // binary grouping + + func testDefaultBinaryGrouping() { + let input = "let foo = 0b11101000_00111111" + let output = "let foo = 0b1110_1000_0011_1111" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testIgnoreBinaryGrouping() { + let input = "let foo = 0b1110_10_00" + let options = FormatOptions(binaryGrouping: .ignore) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + func testNoBinaryGrouping() { + let input = "let foo = 0b1110_10_00" + let output = "let foo = 0b11101000" + let options = FormatOptions(binaryGrouping: .none) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testBinaryGroupingCustom() { + let input = "let foo = 0b110011" + let output = "let foo = 0b11_00_11" + let options = FormatOptions(binaryGrouping: .group(2, 2)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // hex grouping + + func testDefaultHexGrouping() { + let input = "let foo = 0xFF01FF01AE45" + let output = "let foo = 0xFF01_FF01_AE45" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testCustomHexGrouping() { + let input = "let foo = 0xFF00p54" + let output = "let foo = 0xFF_00p54" + let options = FormatOptions(hexGrouping: .group(2, 2)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // octal grouping + + func testDefaultOctalGrouping() { + let input = "let foo = 0o123456701234" + let output = "let foo = 0o1234_5670_1234" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testCustomOctalGrouping() { + let input = "let foo = 0o12345670" + let output = "let foo = 0o12_34_56_70" + let options = FormatOptions(octalGrouping: .group(2, 2)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // fraction grouping + + func testIgnoreFractionGrouping() { + let input = "let foo = 1.234_5_678" + let options = FormatOptions(decimalGrouping: .ignore, fractionGrouping: true) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + func testNoFractionGrouping() { + let input = "let foo = 1.234_5_678" + let output = "let foo = 1.2345678" + let options = FormatOptions(decimalGrouping: .none, fractionGrouping: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testFractionGroupingThousands() { + let input = "let foo = 12.34_56_78" + let output = "let foo = 12.345_678" + let options = FormatOptions(decimalGrouping: .group(3, 3), fractionGrouping: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testHexFractionGrouping() { + let input = "let foo = 0x12.34_56_78p56" + let output = "let foo = 0x12.34_5678p56" + let options = FormatOptions(hexGrouping: .group(4, 4), fractionGrouping: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } +} diff --git a/Tests/Rules/OpaqueGenericParametersTests.swift b/Tests/Rules/OpaqueGenericParametersTests.swift new file mode 100644 index 00000000..13eca06d --- /dev/null +++ b/Tests/Rules/OpaqueGenericParametersTests.swift @@ -0,0 +1,694 @@ +// +// OpaqueGenericParametersTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/5/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class OpaqueGenericParametersTests: XCTestCase { + func testGenericNotModifiedBelowSwift5_7() { + let input = """ + func foo(_ value: T) { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.6") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithNoConstraint() { + let input = """ + func foo(_ value: T) { + print(value) + } + + init(_ value: T) { + print(value) + } + + subscript(_ value: T) -> Foo { + Foo(value) + } + + subscript(_ value: T) -> Foo { + get { + Foo(value) + } + set { + print(newValue) + } + } + """ + + let output = """ + func foo(_ value: some Any) { + print(value) + } + + init(_ value: some Any) { + print(value) + } + + subscript(_ value: some Any) -> Foo { + Foo(value) + } + + subscript(_ value: some Any) -> Foo { + get { + Foo(value) + } + set { + print(newValue) + } + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testDisableSomeAnyGenericType() { + let input = """ + func foo(_ value: T) { + print(value) + } + """ + + let options = FormatOptions(useSomeAny: false, swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithConstraintInBracket() { + let input = """ + func foo(_ fooable: T, barable: U) -> Baaz { + print(fooable, barable) + } + + init(_ fooable: T, barable: U) { + print(fooable, barable) + } + + subscript(_ fooable: T, barable: U) -> Any { + (fooable, barable) + } + """ + + let output = """ + func foo(_ fooable: some Fooable, barable: some Barable) -> Baaz { + print(fooable, barable) + } + + init(_ fooable: some Fooable, barable: some Barable) { + print(fooable, barable) + } + + subscript(_ fooable: some Fooable, barable: some Barable) -> Any { + (fooable, barable) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithConstraintsInWhereClause() { + let input = """ + func foo(_ t: T, _ u: U) -> Baaz where T: Fooable, T: Barable, U: Baazable { + print(t, u) + } + + init(_ t: T, _ u: U) where T: Fooable, T: Barable, U: Baazable { + print(t, u) + } + """ + + let output = """ + func foo(_ t: some Fooable & Barable, _ u: some Baazable) -> Baaz { + print(t, u) + } + + init(_ t: some Fooable & Barable, _ u: some Baazable) { + print(t, u) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterCanRemoveOneButNotOthers_onOneLine() { + let input = """ + func foo(_ foo: T, bar1: U, bar2: U) where S.AssociatedType == Baaz, T: Quuxable, U: Qaaxable { + print(foo, bar1, bar2) + } + """ + + let output = """ + func foo(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where S.AssociatedType == Baaz, U: Qaaxable { + print(foo, bar1, bar2) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterCanRemoveOneButNotOthers_onMultipleLines() { + let input = """ + func foo< + S: Baazable, + T: Fooable, + U: Barable + >(_ foo: T, bar1: U, bar2: U) where + S.AssociatedType == Baaz, + T: Quuxable, + U: Qaaxable + { + print(foo, bar1, bar2) + } + """ + + let output = """ + func foo< + S: Baazable, + U: Barable + >(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where + S.AssociatedType == Baaz, + U: Qaaxable + { + print(foo, bar1, bar2) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithUnknownAssociatedTypeConstraint() { + // If we knew that `T.AssociatedType` was the protocol's primary + // associated type we could update this to `value: some Fooable`, + // but we don't necessarily have that type information available. + // - If primary associated types become very widespread, it may make + // sense to assume (or have an option to assume) that this would work. + let input = """ + func foo(_ value: T) where T.AssociatedType == Bar { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithAssociatedTypeConformance() { + // There is no opaque generic parameter syntax that supports this type of constraint + let input = """ + func foo(_ value: T) where T.AssociatedType: Bar { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithKnownAssociatedTypeConstraint() { + // For known types (like those in the standard library), + // we are able to know their primary associated types + let input = """ + func foo(_ value: T) where T.Element == Foo { + print(value) + } + """ + + let output = """ + func foo(_ value: some Collection) { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithAssociatedTypeConstraint() { + let input = """ + func foo>(_: T) {} + func bar(_: T) where T: Collection {} + func baaz(_: T) where T == any Collection {} + """ + + let output = """ + func foo(_: some Collection) {} + func bar(_: some Collection) {} + func baaz(_: any Collection) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedInMultipleParameters() { + let input = """ + func foo(_ first: T, second: T) { + print(first, second) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedInClosureMultipleTimes() { + let input = """ + func foo(_ closure: (T) -> T) { + closure(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedAsReturnType() { + // A generic used as a return type is different from an opaque result type (SE-244). + // In `-> T where T: Fooable`, the generic type is caller-specified, but with + // `-> some Fooable` the generic type is specified by the function implementation. + // Because those represent different concepts, we can't convert between them. + let input = """ + func foo() -> T { + // ... + } + + func bar() -> T where T: Barable { + // ... + } + + func baaz() -> Set> { + // ... + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedAsReturnTypeAndParameter() { + // Since we can't change the return value, we can't change any of the use cases of T + let input = """ + func foo(_ value: T) -> T { + value + } + + func bar(_ value: T) -> T where T: Barable { + value + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeWithClosureInWhereClauseDoesntCrash() { + let input = """ + struct Foo { + func bar(_ value: V) where U == @Sendable (V) -> Int {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericExtensionSameTypeConstraint() { + let input = """ + func foo(_ u: U) where U == String { + print(u) + } + """ + + let output = """ + func foo(_ u: String) { + print(u) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericExtensionSameTypeGenericConstraint() { + let input = """ + func foo(_ u: U, _ v: V) where U == V { + print(u, v) + } + + func foo(_ u: U, _ v: V) where V == U { + print(u, v) + } + """ + + let output = """ + func foo(_ u: V, _ v: V) { + print(u, v) + } + + func foo(_ u: U, _ v: U) { + print(u, v) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1269() { + let input = """ + func bar( + _ value: V, + _ work: () -> R + ) -> R + where Value == @Sendable () -> V, + V: Sendable + { + work() + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testVariadicParameterNotConvertedToOpaqueGeneric() { + let input = """ + func variadic(_ t: T...) { + print(t) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testNonGenericVariadicParametersDoesntPreventUsingOpaqueGenerics() { + let input = """ + func variadic(t: Any..., u: U, v: Any...) { + print(t, u, v) + } + """ + + let output = """ + func variadic(t: Any..., u: some Any, v: Any...) { + print(t, u, v) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1275() { + let input = """ + func loggedKeypath( + by _: KeyPath..., + actionKeyword _: UserActionKeyword, + identifier _: String + ) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1278() { + let input = """ + public struct Foo { + public func withValue( + _: V, + operation _: () throws -> R + ) rethrows -> R + where Value == @Sendable () -> V, + V: Sendable + {} + + public func withValue( + _: V, + operation _: () async throws -> R + ) async rethrows -> R + where Value == () -> V + {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1392() { + let input = """ + public struct Ref {} + + public extension Ref { + static func weak( + _: Base, + _: ReferenceWritableKeyPath + ) -> Ref where T? == Value {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1684() { + let input = """ + @_specialize(where S == Int) + func foo>(t: S) { + print(t) + } + """ + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericSimplifiedInMethodWithAttributeOrMacro() { + let input = """ + @MyResultBuilder + func foo(foo: T, bar: U) -> MyResult { + foo + bar + } + + @MyFunctionBodyMacro(withArgument: true) + func foo(foo: T, bar: U) { + print(foo, bar) + } + """ + + let output = """ + @MyResultBuilder + func foo(foo: some Foo, bar: some Bar) -> MyResult { + foo + bar + } + + @MyFunctionBodyMacro(withArgument: true) + func foo(foo: some Foo, bar: some Bar) { + print(foo, bar) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericThrowsTypeNotTreatedAsAny() { + let input = """ + func sample(error: ErrorType) throws(ErrorType) { + throw error + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + // MARK: - genericExtensions + + func testGenericExtensionNotModifiedBeforeSwift5_7() { + let input = "extension Array where Element == Foo {}" + + let options = FormatOptions(swiftVersion: "5.6") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParametersRuleSuccessfullyTerminatesInSampleCode() { + let input = """ + class Service { + public func run() {} + private let foo: Foo + private func a() -> Eventual {} + private func b() -> Eventual {} + private func c() -> Eventual {} + private func d() -> Eventual {} + private func e() -> Eventual {} + private func f() -> Eventual {} + private func g() -> Eventual {} + private func h() -> Eventual {} + private func i() {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterUsedInConstraintOfOtherTypeNotChanged() { + let input = """ + func combineResults( + _: Potential, + _: Potential + ) -> Potential where + Success == (Result, Result), + Failure == Never + {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterInheritedFromContextNotRemoved() { + let input = """ + func assign( + on _: DispatchQueue, + to _: AssignTarget, + at _: ReferenceWritableKeyPath + ) where Value: Equatable {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterUsedInBodyNotRemoved() { + let input = """ + func foo(_ value: T) { + typealias TTT = T + let casted = value as TTT + print(casted) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterUsedAsClosureParameterNotRemoved() { + let input = """ + func foo(_: (Foo) -> Void) {} + func bar(_: (Foo) throws -> Void) {} + func baz(_: (Foo) throws(Bar) -> Void) {} + func baaz(_: (Foo) async -> Void) {} + func qux(_: (Foo) async throws -> Void) {} + func quux(_: (Foo) async throws(Bar) -> Void) {} + func qaax(_: ([Foo]) -> Void) {} + func qaax(_: ((Foo, Bar)) -> Void) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testFinalGenericParamRemovedProperlyWithoutHangingComma() { + let input = """ + func foo( + bar _: (Bar) -> Void, + baaz _: Baaz + ) {} + """ + + let output = """ + func foo( + bar _: (Bar) -> Void, + baaz _: some Any + ) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testAddsParensAroundTypeIfNecessary() { + let input = """ + func foo(_: Foo.Type) {} + func bar(_: Foo?) {} + """ + + let output = """ + func foo(_: (some Any).Type) {} + func bar(_: (some Any)?) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testHandlesSingleExactTypeGenericConstraint() { + let input = """ + func foo(with _: T) -> Foo where T == Dependencies {} + """ + + let output = """ + func foo(with _: Dependencies) -> Foo {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericConstraintThatIsGeneric() { + let input = """ + class Foo {} + func foo>(_: T) {} + class Bar {} + func bar>(_: T) {} + """ + + let output = """ + class Foo {} + func foo(_: some Foo) {} + class Bar {} + func bar(_: some Bar) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testDoesntChangeTypeWithConstraintThatReferencesItself() { + // This is a weird one but in the actual code this comes from `ViewModelContext` is both defined + // on the parent type of this declaration (where it has additional important constraints), + // and again in the method itself. Changing this to an opaque parameter breaks the build, because + // it loses the generic constraints applied by the parent type. + let input = """ + func makeSections>(_: ViewModelContext) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParametersDoesntleaveTrailingComma() { + let input = "func f(x: U) -> T where T: A, U: B {}" + let output = "func f(x: some B) -> T where T: A {}" + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, + options: options, exclude: [.unusedArguments]) + } +} diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift new file mode 100644 index 00000000..7880fc4f --- /dev/null +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -0,0 +1,2980 @@ +// +// OrganizeDeclarationsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 8/16/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class OrganizeDeclarationsTests: XCTestCase { + func testOrganizeClassDeclarationsIntoCategories() { + let input = """ + class Foo { + private func privateMethod() {} + + private let bar = 1 + public let baz = 1 + open var quack = 2 + package func packageMethod() {} + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + /* + * Block comment + */ + + init() {} + + /// Doc comment + public func publicMethod() {} + + #if DEBUG + private var foo: Foo? { nil } + #endif + } + + enum Bar { + private var bar: Bar { Bar() } + case enumCase + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + /* + * Block comment + */ + + init() {} + + // MARK: Open + + open var quack = 2 + + // MARK: Public + + public let baz = 1 + + /// Doc comment + public func publicMethod() {} + + // MARK: Package + + package func packageMethod() {} + + // MARK: Internal + + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + // MARK: Private + + private let bar = 1 + + #if DEBUG + private var foo: Foo? { nil } + #endif + + private func privateMethod() {} + + } + + enum Bar { + case enumCase + + // MARK: Private + + private var bar: Bar { Bar() } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testOrganizeClassDeclarationsIntoCategoriesWithCustomTypeOrder() { + let input = """ + class Foo { + private func privateMethod() {} + + private let bar = 1 + public let baz = 1 + open var quack = 2 + package func packageMethod() {} + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + /* + * Block comment + */ + + init() {} + + /// Doc comment + public func publicMethod() {} + + #if DEBUG + private var foo: Foo? { nil } + #endif + } + + enum Bar { + private var bar: Bar { Bar() } + case enumCase + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + /* + * Block comment + */ + + init() {} + + // MARK: Open + + open var quack = 2 + + // MARK: Public + + public let baz = 1 + + /// Doc comment + public func publicMethod() {} + + // MARK: Package + + package func packageMethod() {} + + // MARK: Internal + + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + // MARK: Private + + private let bar = 1 + + #if DEBUG + private var foo: Foo? { nil } + #endif + + private func privateMethod() {} + + } + + enum Bar { + case enumCase + + // MARK: Private + + private var bar: Bar { Bar() } + } + """ + + // The configuration used in Airbnb's Swift Style Guide, + // as defined here: https://github.com/airbnb/swift#subsection-organization + let airbnbVisibilityOrder = "beforeMarks,instanceLifecycle,open,public,package,internal,private,fileprivate" + let airbnbTypeOrder = "nestedType,staticProperty,staticPropertyWithBody,classPropertyWithBody,instanceProperty,instancePropertyWithBody,staticMethod,classMethod,instanceMethod" + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + visibilityOrder: airbnbVisibilityOrder.components(separatedBy: ","), + typeOrder: airbnbTypeOrder.components(separatedBy: ",") + ), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testOrganizeClassDeclarationsIntoCategoriesInTypeOrder() { + let input = """ + class Foo { + private func privateMethod() {} + + private let bar = 1 + public let baz = 1 + open var quack = 2 + package func packageMethod() {} + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + /* + * Block comment + */ + + init() {} + + /// Doc comment + public func publicMethod() {} + } + """ + + let output = """ + class Foo { + + // MARK: Properties + + open var quack = 2 + + public let baz = 1 + + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + private let bar = 1 + + // MARK: Lifecycle + + /* + * Block comment + */ + + init() {} + + // MARK: Functions + + /// Doc comment + public func publicMethod() {} + + package func packageMethod() {} + + private func privateMethod() {} + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testOrganizeTypeWithOverridenFieldsInVisibilityOrder() { + let input = """ + class Test { + + override var b: Any? { nil } + + var a = "" + + override func bar() -> Bar { + Bar() + } + + func foo() -> Foo { + Foo() + } + + func baaz() -> Baaz { + Baaz() + } + + } + """ + + testFormatting( + for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports] + ) + } + + func testOrganizeTypeWithOverridenFieldsInTypeOrder() { + let input = """ + class Test { + + var a = "" + + override var b: Any? { nil } + + func foo() -> Foo { + Foo() + } + + override func bar() -> Bar { + Bar() + } + + func baaz() -> Baaz { + Baaz() + } + + } + """ + + let output = """ + class Test { + + // MARK: Overridden Properties + + override var b: Any? { nil } + + // MARK: Properties + + var a = "" + + // MARK: Overridden Functions + + override func bar() -> Bar { + Bar() + } + + // MARK: Functions + + func foo() -> Foo { + Foo() + } + + func baaz() -> Baaz { + Baaz() + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(organizationMode: .type), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports] + ) + } + + func testOrganizeTypeWithSwiftUIMethodInVisibilityOrder() { + let input = """ + class Test { + + func bar() -> some View { + EmptyView() + } + + func foo() -> Foo { + Foo() + } + + func baaz() -> Baaz { + Baaz() + } + + } + """ + + testFormatting( + for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports] + ) + } + + func testOrganizeSwiftUIViewInTypeOrder() { + let input = """ + struct ContentView: View { + + private var label: String + + @State + var isOn: Bool = false + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + .fixedSize() + } + + init(label: String) { + self.label = label + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: SwiftUI Properties + + @State + var isOn: Bool = false + + // MARK: Properties + + private var label: String + + // MARK: Lifecycle + + init(label: String) { + self.label = label + } + + // MARK: Content Properties + + @ViewBuilder + var body: some View { + toggle + } + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + .fixedSize() + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testOrganizeSwiftUIViewModifierInTypeOrder() { + let input = """ + struct Modifier: ViewModifier { + + private var label: String + + @State + var isOn: Bool = false + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + .fixedSize() + } + + func body(content: Content) -> some View { + content + .overlay { + toggle + } + } + + init(label: String) { + self.label = label + } + } + """ + + let output = """ + struct Modifier: ViewModifier { + + // MARK: SwiftUI Properties + + @State + var isOn: Bool = false + + // MARK: Properties + + private var label: String + + // MARK: Lifecycle + + init(label: String) { + self.label = label + } + + // MARK: Content Properties + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + .fixedSize() + } + + // MARK: Content Methods + + func body(content: Content) -> some View { + content + .overlay { + toggle + } + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testCustomOrganizationInVisibilityOrder() { + let input = """ + class Foo { + public func bar() {} + func baz() {} + private func quux() {} + } + """ + + let output = """ + class Foo { + + // MARK: Private + + private func quux() {} + + // MARK: Internal + + func baz() {} + + // MARK: Public + + public func bar() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + visibilityOrder: ["private", "internal", "public"], + typeOrder: DeclarationType.allCases.map(\.rawValue) + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomOrganizationInVisibilityOrderWithParametrizedTypeOrder() { + let input = """ + class Foo { + + // MARK: Private + + private func quux() {} + + // MARK: Internal + + var baaz: Baaz + + func baz() {} + + // MARK: Public + + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Private + + private func quux() {} + + // MARK: Internal + + func baz() {} + + var baaz: Baaz + + // MARK: Public + + public func bar() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + visibilityOrder: ["private", "internal", "public"], + typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomOrganizationInTypeOrder() { + let input = """ + class Foo { + private func quux() {} + var baaz: Baaz + func baz() {} + init() + override public func baar() + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() + + // MARK: Functions + + public func bar() {} + + func baz() {} + + private func quux() {} + + // MARK: Properties + + var baaz: Baaz + + // MARK: Overridden Functions + + override public func baar() + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + typeOrder: ["beforeMarks", "instanceLifecycle", "instanceMethod", "nestedType", "instanceProperty", "overriddenMethod"] + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testOrganizeDeclarationsIgnoresNotDefinedCategories() { + let input = """ + class Foo { + private func quux() {} + var baaz: Baaz + func baz() {} + init() + override public func baar() + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() + + // MARK: Functions + + override public func baar() + public func bar() {} + + func baz() {} + + private func quux() {} + + // MARK: Properties + + var baaz: Baaz + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomOrganizationInTypeOrderWithParametrizedVisibilityOrder() { + let input = """ + class Foo { + private func quux() {} + var baaz: Baaz + private var fooo: Fooo + func baz() {} + init() + override public func baar() + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() + + // MARK: Functions + + private func quux() {} + + func baz() {} + + public func bar() {} + + // MARK: Properties + + private var fooo: Fooo + + var baaz: Baaz + + // MARK: Overridden Functions + + override public func baar() + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + visibilityOrder: ["private", "internal", "public"], + typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomDeclarationTypeUsedAsTopLevelCategory() { + let input = """ + class Test { + private let foo = "foo" + func bar() {} + } + """ + + let output = """ + class Test { + + // MARK: Functions + + func bar() {} + + // MARK: Private + + private let foo = "foo" + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .visibility, + visibilityOrder: ["instanceMethod"] + Visibility.allCases.map(\.rawValue), + typeOrder: DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" } + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testVisibilityModeWithoutInstanceLifecycle() { + let input = """ + class Test { + init() {} + private func bar() {} + } + """ + + let output = """ + class Test { + + // MARK: Internal + + init() {} + + // MARK: Private + + private func bar() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .visibility, + visibilityOrder: Visibility.allCases.map(\.rawValue), + typeOrder: DeclarationType.allCases.map(\.rawValue) + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomCategoryNamesInVisibilityOrder() { + let input = """ + class Foo { + public var bar: Bar + init(bar: Bar) { + self.bar = bar + } + func baaz() {} + } + """ + + let output = """ + class Foo { + + // MARK: Init + + init(bar: Bar) { + self.bar = bar + } + + // MARK: Public_Group + + public var bar: Bar + + // MARK: Internal + + func baaz() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .visibility, + customVisibilityMarks: ["instanceLifecycle:Init", "public:Public_Group"] + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomCategoryNamesInTypeOrder() { + let input = """ + class Foo { + public var bar: Bar + init(bar: Bar) { + self.bar = bar + } + func baaz() {} + } + """ + + let output = """ + class Foo { + + // MARK: Bar_Bar + + public var bar: Bar + + // MARK: Init + + init(bar: Bar) { + self.bar = bar + } + + // MARK: Buuuz Lightyeeeaaar + + func baaz() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + customTypeMarks: ["instanceLifecycle:Init", "instanceProperty:Bar_Bar", "instanceMethod:Buuuz Lightyeeeaaar"] + ), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testClassNestedInClassIsOrganized() { + let input = """ + public class Foo { + public class Bar { + fileprivate func baz() {} + public var quux: Int + init() {} + deinit {} + } + } + """ + + let output = """ + public class Foo { + public class Bar { + + // MARK: Lifecycle + + init() {} + deinit {} + + // MARK: Public + + public var quux: Int + + // MARK: Fileprivate + + fileprivate func baz() {} + } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .enumNamespaces] + ) + } + + func testStructNestedInExtensionIsOrganized() { + let input = """ + public extension Foo { + struct Bar { + private var foo: Int + private let bar: Int + + public var foobar: (Int, Int) { + (foo, bar) + } + + public init(foo: Int, bar: Int) { + self.foo = foo + self.bar = bar + } + } + } + """ + + let output = """ + public extension Foo { + struct Bar { + + // MARK: Lifecycle + + public init(foo: Int, bar: Int) { + self.foo = foo + self.bar = bar + } + + // MARK: Public + + public var foobar: (Int, Int) { + (foo, bar) + } + + // MARK: Private + + private var foo: Int + private let bar: Int + + } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testOrganizePrivateSet() { + let input = """ + class Foo { + public private(set) var bar: Int + private(set) var baz: Int + internal private(set) var baz: Int + } + """ + + let output = """ + class Foo { + + // MARK: Public + + public private(set) var bar: Int + + // MARK: Internal + + private(set) var baz: Int + internal private(set) var baz: Int + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .redundantInternal] + ) + } + + func testSortDeclarationTypes() { + let input = """ + class Foo { + static var a1: Int = 1 + static var a2: Int = 2 + var d1: CGFloat { + 3.141592653589 + } + + class var b2: String { + "class computed property" + } + + func g() -> Int { + 10 + } + + let c: String = String { + "closure body" + }() + + static func e() {} + + typealias Bar = Int + + static var b1: String { + "static computed property" + } + + class func f() -> Foo { + Foo() + } + + enum NestedEnum {} + + var d2: CGFloat = 3.141592653589 { + didSet {} + } + } + """ + + let output = """ + class Foo { + typealias Bar = Int + + enum NestedEnum {} + + static var a1: Int = 1 + static var a2: Int = 2 + + static var b1: String { + "static computed property" + } + + class var b2: String { + "class computed property" + } + + let c: String = String { + "closure body" + }() + + var d1: CGFloat { + 3.141592653589 + } + + var d2: CGFloat = 3.141592653589 { + didSet {} + } + + static func e() {} + + class func f() -> Foo { + Foo() + } + + func g() -> Int { + 10 + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtEndOfScope, .redundantType, .redundantClosure] + ) + } + + func testSortDeclarationTypesByType() { + let input = """ + class Foo { + var a: Int + init(a: Int) { + self.a = a + } + private convenience init() { + self.init(a: 0) + } + + static var a1: Int = 1 + static var a2: Int = 2 + var d1: CGFloat { + 3.141592653589 + } + + class var b2: String { + "class computed property" + } + + func g() -> Int { + 10 + } + + let c: String = String { + "closure body" + }() + + static func e() {} + + typealias Bar = Int + + static var b1: String { + "static computed property" + } + + class func f() -> Foo { + Foo() + } + + enum NestedEnum {} + + var d2: CGFloat = 3.141592653589 { + didSet {} + } + } + """ + + let output = """ + class Foo { + + // MARK: Nested Types + + typealias Bar = Int + + enum NestedEnum {} + + // MARK: Static Properties + + static var a1: Int = 1 + static var a2: Int = 2 + + // MARK: Static Computed Properties + + static var b1: String { + "static computed property" + } + + // MARK: Class Properties + + class var b2: String { + "class computed property" + } + + // MARK: Properties + + var a: Int + let c: String = String { + "closure body" + }() + + // MARK: Computed Properties + + var d1: CGFloat { + 3.141592653589 + } + + var d2: CGFloat = 3.141592653589 { + didSet {} + } + + // MARK: Lifecycle + + init(a: Int) { + self.a = a + } + + private convenience init() { + self.init(a: 0) + } + + // MARK: Static Functions + + static func e() {} + + // MARK: Class Functions + + class func f() -> Foo { + Foo() + } + + // MARK: Functions + + func g() -> Int { + 10 + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), + exclude: [.blankLinesAtEndOfScope, .blankLinesAtStartOfScope, .redundantType, .redundantClosure] + ) + } + + func testOrganizeEnumCasesFirst() { + let input = """ + enum Foo { + init?(rawValue: String) { + return nil + } + + case bar + case baz + case quux + } + """ + + let output = """ + enum Foo { + case bar + case baz + case quux + + // MARK: Lifecycle + + init?(rawValue: String) { + return nil + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtEndOfScope, .unusedArguments] + ) + } + + func testPlacingCustomDeclarationsBeforeMarks() { + let input = """ + struct Foo { + + public init() {} + + public typealias Bar = Int + + public struct Baz {} + + } + """ + + let output = """ + struct Foo { + + public typealias Bar = Int + + public struct Baz {} + + // MARK: Lifecycle + + public init() {} + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(beforeMarks: ["typealias", "struct"]), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testCustomLifecycleMethods() { + let input = """ + class ViewController: UIViewController { + + public init() { + super.init(nibName: nil, bundle: nil) + } + + func viewDidLoad() { + super.viewDidLoad() + } + + func internalInstanceMethod() {} + + func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + } + """ + + let output = """ + class ViewController: UIViewController { + + // MARK: Lifecycle + + public init() { + super.init(nibName: nil, bundle: nil) + } + + func viewDidLoad() { + super.viewDidLoad() + } + + func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + // MARK: Internal + + func internalInstanceMethod() {} + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(lifecycleMethods: ["viewDidLoad", "viewWillAppear", "viewDidAppear"]), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testCustomCategoryMarkTemplate() { + let input = """ + struct Foo { + public init() {} + public func publicInstanceMethod() {} + } + """ + + let output = """ + struct Foo { + + // - Lifecycle + + public init() {} + + // - Public + + public func publicInstanceMethod() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(categoryMarkComment: "- %c"), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testBelowCustomStructOrganizationThreshold() { + let input = """ + struct StructBelowThreshold { + init() {} + } + """ + + testFormatting( + for: input, + rule: .organizeDeclarations, + options: FormatOptions(organizeStructThreshold: 2) + ) + } + + func testAboveCustomStructOrganizationThreshold() { + let input = """ + struct StructAboveThreshold { + init() {} + public func instanceMethod() {} + } + """ + + let output = """ + struct StructAboveThreshold { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public func instanceMethod() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(organizeStructThreshold: 2), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testCustomClassOrganizationThreshold() { + let input = """ + class ClassBelowThreshold { + init() {} + } + """ + + testFormatting( + for: input, + rule: .organizeDeclarations, + options: FormatOptions(organizeClassThreshold: 2) + ) + } + + func testCustomEnumOrganizationThreshold() { + let input = """ + enum EnumBelowThreshold { + case enumCase + } + """ + + testFormatting( + for: input, + rule: .organizeDeclarations, + options: FormatOptions(organizeEnumThreshold: 2) + ) + } + + func testBelowCustomExtensionOrganizationThreshold() { + let input = """ + extension FooBelowThreshold { + func bar() {} + } + """ + + testFormatting( + for: input, + rule: .organizeDeclarations, + options: FormatOptions( + organizeTypes: ["class", "struct", "enum", "extension"], + organizeExtensionThreshold: 2 + ) + ) + } + + func testAboveCustomExtensionOrganizationThreshold() { + let input = """ + extension FooBelowThreshold { + public func bar() {} + func baz() {} + private func quux() {} + } + """ + + let output = """ + extension FooBelowThreshold { + + // MARK: Public + + public func bar() {} + + // MARK: Internal + + func baz() {} + + // MARK: Private + + private func quux() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizeTypes: ["class", "struct", "enum", "extension"], + organizeExtensionThreshold: 2 + ), exclude: [.blankLinesAtStartOfScope] + ) + } + + func testPreservesExistingMarks() { + let input = """ + actor Foo { + + // MARK: Lifecycle + + init(json: JSONObject) throws { + bar = try json.value(for: "bar") + baz = try json.value(for: "baz") + } + + // MARK: Internal + + let bar: String + let baz: Int? + } + """ + testFormatting(for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope]) + } + + func testUpdatesMalformedMarks() { + let input = """ + actor Foo { + + // MARK: lifecycle + + // MARK: Lifeycle + + init() {} + + // Public + + // - Public + + public func bar() {} + + // MARK: - Internal + + func baz() {} + + // mrak: privat + + // Pulse + + private func quux() {} + } + """ + + let output = """ + actor Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public func bar() {} + + // MARK: Internal + + func baz() {} + + // MARK: Private + + // Pulse + + private func quux() {} + } + """ + + testFormatting(for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope]) + } + + func testDoesntAttemptToUpdateMarksNotAtTopLevel() { + let input = """ + class Foo { + + // MARK: Lifecycle + + public init() { + foo = ["foo"] + } + + // Comment at bottom of lifecycle category + + // MARK: Private + + @annotation // Private + /// Private + private var foo: [String] = [] + + private func bar() { + // Private + guard let baz = bar else { + return + } + } + } + """ + + testFormatting(for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .docCommentsBeforeAttributes]) + } + + func testHandlesTrailingCommentCorrectly() { + let input = """ + class Foo { + var bar = "bar" + /// Leading comment + public var baz = "baz" // Trailing comment + var quux = "quux" + } + """ + + let output = """ + class Foo { + + // MARK: Public + + /// Leading comment + public var baz = "baz" // Trailing comment + + // MARK: Internal + + var bar = "bar" + var quux = "quux" + } + """ + + testFormatting(for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope]) + } + + func testDoesntInsertMarkWhenOnlyOneCategory() { + let input = """ + class Foo { + var bar: Int + var baz: Int + func instanceMethod() {} + } + """ + + let output = """ + class Foo { + var bar: Int + var baz: Int + + func instanceMethod() {} + } + """ + + testFormatting(for: input, output, rule: .organizeDeclarations) + } + + func testOrganizesTypesWithinConditionalCompilationBlock() { + let input = """ + #if DEBUG + struct DebugFoo { + init() {} + public func instanceMethod() {} + } + #else + struct ProductionFoo { + init() {} + public func instanceMethod() {} + } + #endif + """ + + let output = """ + #if DEBUG + struct DebugFoo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public func instanceMethod() {} + } + #else + struct ProductionFoo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public func instanceMethod() {} + } + #endif + """ + + testFormatting(for: input, output, rule: .organizeDeclarations, + options: FormatOptions(ifdefIndent: .noIndent), + exclude: [.blankLinesAtStartOfScope]) + } + + func testOrganizesTypesBelowConditionalCompilationBlock() { + let input = """ + #if canImport(UIKit) + import UIKit + #endif + + struct Foo { + init() {} + public func instanceMethod() {} + } + """ + + let output = """ + #if canImport(UIKit) + import UIKit + #endif + + struct Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public func instanceMethod() {} + } + """ + + testFormatting(for: input, output, rule: .organizeDeclarations, + options: FormatOptions(ifdefIndent: .noIndent), + exclude: [.blankLinesAtStartOfScope]) + } + + func testOrganizesNestedTypesWithinConditionalCompilationBlock() { + let input = """ + public struct Foo { + + public var bar = "bar" + var baz = "baz" + + #if DEBUG + public struct DebugFoo { + init() {} + var debugBar = "debug" + } + + static let debugFoo = DebugFoo() + + private let other = "other" + #endif + + init() {} + + var quuz = "quux" + + #if DEBUG + struct Test { + let foo: Bar + } + #endif + } + """ + + let output = """ + public struct Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + #if DEBUG + public struct DebugFoo { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + var debugBar = "debug" + } + + static let debugFoo = DebugFoo() + + private let other = "other" + #endif + + public var bar = "bar" + + // MARK: Internal + + #if DEBUG + struct Test { + let foo: Bar + } + #endif + + var baz = "baz" + + var quuz = "quux" + + } + """ + + testFormatting(for: input, output, rule: .organizeDeclarations, + options: FormatOptions(ifdefIndent: .noIndent), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .propertyType]) + } + + func testOrganizesTypeBelowSymbolImport() { + let input = """ + import protocol SomeModule.SomeProtocol + import class SomeModule.SomeClass + import enum SomeModule.SomeEnum + import struct SomeModule.SomeStruct + import typealias SomeModule.SomeTypealias + import let SomeModule.SomeGlobalConstant + import var SomeModule.SomeGlobalVariable + import func SomeModule.SomeFunc + + struct Foo { + init() {} + public func instanceMethod() {} + } + """ + + let output = """ + import protocol SomeModule.SomeProtocol + import class SomeModule.SomeClass + import enum SomeModule.SomeEnum + import struct SomeModule.SomeStruct + import typealias SomeModule.SomeTypealias + import let SomeModule.SomeGlobalConstant + import var SomeModule.SomeGlobalVariable + import func SomeModule.SomeFunc + + struct Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public func instanceMethod() {} + } + """ + + testFormatting( + for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .sortImports] + ) + } + + func testDoesntBreakStructSynthesizedMemberwiseInitializer() { + let input = """ + struct Foo { + var bar: Int { + didSet {} + } + + var baz: Int + public let quux: Int + } + + Foo(bar: 1, baz: 2, quux: 3) + """ + + testFormatting(for: input, rule: .organizeDeclarations) + } + + func testOrganizesStructPropertiesThatDontBreakMemberwiseInitializer() { + let input = """ + struct Foo { + var computed: String { + let didSet = "didSet" + let willSet = "willSet" + return didSet + willSet + } + + private func instanceMethod() {} + public let bar: Int + var baz: Int + var quux: Int { + didSet {} + } + } + + Foo(bar: 1, baz: 2, quux: 3) + """ + + let output = """ + struct Foo { + + // MARK: Public + + public let bar: Int + + // MARK: Internal + + var baz: Int + + var computed: String { + let didSet = "didSet" + let willSet = "willSet" + return didSet + willSet + } + + var quux: Int { + didSet {} + } + + // MARK: Private + + private func instanceMethod() {} + } + + Foo(bar: 1, baz: 2, quux: 3) + """ + + testFormatting( + for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testPreservesCategoryMarksInStructWithIncorrectSubcategoryOrdering() { + let input = """ + struct Foo { + + // MARK: Public + + public let quux: Int + + // MARK: Internal + + var bar: Int { + didSet {} + } + + var baz: Int + } + + Foo(bar: 1, baz: 2, quux: 3) + """ + + testFormatting( + for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testPreservesCommentsAtBottomOfCategory() { + let input = """ + struct Foo { + + // MARK: Lifecycle + + init() {} + + // Important comment at end of section! + + // MARK: Public + + public let bar = 1 + } + """ + + testFormatting( + for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testPreservesCommentsAtBottomOfCategoryWhenReorganizing() { + let input = """ + struct Foo { + + // MARK: Lifecycle + + init() {} + + // Important comment at end of section! + + // MARK: Internal + + // Important comment at start of section! + + var baz = 1 + + public let bar = 1 + } + """ + + let output = """ + struct Foo { + + // MARK: Lifecycle + + init() {} + + // Important comment at end of section! + + // MARK: Public + + public let bar = 1 + + // MARK: Internal + + // Important comment at start of section! + + var baz = 1 + + } + """ + + testFormatting( + for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testDoesntRemoveCategorySeparatorsFromBodyNotBeingOrganized() { + let input = """ + struct Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + public var bar = 10 + } + + extension Foo { + + // MARK: Public + + public var baz: Int { 20 } + + // MARK: Internal + + var quux: Int { 30 } + } + """ + + testFormatting( + for: input, rule: .organizeDeclarations, + options: FormatOptions(organizeStructThreshold: 20), + exclude: [.blankLinesAtStartOfScope] + ) + } + + func testParsesPropertiesWithBodies() { + let input = """ + class Foo { + // Instance properties without bodies: + + let propertyWithoutBody1 = 10 + + let propertyWithoutBody2: String = { + "bar" + }() + + let propertyWithoutBody3: () -> String = { + "bar" + } + + // Instance properties with bodies: + + var withBody1: String { + "bar" + } + + var withBody2: String { + didSet { print("didSet") } + } + + var withBody3: String = "bar" { + didSet { print("didSet") } + } + + var withBody4: String = "bar" { + didSet { print("didSet") } + } + + var withBody5: () -> String = { "bar" } { + didSet { print("didSet") } + } + + var withBody6: String = { "bar" }() { + didSet { print("didSet") } + } + } + """ + + testFormatting(for: input, rule: .organizeDeclarations, exclude: [.redundantClosure]) + } + + func testFuncWithNestedInitNotTreatedAsLifecycle() { + let input = """ + struct Foo { + + // MARK: Public + + public func baz() {} + + // MARK: Internal + + func bar() { + class NestedClass { + init() {} + } + + // ... + } + } + """ + + testFormatting(for: input, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope]) + } + + func testOrganizeRuleNotConfusedByClassProtocol() { + let input = """ + protocol Foo: class { + func foo() + } + + class Bar { + // MARK: Fileprivate + + private var baz: Int + + // MARK: Private + + private let quux: String + } + """ + + let output = """ + protocol Foo: class { + func foo() + } + + class Bar { + private var baz: Int + + private let quux: String + } + """ + + testFormatting(for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope]) + } + + func testOrganizeClassDeclarationsIntoCategoriesWithNoBlankLineAfterMark() { + let input = """ + class Foo { + private func privateMethod() {} + + private let bar = 1 + public let baz = 1 + open var quack = 2 + var quux = 2 + + init() {} + + /// Doc comment + public func publicMethod() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + init() {} + + // MARK: Open + open var quack = 2 + + // MARK: Public + public let baz = 1 + + /// Doc comment + public func publicMethod() {} + + // MARK: Internal + var quux = 2 + + // MARK: Private + private let bar = 1 + + private func privateMethod() {} + + } + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: options, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testOrganizeWithNoCategoryMarks_noSpacesBetweenDeclarations() { + let input = """ + class Foo { + private func privateMethod() {} + private let bar = 1 + public let baz = 1 + } + """ + + let output = """ + class Foo { + public let baz = 1 + + private let bar = 1 + + private func privateMethod() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(markCategories: false) + ) + } + + func testOrganizeWithNoCategoryMarks_withSpacesBetweenDeclarations() { + let input = """ + class Foo { + private func privateMethod() {} + + private let bar = 1 + + public let baz = 1 + + private func anotherPrivateMethod() {} + } + """ + + let output = """ + class Foo { + public let baz = 1 + + private let bar = 1 + + private func privateMethod() {} + + private func anotherPrivateMethod() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(markCategories: false) + ) + } + + func testOrganizeConditionalInitDeclaration() { + let input = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + #if DEBUG + init() { + print("Debug") + } + #endif + + // MARK: Internal + + func test() {} + } + """ + + testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testOrganizeConditionalPublicFunction() { + let input = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Public + + #if DEBUG + public func publicTest() {} + #endif + + // MARK: Internal + + func internalTest() {} + } + """ + + testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testDoesntConflictWithOrganizeDeclarations() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + case upsellB + + // MARK: Internal + + var anUnsortedProperty: Foo { + Foo() + } + + var unsortedProperty: Foo { + Foo() + } + } + """ + + testFormatting(for: input, rule: .organizeDeclarations) + } + + func testSortsWithinOrganizeDeclarations() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() + } + + var sortedProperty: Foo { + Foo() + } + + } + """ + + testFormatting(for: input, [output], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + exclude: [.blankLinesAtEndOfScope]) + } + + func testSortsWithinOrganizeDeclarationsByClassName() { + let input = """ + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() + } + + var sortedProperty: Foo { + Foo() + } + + } + """ + + testFormatting(for: input, [output], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), + exclude: [.blankLinesAtEndOfScope]) + } + + func testSortsWithinOrganizeDeclarationsByPartialClassName() { + let input = """ + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() + } + + var sortedProperty: Foo { + Foo() + } + + } + """ + + testFormatting(for: input, [output], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), + exclude: [.blankLinesAtEndOfScope]) + } + + func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() { + let input = """ + /// Comment + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + testFormatting(for: input, + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), + exclude: [.blankLinesAtEndOfScope]) + } + + func testOrganizeDeclarationsSortUsesLocalizedCompare() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsella + case upsellA + case upsellb + case upsellB + } + """ + + testFormatting(for: input, rule: .organizeDeclarations) + } + + func testSortDeclarationsSortsExtensionBody() { + let input = """ + enum Namespace {} + + // swiftformat:sort + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let output = """ + enum Namespace {} + + // swiftformat:sort + extension Namespace { + static let baaz = "baaz" + public static let bar = "bar" + static let foo = "foo" + } + """ + + // organizeTypes doesn't include "extension". So even though the + // organizeDeclarations rule is enabled, the extension should be + // sorted by the sortDeclarations rule. + let options = FormatOptions(organizeTypes: ["class"]) + testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations], options: options) + } + + func testOrganizeDeclarationsSortsExtensionBody() { + let input = """ + enum Namespace {} + + // swiftformat:sort + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let output = """ + enum Namespace {} + + // swiftformat:sort + extension Namespace { + + // MARK: Public + + public static let bar = "bar" + + // MARK: Internal + + static let baaz = "baaz" + static let foo = "foo" + } + """ + + let options = FormatOptions(organizeTypes: ["extension"]) + testFormatting(for: input, output, rule: .organizeDeclarations, options: options, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testOrganizeDeclarationsContainingNonisolated() { + let input = """ + class Test { + public static func test1() {} + + private nonisolated(unsafe) static var test3: (( + _ arg1: Bool, + _ arg2: Int + ) -> Bool)? + + static func test2() {} + } + """ + let output = """ + class Test { + + // MARK: Public + + public static func test1() {} + + // MARK: Internal + + static func test2() {} + + // MARK: Private + + private nonisolated(unsafe) static var test3: (( + _ arg1: Bool, + _ arg2: Int + ) -> Bool)? + + } + """ + testFormatting(for: input, output, rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testSortStructPropertiesWithAttributes() { + let input = """ + // swiftformat:sort + struct BookReaderView { + @Namespace private var animation + @State private var animationContent: Bool = false + @State private var offsetY: CGFloat = 0 + @Bindable var model: Book + @Query( + filter: #Predicate { $0.progress_ < 1 }, + sort: \\.updatedAt_, + order: .reverse + ) private var incompleteTextContents: [TextContent] + } + """ + let output = """ + // swiftformat:sort + struct BookReaderView { + + // MARK: Internal + + @Bindable var model: Book + + // MARK: Private + + @Namespace private var animation + @State private var animationContent: Bool = false + @Query( + filter: #Predicate { $0.progress_ < 1 }, + sort: \\.updatedAt_, + order: .reverse + ) private var incompleteTextContents: [TextContent] + @State private var offsetY: CGFloat = 0 + } + """ + let options = FormatOptions(indent: " ", organizeTypes: ["struct"]) + testFormatting(for: input, output, rule: .organizeDeclarations, + options: options, exclude: [.blankLinesAtStartOfScope]) + } + + func testSortSingleSwiftUIPropertyWrapper() { + let input = """ + struct ContentView: View { + + private var label: String + + @State + private var isOn: Bool = false + + private var foo = true + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @State + private var isOn: Bool = false + + private var label: String + + private var foo = true + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testSortMultipleSwiftUIPropertyWrappers() { + let input = """ + struct ContentView: View { + + let foo: Foo + @State var bar: Bar + let baaz: Baaz + @State var quux: Quux + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @State var bar: Bar + @State var quux: Quux + + let foo: Foo + let baaz: Baaz + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testSortSwiftUIPropertyWrappersWithDifferentVisibility() { + let input = """ + struct ContentView: View { + + let foo: Foo + @State private var bar: Bar + private let baaz: Baaz + @Binding var isOn: Bool + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @Binding var isOn: Bool + + let foo: Foo + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @State private var bar: Bar + + private let baaz: Baaz + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testSortSwiftUIPropertyWrappersWithArguments() { + let input = """ + struct ContentView: View { + + let foo: Foo + @Environment(\\.colorScheme) var colorScheme + let baaz: Baaz + @Environment(\\.quux) let quux: Quux + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) let quux: Quux + + let foo: Foo + let baaz: Baaz + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testDoesntAddUnexpectedBlankLinesDueToBlankLinesWithSpaces() { + // The blank lines in this input code are indented with four spaces. + // Done using string interpolation in the input code to make this + // more clear, and to prevent the spaces from being removed automatically. + let input = """ + public class TestClass { + var variable01 = 1 + var variable02 = 2 + var variable03 = 3 + var variable04 = 4 + var variable05 = 5 + \(" ") + public func foo() {} + \(" ") + func bar() {} + \(" ") + private func baz() {} + } + """ + + let output = """ + public class TestClass { + + // MARK: Public + + public func foo() {} + \(" ") + // MARK: Internal + + var variable01 = 1 + var variable02 = 2 + var variable03 = 3 + var variable04 = 4 + var variable05 = 5 + \(" ") + func bar() {} + \(" ") + // MARK: Private + + private func baz() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .consecutiveBlankLines, .trailingSpace, .consecutiveSpaces, .indent] + ) + } +} diff --git a/Tests/Rules/PreferForLoopTests.swift b/Tests/Rules/PreferForLoopTests.swift new file mode 100644 index 00000000..f7ef651a --- /dev/null +++ b/Tests/Rules/PreferForLoopTests.swift @@ -0,0 +1,409 @@ +// +// PreferForLoopTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 8/12/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class PreferForLoopTests: XCTestCase { + func testConvertSimpleForEachToForLoop() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { string in + print(string) + } + + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { (string: String) in + print(string) + } + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for string in placeholderStrings { + print(string) + } + + let placeholderStrings = ["foo", "bar", "baaz"] + for string in placeholderStrings { + print(string) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testConvertAnonymousForEachToForLoop() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + print($0) + } + + potatoes.forEach({ $0.bake() }) + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for placeholderString in placeholderStrings { + print(placeholderString) + } + + potatoes.forEach({ $0.bake() }) + """ + + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.trailingClosures]) + } + + func testNoConvertAnonymousForEachToForLoop() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + print($0) + } + + potatoes.forEach({ $0.bake() }) + """ + + let options = FormatOptions(preserveAnonymousForEach: true, preserveSingleLineForEach: false) + testFormatting(for: input, rule: .preferForLoop, options: options, exclude: [.trailingClosures]) + } + + func testConvertSingleLineForEachToForLoop() { + let input = "potatoes.forEach({ item in item.bake() })" + let output = "for item in potatoes { item.bake() }" + + let options = FormatOptions(preserveSingleLineForEach: false) + testFormatting(for: input, output, rule: .preferForLoop, options: options, + exclude: [.wrapLoopBodies]) + } + + func testConvertSingleLineAnonymousForEachToForLoop() { + let input = "potatoes.forEach({ $0.bake() })" + let output = "for potato in potatoes { potato.bake() }" + + let options = FormatOptions(preserveSingleLineForEach: false) + testFormatting(for: input, output, rule: .preferForLoop, options: options, + exclude: [.wrapLoopBodies]) + } + + func testConvertNestedForEach() { + let input = """ + let nestedArrays = [[1, 2], [3, 4]] + nestedArrays.forEach { + $0.forEach { + $0.forEach { + print($0) + } + } + } + """ + + let output = """ + let nestedArrays = [[1, 2], [3, 4]] + for nestedArray in nestedArrays { + for item in nestedArray { + for item in item { + print(item) + } + } + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testDefaultNameAlreadyUsedInLoopBody() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + let placeholderString = $0.uppercased() + print(placeholderString, $0) + } + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for item in placeholderStrings { + let placeholderString = item.uppercased() + print(placeholderString, item) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testIgnoreLoopsWithCaptureListForNow() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { [someCapturedValue = fooBar] in + print($0, someCapturedValue) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testRemoveAllPrefixFromLoopIdentifier() { + let input = """ + allWindows.forEach { + print($0) + } + """ + + let output = """ + for window in allWindows { + print(window) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testConvertsReturnToContinue() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + func capitalize(_ value: String) -> String { + return value.uppercased() + } + + if $0 == "foo" { + return + } else { + print(capitalize($0)) + } + } + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for placeholderString in placeholderStrings { + func capitalize(_ value: String) -> String { + return value.uppercased() + } + + if placeholderString == "foo" { + continue + } else { + print(capitalize(placeholderString)) + } + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnChainedProperties() { + let input = """ + let bar = foo.bar + bar.baaz.quux.strings.forEach { + print($0) + } + """ + + let output = """ + let bar = foo.bar + for string in bar.baaz.quux.strings { + print(string) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnFunctionCallResult() { + let input = """ + let bar = foo.bar + foo.item().bar[2].baazValues(option: true).forEach { + print($0) + } + """ + + let output = """ + let bar = foo.bar + for baazValue in foo.item().bar[2].baazValues(option: true) { + print(baazValue) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnSubscriptResult() { + let input = """ + let bar = foo.bar + foo.item().bar[2].dictionary["myValue"].forEach { + print($0) + } + """ + + let output = """ + let bar = foo.bar + for item in foo.item().bar[2].dictionary["myValue"] { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnArrayLiteral() { + let input = """ + let quux = foo.bar.baaz.quux + ["foo", "bar", "baaz", quux].forEach { + print($0) + } + """ + + let output = """ + let quux = foo.bar.baaz.quux + for item in ["foo", "bar", "baaz", quux] { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnCurriedFunctionWithSubscript() { + let input = """ + let quux = foo.bar.baaz.quux + foo(bar)(baaz)["item"].forEach { + print($0) + } + """ + + let output = """ + let quux = foo.bar.baaz.quux + for item in foo(bar)(baaz)["item"] { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnArrayLiteralInParens() { + let input = """ + let quux = foo.bar.baaz.quux + (["foo", "bar", "baaz", quux]).forEach { + print($0) + } + """ + + let output = """ + let quux = foo.bar.baaz.quux + for item in (["foo", "bar", "baaz", quux]) { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.redundantParens]) + } + + func testPreservesForEachAfterMultilineChain() { + let input = """ + placeholderStrings + .filter { $0.style == .fooBar } + .map { $0.uppercased() } + .forEach { print($0) } + + placeholderStrings + .filter({ $0.style == .fooBar }) + .map({ $0.uppercased() }) + .forEach({ print($0) }) + """ + testFormatting(for: input, rule: .preferForLoop, exclude: [.trailingClosures]) + } + + func testPreservesChainWithClosure() { + let input = """ + // Converting this to a for loop would result in unusual looking syntax like + // `for string in strings.map { $0.uppercased() } { print($0) }` + // which causes a warning to be emitted: "trailing closure in this context is + // confusable with the body of the statement; pass as a parenthesized argument + // to silence this warning". + strings.map { $0.uppercased() }.forEach { print($0) } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testForLoopVariableNotUsedIfClashesWithKeyword() { + let input = """ + Foo.allCases.forEach { + print($0) + } + """ + let output = """ + for item in Foo.allCases { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testTryNotRemovedInThrowingForEach() { + let input = """ + try list().forEach { + print($0) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testOptionalTryNotRemovedInThrowingForEach() { + let input = """ + try? list().forEach { + print($0) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testAwaitNotRemovedInAsyncForEach() { + let input = """ + await list().forEach { + print($0) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testForEachOverDictionary() { + let input = """ + let dict = ["a": "b"] + + dict.forEach { (header: (key: String, value: String)) in + print(header.key) + print(header.value) + } + """ + + let output = """ + let dict = ["a": "b"] + + for header in dict { + print(header.key) + print(header.value) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testConvertsForEachWithGuardElseReturn() { + let input = """ + strings.forEach { string in + guard !string.isEmpty else { return } + print(string) + } + """ + + let output = """ + for string in strings { + guard !string.isEmpty else { continue } + print(string) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.wrapConditionalBodies]) + } +} diff --git a/Tests/Rules/PreferKeyPathTests.swift b/Tests/Rules/PreferKeyPathTests.swift new file mode 100644 index 00000000..54abad1e --- /dev/null +++ b/Tests/Rules/PreferKeyPathTests.swift @@ -0,0 +1,113 @@ +// +// PreferKeyPathTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 7/29/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class PreferKeyPathTests: XCTestCase { + func testMapPropertyToKeyPath() { + let input = "let foo = bar.map { $0.foo }" + let output = "let foo = bar.map(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testCompactMapPropertyToKeyPath() { + let input = "let foo = bar.compactMap { $0.foo }" + let output = "let foo = bar.compactMap(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testFlatMapPropertyToKeyPath() { + let input = "let foo = bar.flatMap { $0.foo }" + let output = "let foo = bar.flatMap(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testMapNestedPropertyWithSpacesToKeyPath() { + let input = "let foo = bar.map { $0 . foo . bar }" + let output = "let foo = bar.map(\\ . foo . bar)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options, exclude: [.spaceAroundOperators]) + } + + func testMultilineMapPropertyToKeyPath() { + let input = """ + let foo = bar.map { + $0.foo + } + """ + let output = "let foo = bar.map(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testParenthesizedMapPropertyToKeyPath() { + let input = "let foo = bar.map({ $0.foo })" + let output = "let foo = bar.map(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testNoMapSelfToKeyPath() { + let input = "let foo = bar.map { $0 }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForSwiftLessThan5_2() { + let input = "let foo = bar.map { $0.foo }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForFunctionCalls() { + let input = "let foo = bar.map { $0.foo() }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForCompoundExpressions() { + let input = "let foo = bar.map { $0.foo || baz }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForOptionalChaining() { + let input = "let foo = bar.map { $0?.foo }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForTrailingContains() { + let input = "let foo = bar.contains { $0.foo }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testMapPropertyToKeyPathForContainsWhere() { + let input = "let foo = bar.contains(where: { $0.foo })" + let output = "let foo = bar.contains(where: \\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, options: options) + } + + func testMultipleTrailingClosuresNotConvertedToKeyPath() { + let input = "foo.map { $0.bar } reverse: { $0.bar }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } +} diff --git a/Tests/Rules/PropertyTypeTests.swift b/Tests/Rules/PropertyTypeTests.swift new file mode 100644 index 00000000..851a3874 --- /dev/null +++ b/Tests/Rules/PropertyTypeTests.swift @@ -0,0 +1,560 @@ +// +// PropertyTypeTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 3/29/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class PropertyTypeTests: XCTestCase { + func testConvertsExplicitTypeToInferredType() { + let input = """ + let foo: Foo = .init() + let bar: Bar = .staticBar + let baaz: Baaz = .Example.default + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + """ + + let output = """ + let foo = Foo() + let bar = Bar.staticBar + let baaz = Baaz.Example.default + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testConvertsInferredTypeToExplicitType() { + let input = """ + let foo = Foo() + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + """ + + let output = """ + let foo: Foo = .init() + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testConvertsTypeMembersToExplicitType() { + let input = """ + struct Foo { + let foo = Foo() + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + } + """ + + let output = """ + struct Foo { + let foo: Foo = .init() + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testConvertsLocalsToImplicitType() { + let input = """ + struct Foo { + let foo = Foo() + + func bar() { + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + } + } + """ + + let output = """ + struct Foo { + let foo: Foo = .init() + + func bar() { + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testPreservesInferredTypeFollowingTypeWithDots() { + let input = """ + let baaz = Baaz.Example.default + let color = Color.Theme.default + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesExplicitTypeIfNoRHS() { + let input = """ + let foo: Foo + let bar: Bar + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesImplicitTypeIfNoRHSType() { + let input = """ + let foo = foo() + let bar = bar + let int = 24 + let array = ["string"] + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesImplicitForVoidAndTuples() { + let input = """ + let foo = Void() + let foo = (foo: "foo", bar: "bar").foo + let foo = ["bar", "baz"].quux(quuz) + let foo = [bar].first + let foo = [bar, baaz].first + let foo = ["foo": "bar"].first + let foo = [foo: bar].first + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options, exclude: [.void]) + } + + func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { + let input = """ + let foo: Foo = localFoo + let bar: Bar = localBar + let int: Int64 = 1234 + let number: CGFloat = 12.345 + let array: [String] = [] + let dictionary: [String: Int] = [:] + let tuple: (String, Int) = ("foo", 123) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options, exclude: [.redundantType]) + } + + func testCompatibleWithRedundantTypeInferred() { + let input = """ + let foo: Foo = Foo() + """ + + let output = """ + let foo = Foo() + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) + } + + func testCompatibleWithRedundantTypeExplicit() { + let input = """ + let foo: Foo = Foo() + """ + + let output = """ + let foo: Foo = .init() + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) + } + + func testCompatibleWithRedundantTypeInferLocalsOnly() { + let input = """ + let foo: Foo = Foo.init() + let foo: Foo = .init() + + func bar() { + let baaz: Baaz = Baaz.init() + let baaz: Baaz = .init() + } + """ + + let output = """ + let foo: Foo = .init() + let foo: Foo = .init() + + func bar() { + let baaz = Baaz() + let baaz = Baaz() + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType, .redundantInit], options: options) + } + + func testPropertyTypeWithIfExpressionDisabledByDefault() { + let input = """ + let foo: SomeTypeWithALongGenrericName = + if condition { + .init(bar) + } else { + .init(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPropertyTypeWithIfExpression() { + let input = """ + let foo: Foo = + if condition { + .init(bar) + } else { + .init(baaz) + } + """ + + let output = """ + let foo = + if condition { + Foo(bar) + } else { + Foo(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testPropertyTypeWithSwitchExpression() { + let input = """ + let foo: Foo = + switch condition { + case true: + .init(bar) + case false: + .init(baaz) + } + """ + + let output = """ + let foo = + switch condition { + case true: + Foo(bar) + case false: + Foo(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testPreservesNonMatchingIfExpression() { + let input = """ + let foo: Foo = + if condition { + .init(bar) + } else { + [] // e.g. using ExpressibleByArrayLiteral + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesExplicitOptionalType() { + // `let foo = Foo?.foo` doesn't work if `.foo` is defined on `Foo` but not `Foo?` + let input = """ + let optionalFoo1: Foo? = .foo + let optionalFoo2: Foo? = Foo.foo + let optionalFoo3: Foo! = .foo + let optionalFoo4: Foo! = Foo.foo + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesTypeWithSeparateDeclarationAndProperty() { + let input = """ + var foo: Foo! + foo = Foo(afterDelay: { + print(foo) + }) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesTypeWithExistentialAny() { + let input = """ + protocol ShapeStyle {} + struct MyShapeStyle: ShapeStyle {} + + extension ShapeStyle where Self == MyShapeStyle { + static var myShape: MyShapeStyle { MyShapeStyle() } + } + + /// This compiles + let myShape1: any ShapeStyle = .myShape + + // This would fail with "error: static member 'myShape' cannot be used on protocol metatype '(any ShapeStyle).Type'" + // let myShape2 = (any ShapeStyle).myShape + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesExplicitRightHandSideWithOperator() { + let input = """ + let value: ClosedRange = .zero ... 10 + let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge + let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() + let dynamicTypeSizeRange: ClosedRange = .convertFromLiteral(.large ... .xxxLarge) + """ + + let output = """ + let value: ClosedRange = .zero ... 10 + let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge + let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() + let dynamicTypeSizeRange = ClosedRange.convertFromLiteral(.large ... .xxxLarge) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testPreservesInferredRightHandSideWithOperators() { + let input = """ + let foo = Foo().bar + let foo = Foo.bar.baaz.quux + let foo = Foo.bar ... baaz + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesUserProvidedSymbolTypes() { + let input = """ + class Foo { + let foo = Foo() + let bar = Bar() + + func bar() { + let foo: Foo = .foo + let bar: Bar = .bar + let baaz: Baaz = .baaz + let quux: Quux = .quux + } + } + """ + + let output = """ + class Foo { + let foo = Foo() + let bar: Bar = .init() + + func bar() { + let foo: Foo = .foo + let bar = Bar.bar + let baaz: Baaz = .baaz + let quux: Quux = .quux + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["Foo", "Baaz", "quux"]) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testPreserveInitIfExplicitlyExcluded() { + let input = """ + class Foo { + let foo = Foo() + let bar = Bar.init() + let baaz = Baaz.baaz() + + func bar() { + let foo: Foo = .init() + let bar: Bar = .init() + let baaz: Baaz = .baaz() + } + } + """ + + let output = """ + class Foo { + let foo = Foo() + let bar = Bar.init() + let baaz: Baaz = .baaz() + + func bar() { + let foo: Foo = .init() + let bar: Bar = .init() + let baaz = Baaz.baaz() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) + testFormatting(for: input, output, rule: .propertyType, options: options, exclude: [.redundantInit]) + } + + func testClosureBodyIsConsideredLocal() { + let input = """ + foo { + let bar = Bar() + let baaz: Baaz = .init() + } + + foo(bar: bar, baaz: baaz, quux: { + let bar = Bar() + let baaz: Baaz = .init() + }) + + foo { + let bar = Bar() + let baaz: Baaz = .init() + } bar: { + let bar = Bar() + let baaz: Baaz = .init() + } + + class Foo { + let foo = Foo.bar { + let baaz = Baaz() + let baaz: Baaz = .init() + } + } + """ + + let output = """ + foo { + let bar = Bar() + let baaz = Baaz() + } + + foo(bar: bar, baaz: baaz, quux: { + let bar = Bar() + let baaz = Baaz() + }) + + foo { + let bar = Bar() + let baaz = Baaz() + } bar: { + let bar = Bar() + let baaz = Baaz() + } + + class Foo { + let foo: Foo = .bar { + let baaz = Baaz() + let baaz = Baaz() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testIfGuardConditionsPreserved() { + let input = """ + if let foo = Foo(bar) { + let foo = Foo(bar) + } else if let foo = Foo(bar) { + let foo = Foo(bar) + } else { + let foo = Foo(bar) + } + + guard let foo = Foo(bar) else { + return + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPropertyObserversConsideredLocal() { + let input = """ + class Foo { + var foo: Foo { + get { + let foo = Foo(bar) + } + set { + let foo = Foo(bar) + } + willSet { + let foo = Foo(bar) + } + didSet { + let foo = Foo(bar) + } + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: .propertyType, options: options) + } +} diff --git a/Tests/Rules/RedundantBackticksTests.swift b/Tests/Rules/RedundantBackticksTests.swift new file mode 100644 index 00000000..fa5930b7 --- /dev/null +++ b/Tests/Rules/RedundantBackticksTests.swift @@ -0,0 +1,214 @@ +// +// RedundantBackticksTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/7/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantBackticksTests: XCTestCase { + func testRemoveRedundantBackticksInLet() { + let input = "let `foo` = bar" + let output = "let foo = bar" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundKeyword() { + let input = "let `let` = foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundSelf() { + let input = "let `self` = foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundClassSelfInTypealias() { + let input = "typealias `Self` = Foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundClassSelfAsReturnType() { + let input = "func foo(bar: `Self`) { print(bar) }" + let output = "func foo(bar: Self) { print(bar) }" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundClassSelfAsParameterType() { + let input = "func foo() -> `Self` {}" + let output = "func foo() -> Self {}" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundClassSelfArgument() { + let input = "func foo(`Self`: Foo) { print(Self) }" + let output = "func foo(Self: Foo) { print(Self) }" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundKeywordFollowedByType() { + let input = "let `default`: Int = foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundContextualGet() { + let input = "var foo: Int {\n `get`()\n return 5\n}" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundGetArgument() { + let input = "func foo(`get` value: Int) { print(value) }" + let output = "func foo(get value: Int) { print(value) }" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundTypeAtRootLevel() { + let input = "enum `Type` {}" + let output = "enum Type {}" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundTypeInsideType() { + let input = "struct Foo {\n enum `Type` {}\n}" + testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) + } + + func testNoRemoveBackticksAroundLetArgument() { + let input = "func foo(`let`: Foo) { print(`let`) }" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundTrueArgument() { + let input = "func foo(`true`: Foo) { print(`true`) }" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundTrueArgument() { + let input = "func foo(`true`: Foo) { print(`true`) }" + let output = "func foo(true: Foo) { print(`true`) }" + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantBackticks, options: options) + } + + func testNoRemoveBackticksAroundTypeProperty() { + let input = "var type: Foo.`Type`" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundTypePropertyInsideType() { + let input = "struct Foo {\n enum `Type` {}\n}" + testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) + } + + func testNoRemoveBackticksAroundTrueProperty() { + let input = "var type = Foo.`true`" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundTrueProperty() { + let input = "var type = Foo.`true`" + let output = "var type = Foo.true" + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantBackticks, options: options, exclude: [.propertyType]) + } + + func testRemoveBackticksAroundProperty() { + let input = "var type = Foo.`bar`" + let output = "var type = Foo.bar" + testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) + } + + func testRemoveBackticksAroundKeywordProperty() { + let input = "var type = Foo.`default`" + let output = "var type = Foo.default" + testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) + } + + func testRemoveBackticksAroundKeypathProperty() { + let input = "var type = \\.`bar`" + let output = "var type = \\.bar" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundKeypathKeywordProperty() { + let input = "var type = \\.`default`" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundKeypathKeywordPropertyInSwift5() { + let input = "var type = \\.`default`" + let output = "var type = \\.default" + let options = FormatOptions(swiftVersion: "5") + testFormatting(for: input, output, rule: .redundantBackticks, options: options) + } + + func testNoRemoveBackticksAroundInitPropertyInSwift5() { + let input = "let foo: Foo = .`init`" + let options = FormatOptions(swiftVersion: "5") + testFormatting(for: input, rule: .redundantBackticks, options: options, exclude: [.propertyType]) + } + + func testNoRemoveBackticksAroundAnyProperty() { + let input = "enum Foo {\n case `Any`\n}" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundGetInSubscript() { + let input = """ + subscript(_ name: String) -> T where T: Equatable { + `get`(name) + } + """ + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundActorProperty() { + let input = "let `actor`: Foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundActorRvalue() { + let input = "let foo = `actor`" + let output = "let foo = actor" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundActorLabel() { + let input = "init(`actor`: Foo)" + let output = "init(actor: Foo)" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundActorLabel2() { + let input = "init(`actor` foo: Foo)" + let output = "init(actor foo: Foo)" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundUnderscore() { + let input = "func `_`(_ foo: T) -> T { foo }" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundShadowedSelf() { + let input = """ + struct Foo { + let `self`: URL + + func printURL() { + print("My URL is \\(self.`self`)") + } + } + """ + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, rule: .redundantBackticks, options: options) + } + + func testNoRemoveBackticksAroundDollar() { + let input = "@attached(peer, names: prefixed(`$`))" + testFormatting(for: input, rule: .redundantBackticks) + } +} diff --git a/Tests/Rules/RedundantBreakTests.swift b/Tests/Rules/RedundantBreakTests.swift new file mode 100644 index 00000000..1424cc81 --- /dev/null +++ b/Tests/Rules/RedundantBreakTests.swift @@ -0,0 +1,79 @@ +// +// RedundantBreakTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/23/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantBreakTests: XCTestCase { + func testRedundantBreaksRemoved() { + let input = """ + switch x { + case foo: + print("hello") + break + case bar: + print("world") + break + default: + print("goodbye") + break + } + """ + let output = """ + switch x { + case foo: + print("hello") + case bar: + print("world") + default: + print("goodbye") + } + """ + testFormatting(for: input, output, rule: .redundantBreak) + } + + func testBreakInEmptyCaseNotRemoved() { + let input = """ + switch x { + case foo: + break + case bar: + break + default: + break + } + """ + testFormatting(for: input, rule: .redundantBreak) + } + + func testConditionalBreakNotRemoved() { + let input = """ + switch x { + case foo: + if bar { + break + } + } + """ + testFormatting(for: input, rule: .redundantBreak) + } + + func testBreakAfterSemicolonNotMangled() { + let input = """ + switch foo { + case 1: print(1); break + } + """ + let output = """ + switch foo { + case 1: print(1); + } + """ + testFormatting(for: input, output, rule: .redundantBreak, exclude: [.semicolons]) + } +} diff --git a/Tests/Rules/RedundantClosureTests.swift b/Tests/Rules/RedundantClosureTests.swift new file mode 100644 index 00000000..0444d63c --- /dev/null +++ b/Tests/Rules/RedundantClosureTests.swift @@ -0,0 +1,1040 @@ +// +// RedundantClosureTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 9/28/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantClosureTests: XCTestCase { + func testClosureAroundConditionalAssignmentNotRedundantForExplicitReturn() { + let input = """ + let myEnum = MyEnum.a + let test: Int = { + switch myEnum { + case .a: + return 0 + case .b: + return 1 + } + }() + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options, + exclude: [.redundantReturn, .propertyType]) + } + + // MARK: redundantClosure + + func testRemoveRedundantClosureInSingleLinePropertyDeclaration() { + let input = """ + let foo = { "Foo" }() + let bar = { "Bar" }() + + let baaz = { "baaz" }() + + let quux = { "quux" }() + """ + + let output = """ + let foo = "Foo" + let bar = "Bar" + + let baaz = "baaz" + + let quux = "quux" + """ + + testFormatting(for: input, output, rule: .redundantClosure) + } + + func testRedundantClosureWithExplicitReturn() { + let input = """ + let foo = { return "Foo" }() + + let bar = { + return if Bool.random() { + "Bar" + } else { + "Baaz" + } + }() + """ + + let output = """ + let foo = "Foo" + + let bar = if Bool.random() { + "Bar" + } else { + "Baaz" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure], + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureWithExplicitReturn2() { + let input = """ + func foo() -> String { + methodCall() + return { return "Foo" }() + } + + func bar() -> String { + methodCall() + return { "Bar" }() + } + + func baaz() -> String { + { return "Baaz" }() + } + """ + + let output = """ + func foo() -> String { + methodCall() + return "Foo" + } + + func bar() -> String { + methodCall() + return "Bar" + } + + func baaz() -> String { + "Baaz" + } + """ + + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure]) + } + + func testKeepsClosureThatIsNotCalled() { + let input = """ + let foo = { "Foo" } + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsEmptyClosures() { + let input = """ + let foo = {}() + let bar = { /* comment */ }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRemoveRedundantClosureInMultiLinePropertyDeclaration() { + let input = """ + lazy var bar = { + Bar() + }() + """ + + let output = """ + lazy var bar = Bar() + """ + + testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) + } + + func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { + let input = #""" + lazy var bar = { + """ + Multiline string literal + """ + }() + """# + + let output = #""" + lazy var bar = """ + Multiline string literal + """ + """# + + testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) + } + + func testRemoveRedundantClosureInMultiLinePropertyDeclarationInClass() { + let input = """ + class Foo { + lazy var bar = { + return Bar(); + }() + } + """ + + let output = """ + class Foo { + lazy var bar = Bar() + } + """ + + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure, + .semicolons], exclude: [.propertyType]) + } + + func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { + let input = """ + lazy var baaz = { + Baaz( + foo: foo, + bar: bar) + }() + """ + + let output = """ + lazy var baaz = Baaz( + foo: foo, + bar: bar) + """ + + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, [output], + rules: [.redundantClosure, .wrapArguments], + options: options, exclude: [.propertyType]) + } + + func testRemoveRedundantClosureInWrappedPropertyDeclaration_afterFirst() { + let input = """ + lazy var baaz = { + Baaz(foo: foo, + bar: bar) + }() + """ + + let output = """ + lazy var baaz = Baaz(foo: foo, + bar: bar) + """ + + let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) + testFormatting(for: input, [output], + rules: [.redundantClosure, .wrapArguments], + options: options, exclude: [.propertyType]) + } + + func testRedundantClosureKeepsMultiStatementClosureThatSetsProperty() { + let input = """ + lazy var baaz = { + let baaz = Baaz(foo: foo, bar: bar) + baaz.foo = foo2 + return baaz + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureKeepsMultiStatementClosureWithMultipleStatements() { + let input = """ + lazy var quux = { + print("hello world") + return "quux" + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureKeepsClosureWithInToken() { + let input = """ + lazy var double = { () -> Double in + 100 + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureKeepsMultiStatementClosureOnSameLine() { + let input = """ + lazy var baaz = { + print("Foo"); return baaz + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureRemovesComplexMultilineClosure() { + let input = """ + lazy var closureInClosure = { + { + print("Foo") + print("Bar"); return baaz + } + }() + """ + + let output = """ + lazy var closureInClosure = { + print("Foo") + print("Bar"); return baaz + } + """ + + testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) + } + + func testKeepsClosureWithIfStatement() { + let input = """ + lazy var baaz = { + if let foo == foo { + return foo + } else { + return Foo() + } + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsClosureWithIfStatementOnSingleLine() { + let input = """ + lazy var baaz = { + if let foo == foo { return foo } else { return Foo() } + }() + """ + + testFormatting(for: input, rule: .redundantClosure, + exclude: [.wrapConditionalBodies]) + } + + func testRemovesClosureWithIfStatementInsideOtherClosure() { + let input = """ + lazy var baaz = { + { + if let foo == foo { + return foo + } else { + return Foo() + } + } + }() + """ + + let output = """ + lazy var baaz = { + if let foo == foo { + return foo + } else { + return Foo() + } + } + """ + + testFormatting(for: input, [output], + rules: [.redundantClosure, .indent]) + } + + func testKeepsClosureWithSwitchStatement() { + let input = """ + lazy var baaz = { + switch foo { + case let .some(foo): + return foo: + case .none: + return Foo() + } + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsClosureWithIfDirective() { + let input = """ + lazy var baaz = { + #if DEBUG + return DebugFoo() + #else + return Foo() + #endif + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsClosureThatCallsMethodThatReturnsNever() { + let input = """ + lazy var foo: String = { fatalError("no default value has been set") }() + lazy var bar: String = { return preconditionFailure("no default value has been set") }() + """ + + testFormatting(for: input, rule: .redundantClosure, + exclude: [.redundantReturn]) + } + + func testRemovesClosureThatHasNestedFatalError() { + let input = """ + lazy var foo = { + Foo(handle: { fatalError() }) + }() + """ + + let output = """ + lazy var foo = Foo(handle: { fatalError() }) + """ + + testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) + } + + func testPreservesClosureWithMultipleVoidMethodCalls() { + let input = """ + lazy var triggerSomething: Void = { + logger.trace("log some stuff before Triggering") + TriggerClass.triggerTheThing() + logger.trace("Finished triggering the thing") + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRemovesClosureWithMultipleNestedVoidMethodCalls() { + let input = """ + lazy var foo: Foo = { + Foo(handle: { + logger.trace("log some stuff before Triggering") + TriggerClass.triggerTheThing() + logger.trace("Finished triggering the thing") + }) + }() + """ + + let output = """ + lazy var foo: Foo = Foo(handle: { + logger.trace("log some stuff before Triggering") + TriggerClass.triggerTheThing() + logger.trace("Finished triggering the thing") + }) + """ + + testFormatting(for: input, [output], rules: [.redundantClosure, .indent], exclude: [.redundantType]) + } + + func testKeepsClosureThatThrowsError() { + let input = "let foo = try bar ?? { throw NSError() }()" + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsDiscardableResultClosure() { + let input = """ + @discardableResult + func discardableResult() -> String { "hello world" } + + /// We can't remove this closure, since the method called inline + /// would return a String instead. + let void: Void = { discardableResult() }() + """ + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsDiscardableResultClosure2() { + let input = """ + @discardableResult + func discardableResult() -> String { "hello world" } + + /// We can't remove this closure, since the method called inline + /// would return a String instead. + let void: () = { discardableResult() }() + """ + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureDoesntLeaveStrayTry() { + let input = """ + let user2: User? = try { + if let data2 = defaults.data(forKey: defaultsKey) { + return try PropertyListDecoder().decode(User.self, from: data2) + } else { + return nil + } + }() + """ + let output = """ + let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { + try PropertyListDecoder().decode(User.self, from: data2) + } else { + nil + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntLeaveStrayTryAwait() { + let input = """ + let user2: User? = try await { + if let data2 = defaults.data(forKey: defaultsKey) { + return try await PropertyListDecoder().decode(User.self, from: data2) + } else { + return nil + } + }() + """ + let output = """ + let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { + try await PropertyListDecoder().decode(User.self, from: data2) + } else { + nil + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInOperatorChain() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes: Int { + { + switch self { + case .uint8: UInt8.bitWidth + case .uint16: UInt16.bitWidth + } + }() / 8 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes: Int { + { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() / 8 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain2() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes: Int { + 8 / { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain3() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes = 8 / { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesRemoveRedundantIfStatementClosureInAssignmentPosition() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes = { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() + } + """ + + let output = """ + private enum Format { + case uint8 + case uint16 + + var bytes = if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInArray() { + let input = """ + private func constraint() -> [Int] { + [ + 1, + 2, + { + if Bool.random() { + 3 + } else { + 4 + } + }(), + ] + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement() { + let input = """ + func method() -> Int { + return { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + }() + } + """ + + let output = """ + func method() -> Int { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement2() { + let input = """ + func method() throws -> Int { + return try { + return try if Bool.random() { + randomThrows() + } else { + randomThrows() + } + }() + } + """ + + let output = """ + func method() throws -> Int { + return try if Bool.random() { + randomThrows() + } else { + randomThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement3() { + let input = """ + func method() async throws -> Int { + return try await { + return try await if Bool.random() { + randomAsyncThrows() + } else { + randomAsyncThrows() + } + }() + } + """ + + let output = """ + func method() async throws -> Int { + return try await if Bool.random() { + randomAsyncThrows() + } else { + randomAsyncThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement4() { + let input = """ + func method() -> Int { + return { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + }() + } + """ + + let output = """ + func method() -> Int { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnStatement() { + let input = """ + func method() -> Int { + return { + return if Bool.random() { + 42 + } else { + 43 + } + }() + } + """ + + let output = """ + func method() -> Int { + return if Bool.random() { + 42 + } else { + 43 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.redundantClosure], + options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsImplicitReturnStatement() { + let input = """ + func method() -> Int { + { + if Bool.random() { + 42 + } else { + 43 + } + }() + } + """ + + let output = """ + func method() -> Int { + if Bool.random() { + 42 + } else { + 43 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent]) + } + + func testClosureNotRemovedAroundIfExpressionInGuard() { + let input = """ + guard let foo = { + if condition { + bar() + } + }() else { + return + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall() { + let input = """ + XCTAssert({ + if foo { + bar + } else { + baaz + } + }()) + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall2() { + let input = """ + method("foo", { + if foo { + bar + } else { + baaz + } + }()) + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall3() { + let input = """ + XCTAssert({ + if foo { + bar + } else { + baaz + } + }(), "message") + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall4() { + let input = """ + method( + "foo", + { + if foo { + bar + } else { + baaz + } + }(), + "bar" + ) + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testDoesntRemoveClosureWithIfExpressionConditionalCastInSwift5_9() { + // The following code doesn't compile in Swift 5.9 due to this issue: + // https://github.com/apple/swift/issues/68764 + // + // let result = if condition { + // foo as? String + // } else { + // "bar" + // } + // + let input = """ + let result1: String? = { + if condition { + return foo as? String + } else { + return "bar" + } + }() + + let result1: String? = { + switch condition { + case true: + return foo as! String + case false: + return "bar" + } + }() + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testDoesRemoveClosureWithIfExpressionConditionalCastInSwift5_10() { + let input = """ + let result1: String? = { + if condition { + foo as? String + } else { + "bar" + } + }() + + let result2: String? = { + switch condition { + case true: + foo as? String + case false: + "bar" + } + }() + """ + + let output = """ + let result1: String? = if condition { + foo as? String + } else { + "bar" + } + + let result2: String? = switch condition { + case true: + foo as? String + case false: + "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.10") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntBreakBuildWithRedundantReturnRuleDisabled() { + let input = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = { + return 0 + }() + """ + + let output = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = 0 + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, + exclude: [.redundantReturn, .blankLinesBetweenScopes, .propertyType]) + } + + func testRedundantClosureWithSwitchExpressionDoesntBreakBuildWithRedundantReturnRuleDisabled() { + // From https://github.com/nicklockwood/SwiftFormat/issues/1565 + let input = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = { + switch myEnum { + case .a: + return 0 + case .b: + return 1 + } + }() + """ + + let output = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = switch myEnum { + case .a: + 0 + case .b: + 1 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], + options: options, + exclude: [.indent, .blankLinesBetweenScopes, .wrapMultilineConditionalAssignment, + .propertyType]) + } + + func testRemovesRedundantClosureWithGenericExistentialTypes() { + let input = """ + let foo: Foo = { DefaultFoo() }() + let foo: any Foo = { DefaultFoo() }() + let foo: any Foo = { DefaultFoo() }() + """ + + let output = """ + let foo: Foo = DefaultFoo() + let foo: any Foo = DefaultFoo() + let foo: any Foo = DefaultFoo() + """ + + testFormatting(for: input, output, rule: .redundantClosure) + } +} diff --git a/Tests/Rules/RedundantExtensionACLTests.swift b/Tests/Rules/RedundantExtensionACLTests.swift new file mode 100644 index 00000000..7072a069 --- /dev/null +++ b/Tests/Rules/RedundantExtensionACLTests.swift @@ -0,0 +1,48 @@ +// +// RedundantExtensionACLTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 2/3/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantExtensionACLTests: XCTestCase { + func testPublicExtensionMemberACLStripped() { + let input = """ + public extension Foo { + public var bar: Int { 5 } + private static let baz = "baz" + public func quux() {} + } + """ + let output = """ + public extension Foo { + var bar: Int { 5 } + private static let baz = "baz" + func quux() {} + } + """ + testFormatting(for: input, output, rule: .redundantExtensionACL) + } + + func testPrivateExtensionMemberACLNotStrippedUnlessFileprivate() { + let input = """ + private extension Foo { + fileprivate var bar: Int { 5 } + private static let baz = "baz" + fileprivate func quux() {} + } + """ + let output = """ + private extension Foo { + var bar: Int { 5 } + private static let baz = "baz" + func quux() {} + } + """ + testFormatting(for: input, output, rule: .redundantExtensionACL) + } +} diff --git a/Tests/Rules/RedundantFileprivateTests.swift b/Tests/Rules/RedundantFileprivateTests.swift new file mode 100644 index 00000000..e3241544 --- /dev/null +++ b/Tests/Rules/RedundantFileprivateTests.swift @@ -0,0 +1,575 @@ +// +// RedundantFileprivateTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 2/3/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantFileprivateTests: XCTestCase { + func testFileScopeFileprivateVarChangedToPrivate() { + let input = """ + fileprivate var foo = "foo" + """ + let output = """ + private var foo = "foo" + """ + testFormatting(for: input, output, rule: .redundantFileprivate) + } + + func testFileScopeFileprivateVarNotChangedToPrivateIfFragment() { + let input = """ + fileprivate var foo = "foo" + """ + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherType() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + } + """ + let output = """ + struct Foo { + private var foo = "foo" + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherTypeAndFileIncludesImports() { + let input = """ + import Foundation + + struct Foo { + fileprivate var foo = "foo" + } + """ + let output = """ + import Foundation + + struct Foo { + private var foo = "foo" + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAnotherType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + struct Bar { + func bar() { + print(Foo().foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromSubclass() { + let input = """ + class Foo { + fileprivate func foo() {} + } + + class Bar: Foo { + func bar() { + return foo() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAFunction() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + func getFoo() -> String { + return Foo().foo + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAConstant() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + let kFoo = Foo().foo + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + var kFoo: String { return Foo().foo } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromCode() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + print(Foo().foo) + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAClosure() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + print({ Foo().foo }()) + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.redundantClosure]) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAnExtensionOnAnotherType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + extension Bar { + func bar() { + print(Foo().foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfAccessedFromAnExtensionOnSameType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + extension Foo { + func bar() { + print(foo) + } + } + """ + let output = """ + struct Foo { + private let foo = "foo" + } + + extension Foo { + func bar() { + print(foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfAccessedViaSelfFromAnExtensionOnSameType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + extension Foo { + func bar() { + print(self.foo) + } + } + """ + let output = """ + struct Foo { + private let foo = "foo" + } + + extension Foo { + func bar() { + print(self.foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, + exclude: [.redundantSelf]) + } + + func testFileprivateMultiLetNotChangedToPrivateIfAccessedOutsideType() { + let input = """ + struct Foo { + fileprivate let foo = "foo", bar = "bar" + } + + extension Foo { + func bar() { + print(foo) + } + } + + extension Bar { + func bar() { + print(Foo().bar) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitChangedToPrivateIfConstructorNotCalledOutsideType() { + let input = """ + struct Foo { + fileprivate init() {} + } + """ + let output = """ + struct Foo { + private init() {} + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType() { + let input = """ + struct Foo { + fileprivate init() {} + } + + let foo = Foo() + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { + let input = """ + class Foo { + fileprivate init() {} + } + + struct Bar { + let foo = Foo() + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { + let input = """ + struct Foo { + fileprivate let bar: String + } + + let foo = Foo(bar: "test") + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { + let input = """ + class Foo { + fileprivate let bar: String + } + + let foo = Foo() + """ + let output = """ + class Foo { + private let bar: String + } + + let foo = Foo() + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { + let input = """ + private class Foo: Equatable { + fileprivate static func == (_: Foo, _: Foo) -> Bool { + return true + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInnerTypeNotChangedToPrivate() { + let input = """ + struct Foo { + fileprivate enum Bar { + case a, b + } + + fileprivate let bar: Bar + } + + func foo(foo: Foo) { + print(foo.bar) + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, + rule: .redundantFileprivate, + options: options, + exclude: [.wrapEnumCases]) + } + + func testFileprivateClassTypeMemberNotChangedToPrivate() { + let input = """ + class Foo { + fileprivate class var bar = "bar" + } + + func foo() { + print(Foo.bar) + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testOverriddenFileprivateInitNotChangedToPrivate() { + let input = """ + class Foo { + fileprivate init() {} + } + + class Bar: Foo, Equatable { + override public init() { + super.init() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testNonOverriddenFileprivateInitChangedToPrivate() { + let input = """ + class Foo { + fileprivate init() {} + } + + class Bar: Baz { + override public init() { + super.init() + } + } + """ + let output = """ + class Foo { + private init() {} + } + + class Bar: Baz { + override public init() { + super.init() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitNotChangedToPrivateWhenUsingTypeInferredInits() { + let input = """ + struct Example { + fileprivate init() {} + } + + enum Namespace { + static let example: Example = .init() + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { + let input = """ + private struct Foo {} + + public struct Bar { + fileprivate let consumeFoo: (Foo) -> Void + } + + public func makeBar() -> Bar { + Bar { _ in } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnContainingType() { + let input = """ + extension Foo.Bar { + fileprivate init() {} + } + + extension Foo { + func baz() -> Foo.Bar { + return Bar() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnNestedType() { + let input = """ + extension Foo { + fileprivate init() {} + } + + extension Foo.Bar { + func baz() -> Foo { + return Foo() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromSubclass() { + let input = """ + class Foo: Bar { + func quux() { + baz() + } + } + + extension Bar { + fileprivate func baz() {} + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitNotChangedToPrivateWhenAccessedFromSubclass() { + let input = """ + public class Foo { + fileprivate init() {} + } + + private class Bar: Foo { + init(something: String) { + print(something) + super.init() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromExtensionOnSubclass() { + let input = """ + class Foo: Bar {} + + extension Foo { + func quux() { + baz() + } + } + + extension Bar { + fileprivate func baz() {} + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarWithPropertWrapperNotChangedToPrivateIfAccessedFromSubclass() { + let input = """ + class Foo { + @Foo fileprivate var foo = 5 + } + + class Bar: Foo { + func bar() { + return $foo + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile() { + let input = """ + extension [String] { + fileprivate func fileprivateMember() {} + } + + extension Namespace { + func testCanAccessFileprivateMember() { + ["string", "array"].fileprivateMember() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile2() { + let input = """ + extension Array { + fileprivate func fileprivateMember() {} + } + + extension Namespace { + func testCanAccessFileprivateMember() { + ["string", "array"].fileprivateMember() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, + options: options, exclude: [.typeSugar]) + } +} diff --git a/Tests/Rules/RedundantGetTests.swift b/Tests/Rules/RedundantGetTests.swift new file mode 100644 index 00000000..53065d0d --- /dev/null +++ b/Tests/Rules/RedundantGetTests.swift @@ -0,0 +1,56 @@ +// +// RedundantGetTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 11/15/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantGetTests: XCTestCase { + func testRemoveSingleLineIsolatedGet() { + let input = "var foo: Int { get { return 5 } }" + let output = "var foo: Int { return 5 }" + testFormatting(for: input, output, rule: .redundantGet) + } + + func testRemoveMultilineIsolatedGet() { + let input = "var foo: Int {\n get {\n return 5\n }\n}" + let output = "var foo: Int {\n return 5\n}" + testFormatting(for: input, [output], rules: [.redundantGet, .indent]) + } + + func testNoRemoveMultilineGetSet() { + let input = "var foo: Int {\n get { return 5 }\n set { foo = newValue }\n}" + testFormatting(for: input, rule: .redundantGet) + } + + func testNoRemoveAttributedGet() { + let input = "var enabled: Bool { @objc(isEnabled) get { true } }" + testFormatting(for: input, rule: .redundantGet) + } + + func testRemoveSubscriptGet() { + let input = "subscript(_ index: Int) {\n get {\n return lookup(index)\n }\n}" + let output = "subscript(_ index: Int) {\n return lookup(index)\n}" + testFormatting(for: input, [output], rules: [.redundantGet, .indent]) + } + + func testGetNotRemovedInFunction() { + let input = "func foo() {\n get {\n self.lookup(index)\n }\n}" + testFormatting(for: input, rule: .redundantGet) + } + + func testEffectfulGetNotRemoved() { + let input = """ + var foo: Int { + get async throws { + try await getFoo() + } + } + """ + testFormatting(for: input, rule: .redundantGet) + } +} diff --git a/Tests/Rules/RedundantInitTests.swift b/Tests/Rules/RedundantInitTests.swift new file mode 100644 index 00000000..39e9da65 --- /dev/null +++ b/Tests/Rules/RedundantInitTests.swift @@ -0,0 +1,223 @@ +// +// RedundantInitTests.swift +// SwiftFormatTests +// +// Created by Alejandro Martínez on 6/19/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantInitTests: XCTestCase { + func testRemoveRedundantInit() { + let input = "[1].flatMap { String.init($0) }" + let output = "[1].flatMap { String($0) }" + testFormatting(for: input, output, rule: .redundantInit) + } + + func testRemoveRedundantInit2() { + let input = "[String.self].map { Type in Type.init(foo: 1) }" + let output = "[String.self].map { Type in Type(foo: 1) }" + testFormatting(for: input, output, rule: .redundantInit) + } + + func testRemoveRedundantInit3() { + let input = "String.init(\"text\")" + let output = "String(\"text\")" + testFormatting(for: input, output, rule: .redundantInit) + } + + func testDontRemoveInitInSuperCall() { + let input = "class C: NSObject { override init() { super.init() } }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitInSelfCall() { + let input = "struct S { let n: Int }; extension S { init() { self.init(n: 1) } }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenPassedAsFunction() { + let input = "[1].flatMap(String.init)" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenUsedOnMetatype() { + let input = "[String.self].map { type in type.init(1) }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenUsedOnImplicitClosureMetatype() { + let input = "[String.self].map { $0.init(1) }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenUsedOnPossibleMetatype() { + let input = "let something = Foo.bar.init()" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWithExplicitSignature() { + let input = "[String.self].map(Foo.init(bar:))" + testFormatting(for: input, rule: .redundantInit) + } + + func testRemoveInitWithOpenParenOnFollowingLine() { + let input = """ + var foo: Foo { + Foo.init + ( + bar: bar, + baaz: baaz + ) + } + """ + let output = """ + var foo: Foo { + Foo( + bar: bar, + baaz: baaz + ) + } + """ + testFormatting(for: input, output, rule: .redundantInit) + } + + func testNoRemoveInitWithOpenParenOnFollowingLineAfterComment() { + let input = """ + var foo: Foo { + Foo.init // foo + ( + bar: bar, + baaz: baaz + ) + } + """ + testFormatting(for: input, rule: .redundantInit) + } + + func testNoRemoveInitForLowercaseType() { + let input = """ + let foo = bar.init() + """ + testFormatting(for: input, rule: .redundantInit) + } + + func testNoRemoveInitForLocalLetType() { + let input = """ + let Foo = Foo.self + let foo = Foo.init() + """ + testFormatting(for: input, rule: .redundantInit, exclude: [.propertyType]) + } + + func testNoRemoveInitForLocalLetType2() { + let input = """ + let Foo = Foo.self + if x { + return Foo.init(x) + } else { + return Foo.init(y) + } + """ + testFormatting(for: input, rule: .redundantInit) + } + + func testNoRemoveInitInsideIfdef() { + let input = """ + func myFunc() async throws -> String { + #if DEBUG + .init("foo") + #else + "" + #endif + } + """ + testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) + } + + func testNoRemoveInitInsideIfdef2() { + let input = """ + func myFunc() async throws(Foo) -> String { + #if DEBUG + .init("foo") + #else + "" + #endif + } + """ + testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) + } + + func testRemoveInitAfterCollectionLiterals() { + let input = """ + let array = [String].init() + let arrayElement = [String].Element.init() + let nestedArray = [[String]].init() + let tupleArray = [(key: String, value: Int)].init() + let dictionary = [String: Int].init() + """ + let output = """ + let array = [String]() + let arrayElement = [String].Element() + let nestedArray = [[String]]() + let tupleArray = [(key: String, value: Int)]() + let dictionary = [String: Int]() + """ + testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) + } + + func testPreservesInitAfterTypeOfCall() { + let input = """ + type(of: oldViewController).init() + """ + + testFormatting(for: input, rule: .redundantInit) + } + + func testRemoveInitAfterOptionalType() { + let input = """ + let someOptional = String?.init("Foo") + // (String!.init("Foo") isn't valid Swift code, so we don't test for it) + """ + let output = """ + let someOptional = String?("Foo") + // (String!.init("Foo") isn't valid Swift code, so we don't test for it) + """ + + testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) + } + + func testPreservesTryBeforeInit() { + let input = """ + let throwing: Foo = try .init() + let throwingOptional1: Foo = try? .init() + let throwingOptional2: Foo = try! .init() + """ + + testFormatting(for: input, rule: .redundantInit) + } + + func testRemoveInitAfterGenericType() { + let input = """ + let array = Array.init() + let dictionary = Dictionary.init() + let atomicDictionary = Atomic<[String: Int]>.init() + """ + let output = """ + let array = Array() + let dictionary = Dictionary() + let atomicDictionary = Atomic<[String: Int]>() + """ + + testFormatting(for: input, output, rule: .redundantInit, exclude: [.typeSugar, .propertyType]) + } + + func testPreserveNonRedundantInitInTernaryOperator() { + let input = """ + let bar: Bar = (foo.isBar && bar.isBaaz) ? .init() : nil + """ + testFormatting(for: input, rule: .redundantInit) + } +} diff --git a/Tests/Rules/RedundantInternalTests.swift b/Tests/Rules/RedundantInternalTests.swift new file mode 100644 index 00000000..37d25393 --- /dev/null +++ b/Tests/Rules/RedundantInternalTests.swift @@ -0,0 +1,108 @@ +// +// RedundantInternalTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantInternalTests: XCTestCase { + func testRemoveRedundantInternalACL() { + let input = """ + internal class Foo { + internal let bar: String + + internal func baaz() {} + + internal init() { + bar = "bar" + } + } + """ + + let output = """ + class Foo { + let bar: String + + func baaz() {} + + init() { + bar = "bar" + } + } + """ + + testFormatting(for: input, output, rule: .redundantInternal) + } + + func testPreserveInternalInNonInternalExtensionExtension() { + let input = """ + extension Foo { + /// internal is redundant here since the extension is internal + internal func bar() {} + + public func baaz() {} + + /// internal is redundant here since the extension is internal + internal func bar() {} + } + + public extension Foo { + /// internal is not redundant here since the extension is public + internal func bar() {} + + public func baaz() {} + + /// internal is not redundant here since the extension is public + internal func bar() {} + } + """ + + let output = """ + extension Foo { + /// internal is redundant here since the extension is internal + func bar() {} + + public func baaz() {} + + /// internal is redundant here since the extension is internal + func bar() {} + } + + public extension Foo { + /// internal is not redundant here since the extension is public + internal func bar() {} + + public func baaz() {} + + /// internal is not redundant here since the extension is public + internal func bar() {} + } + """ + + testFormatting(for: input, output, rule: .redundantInternal, exclude: [.redundantExtensionACL]) + } + + func testPreserveInternalImport() { + let input = "internal import MyPackage" + testFormatting(for: input, rule: .redundantInternal) + } + + func testPreservesInternalInPublicExtensionWithWhereClause() { + let input = """ + public extension SomeProtocol where SomeAssociatedType == SomeOtherType { + internal func fun1() {} + func fun2() {} + } + + public extension OtherProtocol { + internal func fun1() {} + func fun2() {} + } + """ + testFormatting(for: input, rule: .redundantInternal) + } +} diff --git a/Tests/Rules/RedundantLetErrorTests.swift b/Tests/Rules/RedundantLetErrorTests.swift new file mode 100644 index 00000000..22d321db --- /dev/null +++ b/Tests/Rules/RedundantLetErrorTests.swift @@ -0,0 +1,24 @@ +// +// RedundantLetErrorTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/16/18. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantLetErrorTests: XCTestCase { + func testCatchLetError() { + let input = "do {} catch let error {}" + let output = "do {} catch {}" + testFormatting(for: input, output, rule: .redundantLetError) + } + + func testCatchLetErrorWithTypedThrows() { + let input = "do throws(Foo) {} catch let error {}" + let output = "do throws(Foo) {} catch {}" + testFormatting(for: input, output, rule: .redundantLetError) + } +} diff --git a/Tests/Rules/RedundantLetTests.swift b/Tests/Rules/RedundantLetTests.swift new file mode 100644 index 00000000..db09444a --- /dev/null +++ b/Tests/Rules/RedundantLetTests.swift @@ -0,0 +1,136 @@ +// +// RedundantLetTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/14/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantLetTests: XCTestCase { + func testRemoveRedundantLet() { + let input = "let _ = bar {}" + let output = "_ = bar {}" + testFormatting(for: input, output, rule: .redundantLet) + } + + func testNoRemoveLetWithType() { + let input = "let _: String = bar {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testRemoveRedundantLetInCase() { + let input = "if case .foo(let _) = bar {}" + let output = "if case .foo(_) = bar {}" + testFormatting(for: input, output, rule: .redundantLet, exclude: [.redundantPattern]) + } + + func testRemoveRedundantVarsInCase() { + let input = "if case .foo(var _, var /* unused */ _) = bar {}" + let output = "if case .foo(_, /* unused */ _) = bar {}" + testFormatting(for: input, output, rule: .redundantLet) + } + + func testNoRemoveLetInIf() { + let input = "if let _ = foo {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInMultiIf() { + let input = "if foo == bar, /* comment! */ let _ = baz {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInGuard() { + let input = "guard let _ = foo else {}" + testFormatting(for: input, rule: .redundantLet, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveLetInWhile() { + let input = "while let _ = foo {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInViewBuilder() { + let input = """ + HStack { + let _ = print("Hi") + Text("Some text") + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInViewBuilderModifier() { + let input = """ + VStack { + Text("Some text") + } + .overlay( + HStack { + let _ = print("") + } + ) + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInIfStatementInViewBuilder() { + let input = """ + VStack { + if visible == "YES" { + let _ = print("") + } + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInSwitchStatementInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + var foo = "" + switch (self.min, self.max) { + case let (nil, max as Int): + let _ = { + foo = "\\(max)" + }() + + default: + EmptyView() + } + + Text(foo) + } + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveAsyncLet() { + let input = "async let _ = foo()" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetImmediatelyAfterMainActorAttribute() { + let input = """ + let foo = bar { @MainActor + let _ = try await baz() + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetImmediatelyAfterSendableAttribute() { + let input = """ + let foo = bar { @Sendable + let _ = try await baz() + } + """ + testFormatting(for: input, rule: .redundantLet) + } +} diff --git a/Tests/Rules/RedundantNilInitTests.swift b/Tests/Rules/RedundantNilInitTests.swift new file mode 100644 index 00000000..c47c2fb0 --- /dev/null +++ b/Tests/Rules/RedundantNilInitTests.swift @@ -0,0 +1,489 @@ +// +// RedundantNilInitTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/5/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantNilInitTests: XCTestCase { + func testRemoveRedundantNilInit() { + let input = "var foo: Int? = nil\nlet bar: Int? = nil" + let output = "var foo: Int?\nlet bar: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveLetNilInitAfterVar() { + let input = "var foo: Int; let bar: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNonNilInit() { + let input = "var foo: Int? = 0" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testRemoveRedundantImplicitUnwrapInit() { + let input = "var foo: Int! = nil" + let output = "var foo: Int!" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testRemoveMultipleRedundantNilInitsInSameLine() { + let input = "var foo: Int? = nil, bar: Int? = nil" + let output = "var foo: Int?, bar: Int?" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveLazyVarNilInit() { + let input = "lazy var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveLazyPublicPrivateSetVarNilInit() { + let input = "lazy private(set) public var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, options: options, + exclude: [.modifierOrder]) + } + + func testNoRemoveCodableNilInit() { + let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithPropertyWrapper() { + let input = "@Foo var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithLowercasePropertyWrapper() { + let input = "@foo var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithPropertyWrapperWithArgument() { + let input = "@Foo(bar: baz) var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithLowercasePropertyWrapperWithArgument() { + let input = "@foo(bar: baz) var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testRemoveNilInitWithObjcAttributes() { + let input = "@objc var foo: Int? = nil" + let output = "@objc var foo: Int?" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInStructWithDefaultInit() { + let input = """ + struct Foo { + var bar: String? = nil + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testRemoveNilInitInStructWithDefaultInitInSwiftVersion5_2() { + let input = """ + struct Foo { + var bar: String? = nil + } + """ + let output = """ + struct Foo { + var bar: String? + } + """ + let options = FormatOptions(nilInit: .remove, swiftVersion: "5.2") + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testRemoveNilInitInStructWithCustomInit() { + let input = """ + struct Foo { + var bar: String? = nil + init() { + bar = "bar" + } + } + """ + let output = """ + struct Foo { + var bar: String? + init() { + bar = "bar" + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + var foo: String? = nil + Text(foo ?? "") + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInIfStatementInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + if true { + var foo: String? = nil + Text(foo ?? "") + } else { + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInSwitchStatementInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + switch foo { + case .bar: + var foo: String? = nil + Text(foo ?? "") + + default: + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + // --nilInit insert + + func testInsertNilInit() { + let input = "var foo: Int?\nlet bar: Int? = nil" + let output = "var foo: Int? = nil\nlet bar: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitBeforeLet() { + let input = "var foo: Int?; let bar: Int? = nil" + let output = "var foo: Int? = nil; let bar: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitAfterLet() { + let input = "let bar: Int? = nil; var foo: Int?" + let output = "let bar: Int? = nil; var foo: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNonNilInit() { + let input = "var foo: Int? = 0" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertRedundantImplicitUnwrapInit() { + let input = "var foo: Int!" + let output = "var foo: Int! = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertMultipleRedundantNilInitsInSameLine() { + let input = "var foo: Int?, bar: Int?" + let output = "var foo: Int? = nil, bar: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertLazyVarNilInit() { + let input = "lazy var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertLazyPublicPrivateSetVarNilInit() { + let input = "lazy private(set) public var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, options: options, + exclude: [.modifierOrder]) + } + + func testNoInsertCodableNilInit() { + let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithPropertyWrapper() { + let input = "@Foo var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithLowercasePropertyWrapper() { + let input = "@foo var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithPropertyWrapperWithArgument() { + let input = "@Foo(bar: baz) var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithLowercasePropertyWrapperWithArgument() { + let input = "@foo(bar: baz) var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitWithObjcAttributes() { + let input = "@objc var foo: Int?" + let output = "@objc var foo: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInStructWithDefaultInit() { + let input = """ + struct Foo { + var bar: String? + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitInStructWithDefaultInitInSwiftVersion5_2() { + let input = """ + struct Foo { + var bar: String? + var foo: String? = nil + } + """ + let output = """ + struct Foo { + var bar: String? = nil + var foo: String? = nil + } + """ + let options = FormatOptions(nilInit: .insert, swiftVersion: "5.2") + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitInStructWithCustomInit() { + let input = """ + struct Foo { + var bar: String? + var foo: String? = nil + init() { + bar = "bar" + foo = "foo" + } + } + """ + let output = """ + struct Foo { + var bar: String? = nil + var foo: String? = nil + init() { + bar = "bar" + foo = "foo" + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInViewBuilder() { + // Not insert `nil` in result builder + let input = """ + struct TestView: View { + var body: some View { + var foo: String? + Text(foo ?? "") + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInIfStatementInViewBuilder() { + // Not insert `nil` in result builder + let input = """ + struct TestView: View { + var body: some View { + if true { + var foo: String? + Text(foo ?? "") + } else { + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInSwitchStatementInViewBuilder() { + // Not insert `nil` in result builder + let input = """ + struct TestView: View { + var body: some View { + switch foo { + case .bar: + var foo: String? + Text(foo ?? "") + + default: + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInSingleLineComputedProperty() { + let input = """ + var bar: String? { "some string" } + var foo: String? { nil } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInMultilineComputedProperty() { + let input = """ + var foo: String? { + print("some") + } + + var bar: String? { + nil + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInCustomGetterAndSetterProperty() { + let input = """ + var _foo: String? = nil + var foo: String? { + set { _foo = newValue } + get { newValue } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitInInstancePropertyWithBody() { + let input = """ + var foo: String? { + didSet { print(foo) } + } + """ + + let output = """ + var foo: String? = nil { + didSet { print(foo) } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInAs() { + let input = """ + let json: Any = ["key": 1] + var jsonObject = json as? [String: Int] + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } +} diff --git a/Tests/Rules/RedundantObjcTests.swift b/Tests/Rules/RedundantObjcTests.swift new file mode 100644 index 00000000..c58ebcab --- /dev/null +++ b/Tests/Rules/RedundantObjcTests.swift @@ -0,0 +1,161 @@ +// +// RedundantObjcTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/30/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantObjcTests: XCTestCase { + func testRedundantObjcRemovedFromBeforeOutlet() { + let input = "@objc @IBOutlet var label: UILabel!" + let output = "@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testRedundantObjcRemovedFromAfterOutlet() { + let input = "@IBOutlet @objc var label: UILabel!" + let output = "@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testRedundantObjcRemovedFromLineBeforeOutlet() { + let input = "@objc\n@IBOutlet var label: UILabel!" + let output = "\n@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testRedundantObjcCommentNotRemoved() { + let input = "@objc /// an outlet\n@IBOutlet var label: UILabel!" + let output = "/// an outlet\n@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcNotRemovedFromNSCopying() { + let input = "@objc @NSCopying var foo: String!" + testFormatting(for: input, rule: .redundantObjc) + } + + func testRenamedObjcNotRemoved() { + let input = "@IBOutlet @objc(uiLabel) var label: UILabel!" + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcRemovedOnObjcMembersClass() { + let input = """ + @objcMembers class Foo: NSObject { + @objc var foo: String + } + """ + let output = """ + @objcMembers class Foo: NSObject { + var foo: String + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcRemovedOnRenamedObjcMembersClass() { + let input = """ + @objcMembers @objc(OCFoo) class Foo: NSObject { + @objc var foo: String + } + """ + let output = """ + @objcMembers @objc(OCFoo) class Foo: NSObject { + var foo: String + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcNotRemovedOnNestedClass() { + let input = """ + @objcMembers class Foo: NSObject { + @objc class Bar: NSObject {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcNotRemovedOnRenamedPrivateNestedClass() { + let input = """ + @objcMembers class Foo: NSObject { + @objc private class Bar: NSObject {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcNotRemovedOnNestedEnum() { + let input = """ + @objcMembers class Foo: NSObject { + @objc enum Bar: Int {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcRemovedOnObjcExtensionVar() { + let input = """ + @objc extension Foo { + @objc var foo: String {} + } + """ + let output = """ + @objc extension Foo { + var foo: String {} + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcRemovedOnObjcExtensionFunc() { + let input = """ + @objc extension Foo { + @objc func foo() -> String {} + } + """ + let output = """ + @objc extension Foo { + func foo() -> String {} + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcNotRemovedOnPrivateFunc() { + let input = """ + @objcMembers class Foo: NSObject { + @objc private func bar() {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcNotRemovedOnFileprivateFunc() { + let input = """ + @objcMembers class Foo: NSObject { + @objc fileprivate func bar() {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcRemovedOnPrivateSetFunc() { + let input = """ + @objcMembers class Foo: NSObject { + @objc private(set) func bar() {} + } + """ + let output = """ + @objcMembers class Foo: NSObject { + private(set) func bar() {} + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } +} diff --git a/Tests/Rules/RedundantOptionalBindingTests.swift b/Tests/Rules/RedundantOptionalBindingTests.swift new file mode 100644 index 00000000..0cd9832d --- /dev/null +++ b/Tests/Rules/RedundantOptionalBindingTests.swift @@ -0,0 +1,183 @@ +// +// RedundantOptionalBindingTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 8/1/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantOptionalBindingTests: XCTestCase { + func testRemovesRedundantOptionalBindingsInSwift5_7() { + let input = """ + if let foo = foo { + print(foo) + } + + else if var bar = bar { + print(bar) + } + + guard let self = self else { + return + } + + while var quux = quux { + break + } + """ + + let output = """ + if let foo { + print(foo) + } + + else if var bar { + print(bar) + } + + guard let self else { + return + } + + while var quux { + break + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options, exclude: [.elseOnSameLine]) + } + + func testRemovesMultipleOptionalBindings() { + let input = """ + if let foo = foo, let bar = bar, let baaz = baaz { + print(foo, bar, baaz) + } + + guard let foo = foo, let bar = bar, let baaz = baaz else { + return + } + """ + + let output = """ + if let foo, let bar, let baaz { + print(foo, bar, baaz) + } + + guard let foo, let bar, let baaz else { + return + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) + } + + func testRemovesMultipleOptionalBindingsOnSeparateLines() { + let input = """ + if + let foo = foo, + let bar = bar, + let baaz = baaz + { + print(foo, bar, baaz) + } + + guard + let foo = foo, + let bar = bar, + let baaz = baaz + else { + return + } + """ + + let output = """ + if + let foo, + let bar, + let baaz + { + print(foo, bar, baaz) + } + + guard + let foo, + let bar, + let baaz + else { + return + } + """ + + let options = FormatOptions(indent: " ", swiftVersion: "5.7") + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) + } + + func testKeepsRedundantOptionalBeforeSwift5_7() { + let input = """ + if let foo = foo { + print(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.6") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) + } + + func testKeepsNonRedundantOptional() { + let input = """ + if let foo = bar { + print(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) + } + + func testKeepsOptionalNotEligibleForShorthand() { + let input = """ + if let foo = self.foo, let bar = bar(), let baaz = baaz[0] { + print(foo, bar, baaz) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options, exclude: [.redundantSelf]) + } + + func testRedundantSelfAndRedundantOptionalTogether() { + let input = """ + if let foo = self.foo { + print(foo) + } + """ + + let output = """ + if let foo { + print(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, [output], rules: [.redundantOptionalBinding, .redundantSelf], options: options) + } + + func testDoesntRemoveShadowingOutsideOfOptionalBinding() { + let input = """ + let foo = foo + + if let bar = baaz({ + let foo = foo + print(foo) + }) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) + } +} diff --git a/Tests/RulesTests+Parens.swift b/Tests/Rules/RedundantParensTests.swift similarity index 62% rename from Tests/RulesTests+Parens.swift rename to Tests/Rules/RedundantParensTests.swift index 032d90fb..8cad8c89 100644 --- a/Tests/RulesTests+Parens.swift +++ b/Tests/Rules/RedundantParensTests.swift @@ -1,206 +1,204 @@ // -// RulesTests+Parens.swift +// RedundantParensTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Nick Lockwood on 11/2/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class ParensTests: RulesTests { - // MARK: - redundantParens - +class RedundantParensTests: XCTestCase { // around expressions func testRedundantParensRemoved() { let input = "(x || y)" let output = "x || y" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved2() { let input = "(x) || y" let output = "x || y" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved3() { let input = "x + (5)" let output = "x + 5" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved4() { let input = "(.bar)" let output = ".bar" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved5() { let input = "(Foo.bar)" let output = "Foo.bar" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved6() { let input = "(foo())" let output = "foo()" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemoved() { let input = "(x || y) * z" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemoved2() { let input = "(x + y) as Int" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemoved3() { let input = "x+(-5)" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators]) } func testRedundantParensAroundIsNotRemoved() { let input = "a = (x is Int)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedBeforeSubscript() { let input = "(foo + bar)[baz]" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedBeforeCollectionLiteral() { let input = "(foo + bar)\n[baz]" let output = "foo + bar\n[baz]" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedBeforeFunctionInvocation() { let input = "(foo + bar)(baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedBeforeTuple() { let input = "(foo + bar)\n(baz, quux).0" let output = "foo + bar\n(baz, quux).0" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedBeforePostfixOperator() { let input = "(foo + bar)!" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedBeforeInfixOperator() { let input = "(foo + bar) * baz" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensNotRemovedAroundSelectorStringLiteral() { let input = "Selector((\"foo\"))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensRemovedOnLineAfterSelectorIdentifier() { let input = "Selector\n((\"foo\"))" let output = "Selector\n(\"foo\")" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testMeaningfulParensNotRemovedAroundFileLiteral() { let input = "func foo(_ file: String = (#file)) {}" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.unusedArguments]) } func testMeaningfulParensNotRemovedAroundOperator() { let input = "let foo: (Int, Int) -> Bool = (<)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensNotRemovedAroundOperatorWithSpaces() { let input = "let foo: (Int, Int) -> Bool = ( < )" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators", "spaceInsideParens"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators, .spaceInsideParens]) } func testMeaningfulParensNotRemovedAroundPrefixOperator() { let input = "let foo: (Int) -> Int = ( -)" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators", "spaceInsideParens"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators, .spaceInsideParens]) } func testMeaningfulParensAroundPrefixExpressionFollowedByDotNotRemoved() { let input = "let foo = (!bar).description" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundPrefixExpressionWithSpacesFollowedByDotNotRemoved() { let input = "let foo = ( !bar ).description" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators", "spaceInsideParens"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators, .spaceInsideParens]) } func testMeaningfulParensAroundPrefixExpressionFollowedByPostfixExpressionNotRemoved() { let input = "let foo = (!bar)!" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundPrefixExpressionFollowedBySubscriptNotRemoved() { let input = "let foo = (!bar)[5]" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionFollowedByPostfixOperatorRemoved() { let input = "let foo = (bar!)!" let output = "let foo = bar!!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionFollowedByPostfixOperatorRemoved2() { let input = "let foo = ( bar! )!" let output = "let foo = bar!!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionRemoved() { let input = "let foo = foo + (bar!)" let output = "let foo = foo + bar!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionFollowedBySubscriptRemoved() { let input = "let foo = (bar!)[5]" let output = "let foo = bar![5]" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPrefixExpressionRemoved() { let input = "let foo = foo + (!bar)" let output = "let foo = foo + !bar" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundInfixExpressionNotRemoved() { let input = "let foo = (foo + bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensAroundInfixEqualsExpressionNotRemoved() { let input = "let foo = (bar == baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensAroundClosureTypeRemoved() { let input = "typealias Foo = ((Int) -> Bool)" let output = "typealias Foo = (Int) -> Bool" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNonRedundantParensAroundClosureTypeNotRemoved() { @@ -210,40 +208,40 @@ class ParensTests: RulesTests { typealias PhotoEnumerationHandler = (PhotoFetchResultEnumeration) -> Void } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // TODO: future enhancement // func testRedundantParensAroundClosureReturnTypeRemoved() { // let input = "typealias Foo = (Int) -> ((Int) -> Bool)" // let output = "typealias Foo = (Int) -> (Int) -> Bool" -// testFormatting(for: input, output, rule: FormatRules.redundantParens) +// testFormatting(for: input, output, rule: .redundantParens) // } func testRedundantParensAroundNestedClosureTypesNotRemoved() { let input = "typealias Foo = (((Int) -> Bool) -> Int) -> ((String) -> Bool) -> Void" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundClosureTypeNotRemoved() { let input = "let foo = ((Int) -> Bool)?" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundTryExpressionNotRemoved() { let input = "let foo = (try? bar()) != nil" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundAwaitExpressionNotRemoved() { let input = "if !(await isSomething()) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensInReturnRemoved() { let input = "return (true)" let output = "return true" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensInMultilineReturnRemovedCleanly() { @@ -259,7 +257,7 @@ class ParensTests: RulesTests { .bar """ - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // around conditions @@ -267,309 +265,309 @@ class ParensTests: RulesTests { func testRedundantParensRemovedInIf() { let input = "if (x || y) {}" let output = "if x || y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf2() { let input = "if (x) || y {}" let output = "if x || y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf3() { let input = "if x + (5) == 6 {}" let output = "if x + 5 == 6 {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf4() { let input = "if (x || y), let foo = bar {}" let output = "if x || y, let foo = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf5() { let input = "if (.bar) {}" let output = "if .bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf6() { let input = "if (Foo.bar) {}" let output = "if Foo.bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf7() { let input = "if (foo()) {}" let output = "if foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf8() { let input = "if x, (y == 2) {}" let output = "if x, y == 2 {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIfWithNoSpace() { let input = "if(x) {}" let output = "if x {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInHashIfWithNoSpace() { let input = "#if(x)\n#endif" let output = "#if x\n#endif" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedInIf() { let input = "if (x || y) * z {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOuterParensRemovedInWhile() { let input = "while ((x || y) && z) {}" let output = "while (x || y) && z {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.andOperator]) } func testOuterParensRemovedInIf() { let input = "if (Foo.bar(baz)) {}" let output = "if Foo.bar(baz) {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testCaseOuterParensRemoved() { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let output = "switch foo {\ncase Foo.bar(let baz):\n}" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["hoistPatternLet"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.hoistPatternLet]) } func testCaseLetOuterParensRemoved() { let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" let output = "switch foo {\ncase let Foo.bar(baz):\n}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testCaseVarOuterParensRemoved() { let input = "switch foo {\ncase var (Foo.bar(baz)):\n}" let output = "switch foo {\ncase var Foo.bar(baz):\n}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testGuardParensRemoved() { let input = "guard (x == y) else { return }" let output = "guard x == y else { return }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, - exclude: ["wrapConditionalBodies"]) + testFormatting(for: input, output, rule: .redundantParens, + exclude: [.wrapConditionalBodies]) } func testForValueParensRemoved() { let input = "for (x) in (y) {}" let output = "for x in y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensForLoopWhereClauseMethodNotRemoved() { let input = "for foo in foos where foo.method() { print(foo) }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.wrapLoopBodies]) } func testSpaceInsertedWhenRemovingParens() { let input = "if(x.y) {}" let output = "if x.y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testSpaceInsertedWhenRemovingParens2() { let input = "while(!foo) {}" let output = "while !foo {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNoDoubleSpaceWhenRemovingParens() { let input = "if ( x.y ) {}" let output = "if x.y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNoDoubleSpaceWhenRemovingParens2() { let input = "if (x.y) {}" let output = "if x.y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // around function and closure arguments func testNestedClosureParensNotRemoved() { let input = "foo { _ in foo(y) {} }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureTypeNotUnwrapped() { let input = "foo = (Bar) -> Baz" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalFunctionCallNotUnwrapped() { let input = "foo?(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalFunctionCallResultNotUnwrapped() { let input = "bar = (foo?()).flatMap(Bar.init)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalSubscriptResultNotUnwrapped() { let input = "bar = (foo?[0]).flatMap(Bar.init)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalMemberResultNotUnwrapped() { let input = "bar = (foo?.baz).flatMap(Bar.init)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testForceUnwrapFunctionCallNotUnwrapped() { let input = "foo!(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testCurriedFunctionCallNotUnwrapped() { let input = "foo(bar)(baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testCurriedFunctionCallNotUnwrapped2() { let input = "foo(bar)(baz) + quux" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testSubscriptFunctionCallNotUnwrapped() { let input = "foo[\"bar\"](baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedInsideClosure() { let input = "{ (foo) + bar }" let output = "{ foo + bar }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedAroundFunctionArgument() { let input = "foo(bar: (5))" let output = "foo(bar: 5)" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedAroundOptionalClosureType() { let input = "let foo = (() -> ())?" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } func testRequiredParensNotRemovedAroundOptionalRange() { let input = "let foo = (2...)?" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalUnwrap() { let input = "let foo = (bar!)+5" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators]) } func testRedundantParensRemovedAroundOptionalOptional() { let input = "let foo: (Int?)?" let output = "let foo: Int??" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalOptional2() { let input = "let foo: (Int!)?" let output = "let foo: Int!?" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalOptional3() { let input = "let foo: (Int?)!" let output = "let foo: Int?!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedAroundOptionalAnyType() { let input = "let foo: (any Foo)?" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundAnyTypeSelf() { let input = "let foo = (any Foo).self" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundAnyTypeType() { let input = "let foo: (any Foo).Type" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundAnyComposedMetatype() { let input = "let foo: any (A & B).Type" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundAnyType() { let input = "let foo: (any Foo)" let output = "let foo: any Foo" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAroundAnyTypeInsideArray() { let input = "let foo: [(any Foo)]" let output = "let foo: [any Foo]" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensAroundParameterPackEachNotRemoved() { let input = "func f(_: repeat ((each V).Type, as: (each V) -> String)) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalClosureType() { let input = "let foo = ((() -> ()))?" let output = "let foo = (() -> ())?" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.void]) } func testRequiredParensNotRemovedAfterClosureArgument() { let input = "foo({ /* code */ }())" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureArgument2() { let input = "foo(bar: { /* code */ }())" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureArgument3() { let input = "foo(bar: 5, { /* code */ }())" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureInsideArray() { let input = "[{ /* code */ }()]" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureInsideArrayWithTrailingComma() { let input = "[{ /* code */ }(),]" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["trailingCommas"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.trailingCommas]) } func testRequiredParensNotRemovedAfterClosureInWhereClause() { let input = "case foo where { x == y }():" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.redundantClosure]) } // around closure arguments @@ -577,77 +575,77 @@ class ParensTests: RulesTests { func testSingleClosureArgumentUnwrapped() { let input = "{ (foo) in }" let output = "{ foo in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.unusedArguments]) } func testSingleMainActorClosureArgumentUnwrapped() { let input = "{ @MainActor (foo) in }" let output = "{ @MainActor foo in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.unusedArguments]) } func testSingleClosureArgumentWithReturnValueUnwrapped() { let input = "{ (foo) -> Int in 5 }" let output = "{ foo -> Int in 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.unusedArguments]) } func testSingleAnonymousClosureArgumentUnwrapped() { let input = "{ (_) in }" let output = "{ _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testSingleAnonymousClosureArgumentNotUnwrapped() { let input = "{ (_ foo) in }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.unusedArguments]) } func testTypedClosureArgumentNotUnwrapped() { let input = "{ (foo: Int) in print(foo) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testSingleClosureArgumentAfterCaptureListUnwrapped() { let input = "{ [weak self] (foo) in self.bar(foo) }" let output = "{ [weak self] foo in self.bar(foo) }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testMultipleClosureArgumentUnwrapped() { let input = "{ (foo, bar) in foo(bar) }" let output = "{ foo, bar in foo(bar) }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testTypedMultipleClosureArgumentNotUnwrapped() { let input = "{ (foo: Int, bar: String) in foo(bar) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testEmptyClosureArgsNotUnwrapped() { let input = "{ () in }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureArgsContainingSelfNotUnwrapped() { let input = "{ (self) in self }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureArgsContainingSelfNotUnwrapped2() { let input = "{ (foo, self) in foo(self) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureArgsContainingSelfNotUnwrapped3() { let input = "{ (self, foo) in foo(self) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNoRemoveParensAroundArrayInitializer() { let input = "let foo = bar { [Int](foo) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNoRemoveParensAroundForIndexInsideClosure() { @@ -656,12 +654,12 @@ class ParensTests: RulesTests { for (i, token) in bar {} }() """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNoRemoveRequiredParensInsideClosure() { let input = "let foo = { _ in (a + b).c }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // before trailing closure @@ -669,55 +667,55 @@ class ParensTests: RulesTests { func testParensRemovedBeforeTrailingClosure() { let input = "var foo = bar() { /* some code */ }" let output = "var foo = bar { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedBeforeTrailingClosure2() { let input = "let foo = bar() { /* some code */ }" let output = "let foo = bar { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedBeforeTrailingClosure3() { let input = "var foo = bar() { /* some code */ }" let output = "var foo = bar { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedBeforeTrailingClosureInsideHashIf() { let input = "#if baz\n let foo = bar() { /* some code */ }\n#endif" let output = "#if baz\n let foo = bar { /* some code */ }\n#endif" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedBeforeVarBody() { let input = "var foo = bar() { didSet {} }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeFunctionBody() { let input = "func bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBody() { let input = "if let foo = bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBody2() { let input = "if try foo as Bar && baz() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.andOperator]) } func testParensNotRemovedBeforeIfBody3() { let input = "if #selector(foo(_:)) && bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.andOperator]) } func testParensNotRemovedBeforeIfBody4() { let input = "if let data = #imageLiteral(resourceName: \"abc.png\").pngData() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBody5() { @@ -726,84 +724,84 @@ class ParensTests: RulesTests { self?.products.accept(newProducts) } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBodyAfterTry() { let input = "if let foo = try bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeCompoundIfBody() { let input = "if let foo = bar(), let baz = quux() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeForBody() { let input = "for foo in bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeWhileBody() { let input = "while let foo = bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeCaseBody() { let input = "if case foo = bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeSwitchBody() { let input = "switch foo() {\ndefault: break\n}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAfterAnonymousClosureInsideIfStatementBody() { let input = "if let foo = bar(), { x == y }() {}" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.redundantClosure]) } func testParensNotRemovedInGenericInit() { let input = "init(_: T) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericInit2() { let input = "init() {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericFunction() { let input = "func foo(_: T) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericFunction2() { let input = "func foo() {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericInstantiation() { let input = "let foo = Foo()" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.propertyType]) } func testParensNotRemovedInGenericInstantiation2() { let input = "let foo = Foo(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.propertyType]) } func testRedundantParensRemovedAfterGenerics() { let input = "let foo: Foo\n(a) + b" let output = "let foo: Foo\na + b" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAfterGenerics2() { let input = "let foo: Foo\n(foo())" let output = "let foo: Foo\nfoo()" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // closure expression @@ -811,229 +809,229 @@ class ParensTests: RulesTests { func testParensAroundClosureRemoved() { let input = "let foo = ({ /* some code */ })" let output = "let foo = { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensAroundClosureAssignmentBlockRemoved() { let input = "let foo = ({ /* some code */ })()" let output = "let foo = { /* some code */ }()" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensAroundClosureInCompoundExpressionRemoved() { let input = "if foo == ({ /* some code */ }), let bar = baz {}" let output = "if foo == { /* some code */ }, let bar = baz {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundClosure() { let input = "if (foo { $0 }) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundClosure2() { let input = "if (foo.filter { $0 > 1 }.isEmpty) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundClosure3() { let input = "if let foo = (bar.filter { $0 > 1 }).first {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // around tuples func testsTupleNotUnwrapped() { let input = "tuple = (1, 2)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testsTupleOfClosuresNotUnwrapped() { let input = "tuple = ({}, {})" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testSwitchTupleNotUnwrapped() { let input = "switch (x, y) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensRemovedAroundTuple() { let input = "let foo = ((bar: Int, baz: String))" let output = "let foo = (bar: Int, baz: String)" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionArgument() { let input = "let foo = bar((bar: Int, baz: String))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionArgumentAfterSubscript() { let input = "bar[5]((bar: Int, baz: String))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNestedParensRemovedAroundTupleFunctionArgument() { let input = "let foo = bar(((bar: Int, baz: String)))" let output = "let foo = bar((bar: Int, baz: String))" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNestedParensRemovedAroundTupleFunctionArgument2() { let input = "let foo = bar(foo: ((bar: Int, baz: String)))" let output = "let foo = bar(foo: (bar: Int, baz: String))" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNestedParensRemovedAroundTupleOperands() { let input = "((1, 2)) == ((1, 2))" let output = "(1, 2) == (1, 2)" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionTypeDeclaration() { let input = "let foo: ((bar: Int, baz: String)) -> Void" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundUnlabelledTupleFunctionTypeDeclaration() { let input = "let foo: ((Int, String)) -> Void" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionTypeAssignment() { let input = "foo = ((bar: Int, baz: String)) -> Void { _ in }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundTupleFunctionTypeAssignment() { let input = "foo = ((((bar: Int, baz: String)))) -> Void { _ in }" let output = "foo = ((bar: Int, baz: String)) -> Void { _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundUnlabelledTupleFunctionTypeAssignment() { let input = "foo = ((Int, String)) -> Void { _ in }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundUnlabelledTupleFunctionTypeAssignment() { let input = "foo = ((((Int, String)))) -> Void { _ in }" let output = "foo = ((Int, String)) -> Void { _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundTupleArgument() { let input = "foo((bar, baz))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundVoidGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } func testParensNotRemovedAroundTupleGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } func testParensNotRemovedAroundLabeledTupleGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } // after indexed tuple func testParensNotRemovedAfterTupleIndex() { let input = "foo.1()" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAfterTupleIndex2() { let input = "foo.1(true)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAfterTupleIndex3() { let input = "foo.1((bar: Int, baz: String))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNestedParensRemovedAfterTupleIndex3() { let input = "foo.1(((bar: Int, baz: String)))" let output = "foo.1((bar: Int, baz: String))" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // inside string interpolation func testParensInStringNotRemoved() { let input = "\"hello \\(world)\"" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // around ranges func testParensAroundRangeNotRemoved() { let input = "(1 ..< 10).reduce(0, combine: +)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensRemovedAroundRangeArguments() { let input = "(a)...(b)" let output = "a...b" - testFormatting(for: input, output, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators"]) + testFormatting(for: input, output, rule: .redundantParens, + exclude: [.spaceAroundOperators]) } func testParensNotRemovedAroundRangeArgumentBeginningWithDot() { let input = "a...(.b)" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators]) } func testParensNotRemovedAroundTrailingRangeFollowedByDot() { let input = "(a...).b" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundRangeArgumentBeginningWithPrefixOperator() { let input = "a...(-b)" - testFormatting(for: input, rule: FormatRules.redundantParens, - exclude: ["spaceAroundOperators"]) + testFormatting(for: input, rule: .redundantParens, + exclude: [.spaceAroundOperators]) } func testParensRemovedAroundRangeArgumentBeginningWithDot() { let input = "a ... (.b)" let output = "a ... .b" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedAroundRangeArgumentBeginningWithPrefixOperator() { let input = "a ... (-b)" let output = "a ... -b" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // around ternaries func testParensNotRemovedAroundTernaryCondition() { let input = "let a = (b == c) ? d : e" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundTernaryAssignment() { let input = "a ? (b = c) : (b = d)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // around parameter repeat each func testRequiredParensNotRemovedAroundRepeat() { let input = "(repeat (each foo, each bar))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // in async expression @@ -1045,7 +1043,7 @@ class ParensTests: RulesTests { async let dataTask2: Void = someTask(request) } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedInAsyncLet2() { @@ -1054,6 +1052,6 @@ class ParensTests: RulesTests { let processURL: (URL) async throws -> Void = { _ in } } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } } diff --git a/Tests/Rules/RedundantPatternTests.swift b/Tests/Rules/RedundantPatternTests.swift new file mode 100644 index 00000000..6d0cef8d --- /dev/null +++ b/Tests/Rules/RedundantPatternTests.swift @@ -0,0 +1,55 @@ +// +// RedundantPatternTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/14/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantPatternTests: XCTestCase { + func testRemoveRedundantPatternInIfCase() { + let input = "if case let .foo(_, _) = bar {}" + let output = "if case .foo = bar {}" + testFormatting(for: input, output, rule: .redundantPattern) + } + + func testNoRemoveRequiredPatternInIfCase() { + let input = "if case (_, _) = bar {}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testRemoveRedundantPatternInSwitchCase() { + let input = "switch foo {\ncase let .bar(_, _): break\ndefault: break\n}" + let output = "switch foo {\ncase .bar: break\ndefault: break\n}" + testFormatting(for: input, output, rule: .redundantPattern) + } + + func testNoRemoveRequiredPatternLetInSwitchCase() { + let input = "switch foo {\ncase let .bar(_, a): break\ndefault: break\n}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testNoRemoveRequiredPatternInSwitchCase() { + let input = "switch foo {\ncase (_, _): break\ndefault: break\n}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testSimplifyLetPattern() { + let input = "let(_, _) = bar" + let output = "let _ = bar" + testFormatting(for: input, output, rule: .redundantPattern, exclude: [.redundantLet]) + } + + func testNoRemoveVoidFunctionCall() { + let input = "if case .foo() = bar {}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testNoRemoveMethodSignature() { + let input = "func foo(_, _) {}" + testFormatting(for: input, rule: .redundantPattern) + } +} diff --git a/Tests/Rules/RedundantPropertyTests.swift b/Tests/Rules/RedundantPropertyTests.swift new file mode 100644 index 00000000..9292231c --- /dev/null +++ b/Tests/Rules/RedundantPropertyTests.swift @@ -0,0 +1,182 @@ +// +// RedundantPropertyTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 6/9/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantPropertyTests: XCTestCase { + func testRemovesRedundantProperty() { + let input = """ + func foo() -> Foo { + let foo = Foo(bar: bar, baaz: baaz) + return foo + } + """ + + let output = """ + func foo() -> Foo { + return Foo(bar: bar, baaz: baaz) + } + """ + + testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) + } + + func testRemovesRedundantPropertyWithIfExpression() { + let input = """ + func foo() -> Foo { + let foo = + if condition { + Foo.foo() + } else { + Foo.bar() + } + + return foo + } + """ + + let output = """ + func foo() -> Foo { + if condition { + Foo.foo() + } else { + Foo.bar() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.redundantProperty, .redundantReturn, .indent], options: options) + } + + func testRemovesRedundantPropertyWithSwitchExpression() { + let input = """ + func foo() -> Foo { + let foo: Foo + switch condition { + case true: + foo = Foo(bar) + case false: + foo = Foo(baaz) + } + + return foo + } + """ + + let output = """ + func foo() -> Foo { + switch condition { + case true: + Foo(bar) + case false: + Foo(baaz) + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .redundantProperty, .redundantReturn, .indent], options: options) + } + + func testRemovesRedundantPropertyWithPreferInferredType() { + let input = """ + func bar() -> Bar { + let bar: Bar = .init(baaz: baaz, quux: quux) + return bar + } + """ + + let output = """ + func bar() -> Bar { + return Bar(baaz: baaz, quux: quux) + } + """ + + testFormatting(for: input, [output], rules: [.propertyType, .redundantProperty, .redundantInit], exclude: [.redundantReturn]) + } + + func testRemovesRedundantPropertyWithComments() { + let input = """ + func foo() -> Foo { + // There's a comment before this property + let foo = Foo(bar: bar, baaz: baaz) + // And there's a comment after the property + return foo + } + """ + + let output = """ + func foo() -> Foo { + // There's a comment before this property + return Foo(bar: bar, baaz: baaz) + // And there's a comment after the property + } + """ + + testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) + } + + func testRemovesRedundantPropertyFollowingOtherProperty() { + let input = """ + func foo() -> Foo { + let bar = Bar(baaz: baaz) + let foo = Foo(bar: bar) + return foo + } + """ + + let output = """ + func foo() -> Foo { + let bar = Bar(baaz: baaz) + return Foo(bar: bar) + } + """ + + testFormatting(for: input, output, rule: .redundantProperty) + } + + func testPreservesPropertyWhereReturnIsNotRedundant() { + let input = """ + func foo() -> Foo { + let foo = Foo(bar: bar, baaz: baaz) + return foo.with(quux: quux) + } + + func bar() -> Foo { + let bar = Bar(baaz: baaz) + return bar.baaz + } + + func baaz() -> Foo { + let bar = Bar(baaz: baaz) + print(bar) + return bar + } + """ + + testFormatting(for: input, rule: .redundantProperty) + } + + func testPreservesUnwrapConditionInIfStatement() { + let input = """ + func foo() -> Foo { + let foo = Foo(bar: bar, baaz: baaz) + + if let foo = foo.nestedFoo { + print(foo) + } + + return foo + } + """ + + testFormatting(for: input, rule: .redundantProperty) + } +} diff --git a/Tests/Rules/RedundantRawValuesTests.swift b/Tests/Rules/RedundantRawValuesTests.swift new file mode 100644 index 00000000..4d20f19f --- /dev/null +++ b/Tests/Rules/RedundantRawValuesTests.swift @@ -0,0 +1,37 @@ +// +// RedundantRawValuesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 12/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantRawValuesTests: XCTestCase { + func testRemoveRedundantRawString() { + let input = "enum Foo: String {\n case bar = \"bar\"\n case baz = \"baz\"\n}" + let output = "enum Foo: String {\n case bar\n case baz\n}" + testFormatting(for: input, output, rule: .redundantRawValues) + } + + func testRemoveCommaDelimitedCaseRawStringCases() { + let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }" + let output = "enum Foo: String { case bar, baz }" + testFormatting(for: input, output, rule: .redundantRawValues, + exclude: [.wrapEnumCases]) + } + + func testRemoveBacktickCaseRawStringCases() { + let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }" + let output = "enum Foo: String { case `as`, `let` }" + testFormatting(for: input, output, rule: .redundantRawValues, + exclude: [.wrapEnumCases]) + } + + func testNoRemoveRawStringIfNameDoesntMatch() { + let input = "enum Foo: String {\n case bar = \"foo\"\n}" + testFormatting(for: input, rule: .redundantRawValues) + } +} diff --git a/Tests/Rules/RedundantReturnTests.swift b/Tests/Rules/RedundantReturnTests.swift new file mode 100644 index 00000000..cfe6418d --- /dev/null +++ b/Tests/Rules/RedundantReturnTests.swift @@ -0,0 +1,1311 @@ +// +// RedundantReturnTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/7/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantReturnTests: XCTestCase { + func testRemoveRedundantReturnInClosure() { + let input = "foo(with: { return 5 })" + let output = "foo(with: { 5 })" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) + } + + func testRemoveRedundantReturnInClosureWithArgs() { + let input = "foo(with: { foo in return foo })" + let output = "foo(with: { foo in foo })" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) + } + + func testRemoveRedundantReturnInMap() { + let input = "let foo = bar.map { return 1 }" + let output = "let foo = bar.map { 1 }" + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testNoRemoveReturnInComputedVar() { + let input = "var foo: Int { return 5 }" + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveReturnInComputedVar() { + let input = "var foo: Int { return 5 }" + let output = "var foo: Int { 5 }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInGet() { + let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveReturnInGet() { + let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" + let output = "var foo: Int {\n get { 5 }\n set { _foo = newValue }\n}" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInGetClosure() { + let input = "let foo = get { return 5 }" + let output = "let foo = get { 5 }" + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testRemoveReturnInVarClosure() { + let input = "var foo = { return 5 }()" + let output = "var foo = { 5 }()" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantClosure]) + } + + func testRemoveReturnInParenthesizedClosure() { + let input = "var foo = ({ return 5 }())" + let output = "var foo = ({ 5 }())" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantParens, .redundantClosure]) + } + + func testNoRemoveReturnInFunction() { + let input = "func foo() -> Int { return 5 }" + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveReturnInFunction() { + let input = "func foo() -> Int { return 5 }" + let output = "func foo() -> Int { 5 }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInOperatorFunction() { + let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) + } + + func testRemoveReturnInOperatorFunction() { + let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" + let output = "func + (lhs: Int, rhs: Int) -> Int { 5 }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options, + exclude: [.unusedArguments]) + } + + func testNoRemoveReturnInFailableInit() { + let input = "init?() { return nil }" + testFormatting(for: input, rule: .redundantReturn) + } + + func testNoRemoveReturnInFailableInitWithConditional() { + let input = """ + init?(optionalHex: String?) { + if let optionalHex { + self.init(hex: optionalHex) + } else { + return nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInFailableInitWithNestedConditional() { + let input = """ + init?(optionalHex: String?) { + if let optionalHex { + self.init(hex: optionalHex) + } else { + switch foo { + case .foo: + self.init() + case .bar: + return nil + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRemoveReturnInFailableInit() { + let input = "init?() { return nil }" + let output = "init?() { nil }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInSubscript() { + let input = "subscript(index: Int) -> String { return nil }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) + } + + func testRemoveReturnInSubscript() { + let input = "subscript(index: Int) -> String { return nil }" + let output = "subscript(index: Int) -> String { nil }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options, + exclude: [.unusedArguments]) + } + + func testNoRemoveReturnInDoCatch() { + let input = """ + func foo() -> Int { + do { + return try Bar() + } catch { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInDoThrowsCatch() { + let input = """ + func foo() -> Int { + do throws(Foo) { + return try Bar() + } catch { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInDoCatchLet() { + let input = """ + func foo() -> Int { + do { + return try Bar() + } catch let e as Error { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInDoThrowsCatchLet() { + let input = """ + func foo() -> Int { + do throws(Foo) { + return try Bar() + } catch let e as Error { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInForIn() { + let input = "for foo in bar { return 5 }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveReturnInForWhere() { + let input = "for foo in bar where baz { return 5 }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveReturnInIfLetTry() { + let input = "if let foo = try? bar() { return 5 }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnInMultiIfLetTry() { + let input = "if let foo = bar, let bar = baz { return 5 }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnAfterMultipleAs() { + let input = "if foo as? bar as? baz { return 5 }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testRemoveVoidReturn() { + let input = "{ _ in return }" + let output = "{ _ in }" + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testNoRemoveReturnAfterKeyPath() { + let input = "func foo() { if bar == #keyPath(baz) { return 5 } }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnAfterParentheses() { + let input = "if let foo = (bar as? String) { return foo }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.redundantParens, .wrapConditionalBodies]) + } + + func testRemoveReturnInTupleVarGetter() { + let input = "var foo: (Int, Int) { return (1, 2) }" + let output = "var foo: (Int, Int) { (1, 2) }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInIfLetWithNoSpaceAfterParen() { + let input = """ + var foo: String? { + if let bar = baz(){ + return bar + } else { + return nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.spaceAroundBraces, .spaceAroundParens]) + } + + func testNoRemoveReturnInIfWithUnParenthesizedClosure() { + let input = """ + if foo { $0.bar } { + return true + } + """ + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveBlankLineWithReturn() { + let input = """ + foo { + return + bar + } + """ + let output = """ + foo { + bar + } + """ + testFormatting(for: input, output, rule: .redundantReturn, + exclude: [.indent]) + } + + func testRemoveRedundantReturnInFunctionWithWhereClause() { + let input = """ + func foo(_ name: String) -> T where T: Equatable { + return name + } + """ + let output = """ + func foo(_ name: String) -> T where T: Equatable { + name + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, + options: options) + } + + func testRemoveRedundantReturnInSubscriptWithWhereClause() { + let input = """ + subscript(_ name: String) -> T where T: Equatable { + return name + } + """ + let output = """ + subscript(_ name: String) -> T where T: Equatable { + name + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, + options: options) + } + + func testNoRemoveReturnFollowedByMoreCode() { + let input = """ + var foo: Bar = { + return foo + let bar = baz + return bar + }() + """ + testFormatting(for: input, rule: .redundantReturn, exclude: [.redundantProperty]) + } + + func testNoRemoveReturnInForWhereLoop() { + let input = """ + func foo() -> Bool { + for bar in baz where !bar { + return false + } + return true + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantReturnInVoidFunction() { + let input = """ + func foo() { + return + } + """ + let output = """ + func foo() { + } + """ + testFormatting(for: input, output, rule: .redundantReturn, + exclude: [.emptyBraces]) + } + + func testRedundantReturnInVoidFunction2() { + let input = """ + func foo() { + print("") + return + } + """ + let output = """ + func foo() { + print("") + } + """ + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testRedundantReturnInVoidFunction3() { + let input = """ + func foo() { + // empty + return + } + """ + let output = """ + func foo() { + // empty + } + """ + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testRedundantReturnInVoidFunction4() { + let input = """ + func foo() { + return // empty + } + """ + let output = """ + func foo() { + // empty + } + """ + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testNoRemoveVoidReturnInCatch() { + let input = """ + func foo() { + do { + try Foo() + } catch Feature.error { + print("feature error") + return + } + print("foo") + } + """ + testFormatting(for: input, rule: .redundantReturn) + } + + func testNoRemoveReturnInIfCase() { + let input = """ + var isSessionDeinitializedError: Bool { + if case .sessionDeinitialized = self { return true } + return false + } + """ + testFormatting(for: input, rule: .redundantReturn, + options: FormatOptions(swiftVersion: "5.1"), + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnInForCasewhere() { + let input = """ + for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] + where names.contains(name) + { + return true + } + """ + testFormatting(for: input, rule: .redundantReturn, + options: FormatOptions(swiftVersion: "5.1")) + } + + func testNoRemoveRequiredReturnInFunctionInsideClosure() { + let input = """ + foo { + func bar() -> Bar { + let bar = Bar() + return bar + } + } + """ + testFormatting(for: input, rule: .redundantReturn, + options: FormatOptions(swiftVersion: "5.1"), exclude: [.redundantProperty]) + } + + func testNoRemoveRequiredReturnInIfClosure() { + let input = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { + return btn + } + return btns.first + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveRequiredReturnInIfClosure2() { + let input = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let foo, let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { + return btn + } + return btns.first + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRemoveRedundantReturnInIfClosure() { + let input = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let btn = btns.first { return !$0.isHidden && $0.alpha > 0.01 } { + print("hello") + } + return btns.first + } + """ + let output = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { + print("hello") + } + return btns.first + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testDisableNextRedundantReturn() { + let input = """ + func foo() -> Foo { + // swiftformat:disable:next redundantReturn + return Foo() + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantIfStatementReturnSwift5_8() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else { + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantReturn, + options: options) + } + + func testNonRedundantIfStatementReturnSwift5_9() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else if !condition { + return "bar" + } + return "baaz" + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantIfStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else if otherCondition { + if anotherCondition { + return "bar" + } else { + return "baaz" + } + } else { + return "quux" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + if condition { + "foo" + } else if otherCondition { + if anotherCondition { + "bar" + } else { + "baaz" + } + } else { + "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveRedundantIfStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else if otherCondition { + if anotherCondition { + return "bar" + } else { + return "baaz" + } + } else { + return "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.conditionalAssignment]) + } + + func testRedundantIfStatementReturnInClosure() { + let input = """ + let closure: (Bool) -> String = { condition in + if condition { + return "foo" + } else { + return "bar" + } + } + """ + let output = """ + let closure: (Bool) -> String = { condition in + if condition { + "foo" + } else { + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveRedundantIfStatementReturnInClosure() { + let input = """ + let closure: (Bool) -> String = { condition in + if condition { + return "foo" + } else { + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.conditionalAssignment]) + } + + func testNoRemoveReturnInConsecutiveIfStatements() { + let input = """ + func foo() -> String? { + if bar { + return nil + } + if baz { + return "baz" + } else { + return "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantIfStatementReturnInRedundantClosure() { + let input = """ + let value = { + if condition { + return "foo" + } else { + return "bar" + } + }() + """ + let output = """ + let value = if condition { + "foo" + } else { + "bar" + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure, .indent], + options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + case false: + return "bar" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + "foo" + case false: + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveRedundantSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + case false: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.conditionalAssignment]) + } + + func testNonRedundantSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + case false: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithDefault() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + default: + return "bar" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + "foo" + default: + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithComment() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + // foo + return "foo" + + default: + /* bar */ + return "bar" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + // foo + "foo" + + default: + /* bar */ + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNonRedundantSwitchStatementReturnInFunctionWithDefault() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + default: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNonRedundantSwitchStatementReturnInFunctionWithFallthrough() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + fallthrough + case false: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testVoidReturnNotStrippedFromSwitch() { + let input = """ + func foo(condition: Bool) { + switch condition { + case true: + print("foo") + case false: + return + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantNestedSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + switch condition { + case true: + return "foo" + case false: + if condition { + return "bar" + } else { + return "baaz" + } + } + + case false: + return "quux" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + switch condition { + case true: + "foo" + case false: + if condition { + "bar" + } else { + "baaz" + } + } + + case false: + "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantSwitchStatementReturnWithAssociatedValueMatchingInFunction() { + let input = """ + func test(_ value: SomeEnum) -> String { + switch value { + case let .first(str): + return "first \\(str)" + case .second("str"): + return "second" + default: + return "default" + } + } + """ + let output = """ + func test(_ value: SomeEnum) -> String { + switch value { + case let .first(str): + "first \\(str)" + case .second("str"): + "second" + default: + "default" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantReturnDoesntFailToTerminateOnLongSwitch() { + let input = """ + func test(_ value: SomeEnum) -> String { + switch value { + case .one: + return "" + case .two: + return "" + case .three: + return "" + case .four: + return "" + case .five: + return "" + case .six: + return "" + case .seven: + return "" + case .eight: + return "" + case .nine: + return "" + case .ten: + return "" + case .eleven: + return "" + case .twelve: + return "" + case .thirteen: + return "" + case .fourteen: + return "" + case .fifteen: + return "" + case .sixteen: + return "" + case .seventeen: + return "" + case .eighteen: + return "" + case .nineteen: + return "" + } + } + """ + let output = """ + func test(_ value: SomeEnum) -> String { + switch value { + case .one: + "" + case .two: + "" + case .three: + "" + case .four: + "" + case .five: + "" + case .six: + "" + case .seven: + "" + case .eight: + "" + case .nine: + "" + case .ten: + "" + case .eleven: + "" + case .twelve: + "" + case .thirteen: + "" + case .fourteen: + "" + case .fifteen: + "" + case .sixteen: + "" + case .seventeen: + "" + case .eighteen: + "" + case .nineteen: + "" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveDebugReturnFollowedBySwitch() { + let input = """ + func swiftFormatBug() -> Foo { + return .foo + + switch state { + case .foo, .bar: + return state + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testDoesntRemoveReturnFromIfExpressionConditionalCastInSwift5_9() { + // The following code doesn't compile in Swift 5.9 due to this issue: + // https://github.com/apple/swift/issues/68764 + // + // var result: String { + // if condition { + // foo as? String + // } else { + // "bar" + // } + // } + // + let input = """ + var result1: String { + if condition { + return foo as? String + } else { + return "bar" + } + } + + var result2: String { + switch condition { + case true: + return foo as? String + case false: + return "bar" + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRemovseReturnFromIfExpressionNestedConditionalCastInSwift5_9() { + let input = """ + var result1: String { + if condition { + return method(foo as? String) + } else { + return "bar" + } + } + """ + + let output = """ + var result1: String { + if condition { + method(foo as? String) + } else { + "bar" + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRemovesReturnFromIfExpressionConditionalCastInSwift5_10() { + let input = """ + var result: String { + if condition { + return foo as? String + } else { + return "bar" + } + } + """ + + let output = """ + var result: String { + if condition { + foo as? String + } else { + "bar" + } + } + """ + + let options = FormatOptions(swiftVersion: "5.10") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRemovesRedundantReturnBeforeIfExpression() { + let input = """ + func foo() -> Foo { + return if condition { + Foo.foo() + } else { + Foo.bar() + } + } + """ + + let output = """ + func foo() -> Foo { + if condition { + Foo.foo() + } else { + Foo.bar() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testRemovesRedundantReturnBeforeSwitchExpression() { + let input = """ + func foo() -> Foo { + return switch condition { + case true: + Foo.foo() + case false: + Foo.bar() + } + } + """ + + let output = """ + func foo() -> Foo { + switch condition { + case true: + Foo.foo() + case false: + Foo.bar() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithMultipleWhereClauses() { + // https://github.com/nicklockwood/SwiftFormat/issues/1554 + let input = """ + func foo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + return "foo" + case .fooCase2 where count < 100, + .fooCase3 where count < 100, + .fooCase4: + return "bar" + default: + return nil + } + } + """ + let output = """ + func foo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + "foo" + case .fooCase2 where count < 100, + .fooCase3 where count < 100, + .fooCase4: + "bar" + default: + nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithSingleWhereClause() { + // https://github.com/nicklockwood/SwiftFormat/issues/1554 + let input = """ + func anotherFoo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + return "foo" + case .fooCase2 where count < 100, + .fooCase4: + return "bar" + default: + return nil + } + } + """ + let output = """ + func anotherFoo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + "foo" + case .fooCase2 where count < 100, + .fooCase4: + "bar" + default: + nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testReturnNotRemovedFromSwitchBodyWithOpaqueReturnType() { + // https://github.com/nicklockwood/SwiftFormat/issues/1819 + let input = """ + extension View { + func foo() -> some View { + if #available(iOS 16.0, *) { + return self.scrollIndicators(.hidden) + } else { + return self + } + } + + func bar() -> (some View) { + if #available(iOS 16.0, *) { + return self.scrollIndicators(.hidden) + } else { + return self + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testReturnNotRemovedFromCatchWhere() { + // https://github.com/nicklockwood/SwiftFormat/issues/1843 + let input = """ + func decodeError(from data: Data, urlResponse: HTTPURLResponse) -> Error { + do { + let decoder = JSONDecoder() + return try decoder.decode(T.self, from: data) + } catch where urlResponse.statusCode >= 400 { + return CustomError() // <- return removed here, introducing a compile time error. + } catch { + return error + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } +} diff --git a/Tests/Rules/RedundantSelfTests.swift b/Tests/Rules/RedundantSelfTests.swift new file mode 100644 index 00000000..4ef3f378 --- /dev/null +++ b/Tests/Rules/RedundantSelfTests.swift @@ -0,0 +1,3393 @@ +// +// RedundantSelfTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/13/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantSelfTests: XCTestCase { + // explicitSelf = .remove + + func testSimpleRemoveRedundantSelf() { + let input = "func foo() { self.bar() }" + let output = "func foo() { bar() }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideStringInterpolation() { + let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" + let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfForArgument() { + let input = "func foo(bar: Int) { self.bar = bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForLocalVariable() { + let input = "func foo() { var bar = self.bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfForLocalVariableOn5_4() { + let input = "func foo() { var bar = self.bar }" + let output = "func foo() { var bar = bar }" + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testNoRemoveSelfForCommaDelimitedLocalVariables() { + let input = "func foo() { let foo = self.foo, bar = self.bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfForCommaDelimitedLocalVariablesOn5_4() { + let input = "func foo() { let foo = self.foo, bar = self.bar }" + let output = "func foo() { let foo = self.foo, bar = bar }" + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testNoRemoveSelfForCommaDelimitedLocalVariables2() { + let input = "func foo() {\n let foo: Foo, bar: Bar\n foo = self.foo\n bar = self.bar\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForTupleAssignedVariables() { + let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" + testFormatting(for: input, rule: .redundantSelf) + } + + // TODO: make this work +// func testRemoveSelfForTupleAssignedVariablesOn5_4() { +// let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" +// let output = "func foo() { let (bar, baz) = (bar, baz) }" +// let options = FormatOptions(swiftVersion: "5.4") +// testFormatting(for: input, output, rule: .redundantSelf, +// options: options) +// } + + func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularVariable() { + let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar), baz = self.baz\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularLet() { + let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar)\n let baz = self.baz\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveNonRedundantNestedFunctionSelf() { + let input = "func foo() { func bar() { self.bar() } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveNonRedundantNestedFunctionSelf2() { + let input = "func foo() {\n func bar() {}\n self.bar()\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveNonRedundantNestedFunctionSelf3() { + let input = "func foo() { let bar = 5; func bar() { self.bar = bar } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveClosureSelf() { + let input = "func foo() { bar { self.bar = 5 } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfAfterOptionalReturn() { + let input = "func foo() -> String? {\n var index = startIndex\n if !matching(self[index]) {\n break\n }\n index = self.index(after: index)\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveRequiredSelfInExtensions() { + let input = "extension Foo {\n func foo() {\n var index = 5\n if true {\n break\n }\n index = self.index(after: index)\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfBeforeInit() { + let input = "convenience init() { self.init(5) }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfInsideSwitch() { + let input = "func foo() {\n switch self.bar {\n case .foo:\n self.baz()\n }\n}" + let output = "func foo() {\n switch bar {\n case .foo:\n baz()\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideSwitchWhere() { + let input = "func foo() {\n switch self.bar {\n case .foo where a == b:\n self.baz()\n }\n}" + let output = "func foo() {\n switch bar {\n case .foo where a == b:\n baz()\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideSwitchWhereAs() { + let input = "func foo() {\n switch self.bar {\n case .foo where a == b as C:\n self.baz()\n }\n}" + let output = "func foo() {\n switch bar {\n case .foo where a == b as C:\n baz()\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideClassInit() { + let input = "class Foo {\n var bar = 5\n init() { self.bar = 6 }\n}" + let output = "class Foo {\n var bar = 5\n init() { bar = 6 }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureInsideIf() { + let input = "if foo { bar { self.baz() } }" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveSelfForErrorInCatch() { + let input = "do {} catch { self.error = error }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForErrorInDoThrowsCatch() { + let input = "do throws(Foo) {} catch { self.error = error }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForNewValueInSet() { + let input = "var foo: Int { set { self.newValue = newValue } get { return 0 } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForCustomNewValueInSet() { + let input = "var foo: Int { set(n00b) { self.n00b = n00b } get { return 0 } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForNewValueInWillSet() { + let input = "var foo: Int { willSet { self.newValue = newValue } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForCustomNewValueInWillSet() { + let input = "var foo: Int { willSet(n00b) { self.n00b = n00b } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForOldValueInDidSet() { + let input = "var foo: Int { didSet { self.oldValue = oldValue } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForCustomOldValueInDidSet() { + let input = "var foo: Int { didSet(oldz) { self.oldz = oldz } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForIndexVarInFor() { + let input = "for foo in bar { self.foo = foo }" + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveSelfForKeyValueTupleInFor() { + let input = "for (foo, bar) in baz { self.foo = foo; self.bar = bar }" + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testRemoveSelfFromComputedVar() { + let input = "var foo: Int { return self.bar }" + let output = "var foo: Int { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromOptionalComputedVar() { + let input = "var foo: Int? { return self.bar }" + let output = "var foo: Int? { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromNamespacedComputedVar() { + let input = "var foo: Swift.String { return self.bar }" + let output = "var foo: Swift.String { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromGenericComputedVar() { + let input = "var foo: Foo { return self.bar }" + let output = "var foo: Foo { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromComputedArrayVar() { + let input = "var foo: [Int] { return self.bar }" + let output = "var foo: [Int] { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromVarSetter() { + let input = "var foo: Int { didSet { self.bar() } }" + let output = "var foo: Int { didSet { bar() } }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarClosure() { + let input = "var foo = { self.bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromLazyVar() { + let input = "lazy var foo = self.bar" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfFromLazyVar() { + let input = "lazy var foo = self.bar" + let output = "lazy var foo = bar" + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { + let input = """ + var baz = bar + lazy var foo = self.bar + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { + let input = """ + var baz = bar + lazy var foo = self.bar + """ + let output = """ + var baz = bar + lazy var foo = bar + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfFromLazyVarClosure() { + let input = "lazy var foo = { self.bar }()" + testFormatting(for: input, rule: .redundantSelf, exclude: [.redundantClosure]) + } + + func testNoRemoveSelfFromLazyVarClosure2() { + let input = "lazy var foo = { let bar = self.baz }()" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromLazyVarClosure3() { + let input = "lazy var foo = { [unowned self] in let bar = self.baz }()" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfFromVarInFuncWithUnusedArgument() { + let input = "func foo(bar _: Int) { self.baz = 5 }" + let output = "func foo(bar _: Int) { baz = 5 }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromVarMatchingUnusedArgument() { + let input = "func foo(bar _: Int) { self.bar = 5 }" + let output = "func foo(bar _: Int) { bar = 5 }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarMatchingRenamedArgument() { + let input = "func foo(bar baz: Int) { self.baz = baz }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarRedeclaredInSubscope() { + let input = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = self.bar\n}" + let output = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = bar\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarDeclaredLaterInScope() { + let input = "func foo() {\n let bar = self.baz\n let baz = quux\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarDeclaredLaterInOuterScope() { + let input = "func foo() {\n if quux {\n let bar = self.baz\n }\n let baz = 6\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInWhilePreceededByVarDeclaration() { + let input = "var index = start\nwhile index < end {\n index = self.index(after: index)\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInLocalVarPrecededByLocalVarFollowedByIfComma() { + let input = "func foo() {\n let bar = Bar()\n let baz = Baz()\n self.baz = baz\n if let bar = bar, bar > 0 {}\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInLocalVarPrecededByIfLetContainingClosure() { + let input = "func foo() {\n if let bar = 5 { baz { _ in } }\n let quux = self.quux\n}" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveSelfForVarCreatedInGuardScope() { + let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testRemoveSelfForVarCreatedInIfScope() { + let input = "func foo() {\n if let bar = bar {}\n let baz = self.bar\n}" + let output = "func foo() {\n if let bar = bar {}\n let baz = bar\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfForVarDeclaredInWhileCondition() { + let input = "while let foo = bar { self.foo = foo }" + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testRemoveSelfForVarNotDeclaredInWhileCondition() { + let input = "while let foo == bar { self.baz = 5 }" + let output = "while let foo == bar { baz = 5 }" + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveSelfForVarDeclaredInSwitchCase() { + let input = "switch foo {\ncase bar: let baz = self.baz\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfAfterGenericInit() { + let input = "init(bar: Int) {\n self = Foo()\n self.bar(bar)\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfInClassFunction() { + let input = "class Foo {\n class func foo() {\n func bar() { self.foo() }\n }\n}" + let output = "class Foo {\n class func foo() {\n func bar() { foo() }\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInStaticFunction() { + let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}" + let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.enumNamespaces]) + } + + func testRemoveSelfInClassFunctionWithModifiers() { + let input = "class Foo {\n class private func foo() {\n func bar() { self.foo() }\n }\n}" + let output = "class Foo {\n class private func foo() {\n func bar() { foo() }\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf, + exclude: [.modifierOrder]) + } + + func testNoRemoveSelfInClassFunction() { + let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { self.foo() }\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForVarDeclaredAfterRepeatWhile() { + let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n let foo = 6\n self.foo()\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForVarInClosureAfterRepeatWhile() { + let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n ({ self.foo() })()\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterVar() { + let input = "var foo: String\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterNamespacedVar() { + let input = "var foo: Swift.String\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterOptionalVar() { + let input = "var foo: String?\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterGenericVar() { + let input = "var foo: Foo\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterArray() { + let input = "var foo: [Int]\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInExpectFunction() { // Special case to support the Nimble framework + let input = """ + class FooTests: XCTestCase { + let foo = 1 + func testFoo() { + expect(self.foo) == 1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveNestedSelfInExpectFunction() { + let input = """ + func testFoo() { + expect(Foo.validate(bar: self.bar)).to(equal(1)) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveNestedSelfInArrayInExpectFunction() { + let input = """ + func testFoo() { + expect(Foo.validate(bar: [self.bar])).to(equal(1)) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveNestedSelfInSubscriptInExpectFunction() { + let input = """ + func testFoo() { + expect(Foo.validations[self.bar]).to(equal(1)) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInOSLogFunction() { + let input = """ + func testFoo() { + os_log("error: \\(self.bar) is nil") + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInExcludedFunction() { + let input = """ + class Foo { + let foo = 1 + func testFoo() { + log(self.foo) + } + } + """ + let options = FormatOptions(selfRequired: ["log"]) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfForExcludedFunction() { + let input = """ + class Foo { + let foo = 1 + func testFoo() { + self.log(foo) + } + } + """ + let options = FormatOptions(selfRequired: ["log"]) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInInterpolatedStringInExcludedFunction() { + let input = """ + class Foo { + let foo = 1 + func testFoo() { + log("\\(self.foo)") + } + } + """ + let options = FormatOptions(selfRequired: ["log"]) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInExcludedInitializer() { + let input = """ + let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) + """ + let options = FormatOptions(selfRequired: ["InspectionView"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.propertyType]) + } + + func testSelfRemovedFromSwitchCaseWhere() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where self.bar.baz: + return self.bar + default: + return nil + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where bar.baz: + return bar + default: + return nil + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testSwitchCaseLetVarRecognized() { + let input = """ + switch foo { + case .bar: + baz = nil + case let baz: + self.baz = baz + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSwitchCaseHoistedLetVarRecognized() { + let input = """ + switch foo { + case .bar: + baz = nil + case let .foo(baz): + self.baz = baz + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSwitchCaseWhereMemberNotTreatedAsVar() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar where self.bar.baz: + return self.bar + default: + return nil + } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInClosureAfterSwitch() { + let input = """ + switch x { + default: + break + } + let foo = { y in + switch y { + default: + self.bar() + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInClosureInCaseWithWhereClause() { + let input = """ + switch foo { + case bar where baz: + quux = { self.foo } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovedInDidSet() { + let input = """ + class Foo { + var bar = false { + didSet { + self.bar = !self.bar + } + } + } + """ + let output = """ + class Foo { + var bar = false { + didSet { + bar = !bar + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testSelfNotRemovedInGetter() { + let input = """ + class Foo { + var bar: Int { + return self.bar + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInIfdef() { + let input = """ + func foo() { + #if os(macOS) + let bar = self.bar + #endif + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfRemovedWhenFollowedBySwitchContainingIfdef() { + let input = """ + struct Foo { + func bar() { + self.method(self.value) + switch x { + #if BAZ + case .baz: + break + #endif + default: + break + } + } + } + """ + let output = """ + struct Foo { + func bar() { + method(value) + switch x { + #if BAZ + case .baz: + break + #endif + default: + break + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfRemovedInsideConditionalCase() { + let input = """ + struct Foo { + func bar() { + let method2 = () -> Void + switch x { + #if BAZ + case .baz: + self.method1(self.value) + #else + case .quux: + self.method2(self.value) + #endif + default: + break + } + } + } + """ + let output = """ + struct Foo { + func bar() { + let method2 = () -> Void + switch x { + #if BAZ + case .baz: + method1(value) + #else + case .quux: + self.method2(value) + #endif + default: + break + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfRemovedAfterConditionalLet() { + let input = """ + class Foo { + var bar: Int? + var baz: Bool + + func foo() { + if let bar = bar, self.baz { + // ... + } + } + } + """ + let output = """ + class Foo { + var bar: Int? + var baz: Bool + + func foo() { + if let bar = bar, baz { + // ... + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNestedClosureInNotMistakenForForLoop() { + let input = """ + func f() { + let str = "hello" + try! str.withCString(encodedAs: UTF8.self) { _ throws in + try! str.withCString(encodedAs: UTF8.self) { _ throws in } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testTypedThrowingNestedClosureInNotMistakenForForLoop() { + let input = """ + func f() { + let str = "hello" + try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in + try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { [self] in + print(self.bar) + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRemovesSelfInClosureWithExplicitStrongCapture() { + let input = """ + class Foo { + let foo: Int + + func baaz() { + closure { [self, bar] baaz, quux in + print(self.foo) + } + } + } + """ + + let output = """ + class Foo { + let foo: Int + + func baaz() { + closure { [self, bar] baaz, quux in + print(foo) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: [.unusedArguments]) + } + + func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { + print(self.bar) + closure { [self] in + print(self.bar) + } + print(self.bar) + } + } + } + """ + + let output = """ + class Foo { + let bar: Int + + func baaz() { + closure { + print(self.bar) + closure { [self] in + print(bar) + } + print(self.bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfKeepsSelfInNestedClosureWithNoExplicitStrongCapture() { + let input = """ + class Foo { + let bar: Int + let baaz: Int? + + func baaz() { + closure { [self] in + print(self.bar) + closure { + print(self.bar) + if let baaz = self.baaz { + print(baaz) + } + } + print(self.bar) + if let baaz = self.baaz { + print(baaz) + } + } + } + } + """ + + let output = """ + class Foo { + let bar: Int + let baaz: Int? + + func baaz() { + closure { [self] in + print(bar) + closure { + print(self.bar) + if let baaz = self.baaz { + print(baaz) + } + } + print(bar) + if let baaz = baaz { + print(baaz) + } + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRemovesSelfInClosureCapturingStruct() { + let input = """ + struct Foo { + let bar: Int + + func baaz() { + closure { + print(self.bar) + } + } + } + """ + + let output = """ + struct Foo { + let bar: Int + + func baaz() { + closure { + print(bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRemovesSelfInClosureCapturingSelfWeakly() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { [weak self] in + print(self?.bar) + guard let self else { + return + } + print(self.bar) + closure { + print(self.bar) + } + closure { [self] in + print(self.bar) + } + print(self.bar) + } + + closure { [weak self] in + guard let self = self else { + return + } + + print(self.bar) + } + + closure { [weak self] in + guard let self = self ?? somethingElse else { + return + } + + print(self.bar) + } + } + } + """ + + let output = """ + class Foo { + let bar: Int + + func baaz() { + closure { [weak self] in + print(self?.bar) + guard let self else { + return + } + print(bar) + closure { + print(self.bar) + } + closure { [self] in + print(bar) + } + print(bar) + } + + closure { [weak self] in + guard let self = self else { + return + } + + print(bar) + } + + closure { [weak self] in + guard let self = self ?? somethingElse else { + return + } + + print(self.bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, output, rule: .redundantSelf, + options: options, exclude: [.redundantOptionalBinding]) + } + + func testWeakSelfNotRemovedIfNotUnwrapped() { + let input = """ + class A { + weak var delegate: ADelegate? + + func testFunction() { + DispatchQueue.main.async { [weak self] in + self.flatMap { $0.delegate?.aDidSomething($0) } + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testClosureParameterListShadowingPropertyOnSelf() { + let input = """ + class Foo { + var bar = "bar" + + func method() { + closure { [self] bar in + self.bar = bar + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testClosureParameterListShadowingPropertyOnSelfInStruct() { + let input = """ + struct Foo { + var bar = "bar" + + func method() { + closure { bar in + self.bar = bar + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testClosureCaptureListShadowingPropertyOnSelf() { + let input = """ + class Foo { + var bar = "bar" + var baaz = "baaz" + + func method() { + closure { [self, bar, baaz = bar] in + self.bar = bar + self.baaz = baaz + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfKeepsSelfInClosureCapturingSelfWeaklyBefore5_8() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { [weak self] in + print(self?.bar) + guard let self else { + return + } + print(self.bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNonRedundantSelfNotRemovedAfterConditionalLet() { + let input = """ + class Foo { + var bar: Int? + var baz: Bool + + func foo() { + let baz = 5 + if let bar = bar, self.baz { + // ... + } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfDoesntGetStuckIfNoParensFound() { + let input = "init_ foo: T {}" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.spaceAroundOperators]) + } + + func testNoRemoveSelfInIfLetSelf() { + let input = """ + func foo() { + if let self = self as? Foo { + self.bar() + } + self.bar() + } + """ + let output = """ + func foo() { + if let self = self as? Foo { + self.bar() + } + bar() + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfInIfLetEscapedSelf() { + let input = """ + func foo() { + if let `self` = self as? Foo { + self.bar() + } + self.bar() + } + """ + let output = """ + func foo() { + if let `self` = self as? Foo { + self.bar() + } + bar() + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfAfterGuardLetSelf() { + let input = """ + func foo() { + guard let self = self as? Foo else { + return + } + self.bar() + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureInIfCondition() { + let input = """ + class Foo { + func foo() { + if bar({ self.baz() }) {} + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInTrailingClosureInVarAssignment() { + let input = """ + func broken() { + var bad = abc { + self.foo() + self.bar + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedWhenPropertyIsKeyword() { + let input = """ + class Foo { + let `default` = 5 + func foo() { + print(self.default) + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedWhenPropertyIsContextualKeyword() { + let input = """ + class Foo { + let `self` = 5 + func foo() { + print(self.self) + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovedForContextualKeywordThatRequiresNoEscaping() { + let input = """ + class Foo { + let get = 5 + func foo() { + print(self.get) + } + } + """ + let output = """ + class Foo { + let get = 5 + func foo() { + print(get) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfForMemberNamedLazy() { + let input = "func foo() { self.lazy() }" + let output = "func foo() { lazy() }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveRedundantSelfInArrayLiteral() { + let input = """ + class Foo { + func foo() { + print([self.bar.x, self.bar.y]) + } + } + """ + let output = """ + class Foo { + func foo() { + print([bar.x, bar.y]) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveRedundantSelfInArrayLiteralVar() { + let input = """ + class Foo { + func foo() { + var bars = [self.bar.x, self.bar.y] + print(bars) + } + } + """ + let output = """ + class Foo { + func foo() { + var bars = [bar.x, bar.y] + print(bars) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveRedundantSelfInGuardLet() { + let input = """ + class Foo { + func foo() { + guard let bar = self.baz else { + return + } + } + } + """ + let output = """ + class Foo { + func foo() { + guard let bar = baz else { + return + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testSelfNotRemovedInClosureInIf() { + let input = """ + if let foo = bar(baz: { [weak self] in + guard let self = self else { return } + _ = self.myVar + }) {} + """ + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testStructSelfRemovedInTrailingClosureInIfCase() { + let input = """ + struct A { + func doSomething() { + B.method { mode in + if case .edit = mode { + self.doA() + } else { + self.doB() + } + } + } + + func doA() {} + func doB() {} + } + """ + let output = """ + struct A { + func doSomething() { + B.method { mode in + if case .edit = mode { + doA() + } else { + doB() + } + } + } + + func doA() {} + func doB() {} + } + """ + testFormatting(for: input, output, rule: .redundantSelf, + options: FormatOptions(swiftVersion: "5.8")) + } + + func testSelfNotRemovedInDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo { + subscript(dynamicMember foo: String) -> String { + foo + "bar" + } + + func bar() { + if self.foo == "foobar" { + return + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotRemovedInDeclarationWithDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo { + subscript(dynamicMember foo: String) -> String { + foo + "bar" + } + + func bar() { + let foo = self.foo + print(foo) + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotRemovedInExtensionOfTypeWithDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo {} + + extension Foo { + subscript(dynamicMember foo: String) -> String { + foo + "bar" + } + + func bar() { + if self.foo == "foobar" { + return + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfRemovedInNestedExtensionOfTypeWithDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo { + var foo: Int + struct Foo {} + extension Foo { + func bar() { + if self.foo == "foobar" { + return + } + } + } + } + """ + let output = """ + @dynamicMemberLookup + struct Foo { + var foo: Int + struct Foo {} + extension Foo { + func bar() { + if foo == "foobar" { + return + } + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testNoRemoveSelfAfterGuardCaseLetWithExplicitNamespace() { + let input = """ + class Foo { + var name: String? + + func bug(element: Something) { + guard case let Something.a(name) = element + else { return } + self.name = name + } + } + """ + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveSelfInAssignmentInsideIfAsStatement() { + let input = """ + if let foo = foo as? Foo, let bar = baz { + self.bar = bar + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInAssignmentInsideIfLetWithPostfixOperator() { + let input = """ + if let foo = baz?.foo, let bar = baz?.bar { + self.foo = foo + self.bar = bar + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfParsingBug() { + let input = """ + private class Foo { + mutating func bar() -> Statement? { + let start = self + guard case Token.identifier(let name)? = self.popFirst() else { + self = start + return nil + } + return Statement.declaration(name: name) + } + } + """ + let output = """ + private class Foo { + mutating func bar() -> Statement? { + let start = self + guard case Token.identifier(let name)? = popFirst() else { + self = start + return nil + } + return Statement.declaration(name: name) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf, + exclude: [.hoistPatternLet]) + } + + func testRedundantSelfParsingBug2() { + let input = """ + extension Foo { + private enum NonHashableEnum: RawRepresentable { + case foo + case bar + + var rawValue: RuntimeTypeTests.TestStruct { + return TestStruct(foo: 0) + } + + init?(rawValue: RuntimeTypeTests.TestStruct) { + switch rawValue.foo { + case 0: + self = .foo + case 1: + self = .bar + default: + return nil + } + } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfWithStaticMethodAfterForLoop() { + let input = """ + struct Foo { + init() { + for foo in self.bar {} + } + + static func foo() {} + } + + """ + let output = """ + struct Foo { + init() { + for foo in bar {} + } + + static func foo() {} + } + + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfWithStaticMethodAfterForWhereLoop() { + let input = """ + struct Foo { + init() { + for foo in self.bar where !bar.isEmpty {} + } + + static func foo() {} + } + + """ + let output = """ + struct Foo { + init() { + for foo in bar where !bar.isEmpty {} + } + + static func foo() {} + } + + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfRuleDoesntErrorInForInTryLoop() { + let input = "for foo in try bar() {}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfInInitWithActorLabel() { + let input = """ + class Foo { + init(actor: Actor, bar: Bar) { + self.actor = actor + self.bar = bar + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfRuleFailsInGuardWithParenthesizedClosureAfterComma() { + let input = """ + guard let foo = bar, foo.bar(baz: { $0 }) else { + return nil + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testMinSelfNotRemoved() { + let input = """ + extension Array where Element: Comparable { + func foo() -> Int { + self.min() + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testMinSelfNotRemovedOnSwift5_4() { + let input = """ + extension Array where Element == Foo { + func smallest() -> Foo? { + let bar = self.min(by: { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + }) + return bar + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testDisableRedundantSelfDirective() { + let input = """ + func smallest() -> Foo? { + // swiftformat:disable:next redundantSelf + let bar = self.foo { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testDisableRedundantSelfDirective2() { + let input = """ + func smallest() -> Foo? { + let bar = + // swiftformat:disable:next redundantSelf + self.foo { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testSelfInsertDirective() { + let input = """ + func smallest() -> Foo? { + // swiftformat:options:next --self insert + let bar = self.foo { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testNoRemoveVariableShadowedLaterInScopeInOlderSwiftVersions() { + let input = """ + func foo() -> Bar? { + guard let baz = self.bar else { + return nil + } + + let bar = Foo() + return Bar(baz) + } + """ + let options = FormatOptions(swiftVersion: "4.2") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testStillRemoveVariableShadowedInSameDecalarationInOlderSwiftVersions() { + let input = """ + func foo() -> Bar? { + guard let bar = self.bar else { + return nil + } + return bar + } + """ + let output = """ + func foo() -> Bar? { + guard let bar = bar else { + return nil + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.0") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testShadowedSelfRemovedInGuardLet() { + let input = """ + func foo() { + guard let optional = self.optional else { + return + } + print(optional) + } + """ + let output = """ + func foo() { + guard let optional = optional else { + return + } + print(optional) + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testShadowedStringValueNotRemovedInInit() { + let input = """ + init() { + let value = "something" + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedIntValueNotRemovedInInit() { + let input = """ + init() { + let value = 5 + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedPropertyValueNotRemovedInInit() { + let input = """ + init() { + let value = foo + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedFuncCallValueNotRemovedInInit() { + let input = """ + init() { + let value = foo() + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedFuncParamRemovedInInit() { + let input = """ + init() { + let value = foo(self.value) + } + """ + let output = """ + init() { + let value = foo(value) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInMacro() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: self.__myVar) + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + // explicitSelf = .insert + + func testInsertSelf() { + let input = "class Foo {\n let foo: Int\n init() { foo = 5 }\n}" + let output = "class Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfInActor() { + let input = "actor Foo {\n let foo: Int\n init() { foo = 5 }\n}" + let output = "actor Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfAfterReturn() { + let input = "class Foo {\n let foo: Int\n func bar() -> Int { return foo }\n}" + let output = "class Foo {\n let foo: Int\n func bar() -> Int { return self.foo }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfInsideStringInterpolation() { + let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" + let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInterpretGenericTypesAsMembers() { + let input = "class Foo {\n let foo: Bar\n init() { self.foo = Int(5) }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testInsertSelfForStaticMemberInClassFunction() { + let input = "class Foo {\n static var foo: Int\n class func bar() { foo = 5 }\n}" + let output = "class Foo {\n static var foo: Int\n class func bar() { self.foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForInstanceMemberInClassFunction() { + let input = "class Foo {\n var foo: Int\n class func bar() { foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForStaticMemberInInstanceFunction() { + let input = "class Foo {\n static var foo: Int\n func bar() { foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForShadowedClassMemberInClassFunction() { + let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { foo = 5 }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInForLoopTuple() { + let input = "class Foo {\n var bar: Int\n func foo() { for (bar, baz) in quux {} }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForTupleTypeMembers() { + let input = "class Foo {\n var foo: (Int, UIColor) {\n let bar = UIColor.red\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForArrayElements() { + let input = "class Foo {\n var foo = [1, 2, nil]\n func bar() { baz(nil) }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForNestedVarReference() { + let input = "class Foo {\n func bar() {\n var bar = 5\n repeat { bar = 6 } while true\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.wrapLoopBodies]) + } + + func testNoInsertSelfInSwitchCaseLet() { + let input = "class Foo {\n var foo: Bar? {\n switch bar {\n case let .baz(foo, _):\n return nil\n }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInFuncAfterImportedClass() { + let input = "import class Foo.Bar\nfunc foo() {\n var bar = 5\n if true {\n bar = 6\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, + exclude: [.blankLineAfterImports]) + } + + func testNoInsertSelfForSubscriptGetSet() { + let input = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return get(key) }\n set { set(key, newValue) }\n }\n}" + let output = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return self.get(key) }\n set { self.set(key, newValue) }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInIfCaseLet() { + let input = "enum Foo {\n case bar(Int)\n var value: Int? {\n if case let .bar(value) = self { return value }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, + exclude: [.wrapConditionalBodies]) + } + + func testNoInsertSelfForPatternLet() { + let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForPatternLet2() { + let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case let .foo(baz): print(baz)\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForTypeOf() { + let input = "class Foo {\n var type: String?\n func bar() {\n print(\"\\(type(of: self))\")\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForConditionalLocal() { + let input = "class Foo {\n func foo() {\n #if os(watchOS)\n var foo: Int\n #else\n var foo: Float\n #endif\n print(foo)\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testInsertSelfInExtension() { + let input = """ + struct Foo { + var bar = 5 + } + + extension Foo { + func baz() { + bar = 6 + } + } + """ + let output = """ + struct Foo { + var bar = 5 + } + + extension Foo { + func baz() { + self.bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testGlobalAfterTypeNotTreatedAsMember() { + let input = """ + struct Foo { + var foo = 1 + } + + var bar = 5 + + extension Foo { + func baz() { + bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testForWhereVarNotTreatedAsMember() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + for bar in self where bar.baz { + return bar + } + return nil + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSwitchCaseWhereVarNotTreatedAsMember() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar where bar.baz: + return bar + default: + return nil + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSwitchCaseVarDoesntLeak() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar: + return bar + default: + return bar + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar: + return bar + default: + return self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInSwitchCaseLet() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo: + return bar + default: + return bar + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo: + return self.bar + default: + return self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInSwitchCaseWhere() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where bar.baz: + return bar + default: + return bar + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where self.bar.baz: + return self.bar + default: + return self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInDidSet() { + let input = """ + class Foo { + var bar = false { + didSet { + bar = !bar + } + } + } + """ + let output = """ + class Foo { + var bar = false { + didSet { + self.bar = !self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedAfterLet() { + let input = """ + struct Foo { + let foo = "foo" + func bar() { + let x = foo + baz(x) + } + + func baz(_: String) {} + } + """ + let output = """ + struct Foo { + let foo = "foo" + func bar() { + let x = self.foo + self.baz(x) + } + + func baz(_: String) {} + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInParameterNames() { + let input = """ + class Foo { + let a: String + + func bar() { + foo(a: a) + } + } + """ + let output = """ + class Foo { + let a: String + + func bar() { + foo(a: self.a) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInCaseLet() { + let input = """ + class Foo { + let a: String? + let b: String + + func bar() { + if case let .some(a) = self.a, case var .some(b) = self.b {} + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInCaseLet2() { + let input = """ + class Foo { + let a: String? + let b: String + + func baz() { + if case let .foos(a, b) = foo, case let .bars(a, b) = bar {} + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInTupleAssignment() { + let input = """ + class Foo { + let a: String? + let b: String + + func bar() { + (a, b) = ("foo", "bar") + } + } + """ + let output = """ + class Foo { + let a: String? + let b: String + + func bar() { + (self.a, self.b) = ("foo", "bar") + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInTupleAssignment() { + let input = """ + class Foo { + let a: String? + let b: String + + func bar() { + let (a, b) = (self.a, self.b) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testInsertSelfForMemberNamedLazy() { + let input = """ + class Foo { + var lazy = "foo" + func foo() { + print(lazy) + } + } + """ + let output = """ + class Foo { + var lazy = "foo" + func foo() { + print(self.lazy) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForVarDefinedInIfCaseLet() { + let input = """ + struct A { + var localVar = "" + + var B: String { + if case let .c(localVar) = self.d, localVar == .e { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForVarDefinedInUnhoistedIfCaseLet() { + let input = """ + struct A { + var localVar = "" + + var B: String { + if case .c(let localVar) = self.d, localVar == .e { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, + exclude: [.hoistPatternLet]) + } + + func testNoInsertSelfForVarDefinedInFor() { + let input = """ + struct A { + var localVar = "" + + var B: String { + for localVar in 0 ..< 6 where localVar < 5 { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForVarDefinedInWhileLet() { + let input = """ + struct A { + var localVar = "" + + var B: String { + while let localVar = self.localVar, localVar < 5 { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInCaptureList() { + let input = """ + class Thing { + var a: String? { nil } + + func foo() { + let b = "" + { [weak a = b] _ in } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInCaptureList2() { + let input = """ + class Thing { + var a: String? { nil } + + func foo() { + { [weak a] _ in } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInCaptureList3() { + let input = """ + class A { + var thing: B? { fatalError() } + + func foo() { + let thing2 = B() + let _: (Bool) -> Void = { [weak thing = thing2] _ in + thing?.bar() + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testBodilessFunctionDoesntBreakParser() { + let input = """ + @_silgen_name("foo") + func foo(_: CFString, _: CFTypeRef) -> Int? + + enum Bar { + static func baz() { + fatalError() + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfBeforeSet() { + let input = """ + class Foo { + var foo: Bool + + var bar: Bool { + get { self.foo } + set { self.foo = newValue } + } + + required init() {} + + func set() {} + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInMacro() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfBeforeBinding() { + let input = """ + struct MyView: View { + @Environment(ViewModel.self) var viewModel + + var body: some View { + @Bindable var viewModel = self.viewModel + ZStack { + MySubview( + navigationPath: $viewModel.navigationPath + ) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert, swiftVersion: "5.10") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + // explicitSelf = .initOnly + + func testPreserveSelfInsideClassInit() { + let input = """ + class Foo { + var bar = 5 + init() { + self.bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRemoveSelfIfNotInsideClassInit() { + let input = """ + class Foo { + var bar = 5 + func baz() { + self.bar = 6 + } + } + """ + let output = """ + class Foo { + var bar = 5 + func baz() { + bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfInsideClassInit() { + let input = """ + class Foo { + var bar = 5 + init() { + bar = 6 + } + } + """ + let output = """ + class Foo { + var bar = 5 + init() { + self.bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInsideClassInitIfNotLvalue() { + let input = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + bar = baz + } + } + """ + let output = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + self.bar = baz + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRemoveSelfInsideClassInitIfNotLvalue() { + let input = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + self.bar = self.baz + } + } + """ + let output = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + self.bar = baz + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfDotTypeInsideClassInitEdgeCase() { + let input = """ + class Foo { + let type: Int + + init() { + self.type = 5 + } + + func baz() { + switch type {} + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInTupleInInit() { + let input = """ + class Foo { + let a: String? + let b: String + + init() { + (a, b) = ("foo", "bar") + } + } + """ + let output = """ + class Foo { + let a: String? + let b: String + + init() { + (self.a, self.b) = ("foo", "bar") + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedAfterLetInInit() { + let input = """ + class Foo { + var foo: String + init(bar: Bar) { + let baz = bar.quux + foo = baz + } + } + """ + let output = """ + class Foo { + var foo: String + init(bar: Bar) { + let baz = bar.quux + self.foo = baz + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleDoesntErrorForStaticFuncInProtocolWithWhere() { + let input = """ + protocol Foo where Self: Bar { + static func baz() -> Self + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleDoesntErrorForStaticFuncInStructWithWhere() { + let input = """ + struct Foo where T: Bar { + static func baz() -> Foo {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleDoesntErrorForClassFuncInClassWithWhere() { + let input = """ + class Foo where T: Bar { + class func baz() -> Foo {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleFailsInInitOnlyMode() { + let input = """ + class Foo { + func foo() -> Foo? { + guard let bar = { nil }() else { + return nil + } + } + + static func baz() -> String? {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantClosure]) + } + + func testRedundantSelfRuleFailsInInitOnlyMode2() { + let input = """ + struct Mesh { + var storage: Storage + init(vertices: [Vertex]) { + let isConvex = pointsAreConvex(vertices) + storage = Storage(vertices: vertices) + } + } + """ + let output = """ + struct Mesh { + var storage: Storage + init(vertices: [Vertex]) { + let isConvex = pointsAreConvex(vertices) + self.storage = Storage(vertices: vertices) + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testSelfNotRemovedInInitForSwift5_4() { + let input = """ + init() { + let foo = 1234 + self.bar = foo + } + """ + let options = FormatOptions(explicitSelf: .initOnly, swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testPropertyInitNotInterpretedAsTypeInit() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + init(initialValue) { + __myVar = initialValue + } + set { + __myVar = newValue + } + get { + __myVar + } + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testPropertyInitNotInterpretedAsTypeInit2() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + init { + __myVar = newValue + } + set { + __myVar = newValue + } + get { + __myVar + } + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + // parsing bugs + + func testSelfRemovalParsingBug() { + let input = """ + extension Dictionary where Key == String { + func requiredValue(for keyPath: String) throws -> T { + return keyPath as! T + } + + func optionalValue(for keyPath: String) throws -> T? { + guard let anyValue = self[keyPath] else { + return nil + } + guard let value = anyValue as? T else { + return nil + } + return value + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovalParsingBug2() { + let input = """ + if let test = value()["hi"] { + print("hi") + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovalParsingBug3() { + let input = """ + func handleGenericError(_ error: Error) { + if let requestableError = error as? RequestableError, + case let .underlying(error as NSError) = requestableError, + error.code == NSURLErrorNotConnectedToInternet + {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfRemovalParsingBug4() { + let input = """ + struct Foo { + func bar() { + for flag in [] where [].filter({ true }) {} + } + + static func baz() {} + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfRemovalParsingBug5() { + let input = """ + extension Foo { + func method(foo: Bar) { + self.foo = foo + + switch foo { + case let .foo(bar): + closure { + Foo.draw() + } + } + } + + private static func draw() {} + } + """ + + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovalParsingBug6() { + let input = """ + something.do(onSuccess: { result in + if case .success((let d, _)) = result { + self.relay.onNext(d) + } + }) + """ + testFormatting(for: input, rule: .redundantSelf, + exclude: [.hoistPatternLet]) + } + + func testSelfRemovalParsingBug7() { + let input = """ + extension Dictionary where Key == String { + func requiredValue(for keyPath: String) throws(Foo) -> T { + return keyPath as! T + } + + func optionalValue(for keyPath: String) throws(Foo) -> T? { + guard let anyValue = self[keyPath] else { + return nil + } + guard let value = anyValue as? T else { + return nil + } + return value + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInCaseIfElse() { + let input = """ + class Foo { + let bar = true + let someOptionalBar: String? = "bar" + + func test() { + guard let bar: String = someOptionalBar else { + return + } + + let result = Result.success(bar) + switch result { + case let .success(value): + if self.bar { + if self.bar { + print(self.bar) + } + } else { + if self.bar { + print(self.bar) + } + } + + case .failure: + if self.bar { + print(self.bar) + } + } + } + } + """ + + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfCallAfterIfStatementInSwitchStatement() { + let input = """ + closure { [weak self] in + guard let self else { + return + } + + switch result { + case let .success(value): + if value != nil { + if value != nil { + self.method() + } + } + self.method() + + case .failure: + break + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotRemovedFollowingNestedSwitchStatements() { + let input = """ + class Foo { + let bar = true + let someOptionalBar: String? = "bar" + + func test() { + guard let bar: String = someOptionalBar else { + return + } + + let result = Result.success(bar) + switch result { + case let .success(value): + switch result { + case .success: + print("success") + case .value: + print("value") + } + + case .failure: + guard self.bar else { + print(self.bar) + return + } + print(self.bar) + } + } + } + """ + + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfWithStaticAsyncSendableClosureFunction() { + let input = """ + class Foo: Bar { + static func bar( + _ closure: @escaping @Sendable () async -> Foo + ) -> @Sendable () async -> Foo { + self.foo = closure + return closure + } + + static func bar() {} + } + """ + let output = """ + class Foo: Bar { + static func bar( + _ closure: @escaping @Sendable () async -> Foo + ) -> @Sendable () async -> Foo { + foo = closure + return closure + } + + static func bar() {} + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + // enable/disable + + func testDisableRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantSelf + self.bar = 1 + // swiftformat:enable redundantSelf + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantSelf + self.bar = 1 + // swiftformat:enable redundantSelf + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testDisableRemoveSelfCaseInsensitive() { + let input = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantself + self.bar = 1 + // swiftformat:enable RedundantSelf + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantself + self.bar = 1 + // swiftformat:enable RedundantSelf + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testDisableNextRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable:next redundantSelf + self.bar = 1 + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable:next redundantSelf + self.bar = 1 + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testMultilineDisableRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testMultilineDisableNextRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable:next redundantSelf */ + self.bar = 1 + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable:next redundantSelf */ + self.bar = 1 + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemovesSelfInNestedFunctionInStrongSelfClosure() { + let input = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [self] in + doWork { + // Not allowed. Warning in Swift 5 and error in Swift 6. + self.test() + } + + func innerFunc() { + // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 + self.test() + } + + innerFunc() + } + } + } + """ + + let output = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [self] in + doWork { + // Not allowed. Warning in Swift 5 and error in Swift 6. + self.test() + } + + func innerFunc() { + // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 + test() + } + + innerFunc() + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf, options: FormatOptions(swiftVersion: "5.8")) + } + + func testPreservesSelfInNestedFunctionInWeakSelfClosure() { + let input = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [weak self] in + func innerFunc() { + self?.test() + } + + guard let self else { + return + } + + self.test() + + func innerFunc() { + self.test() + } + + self.test() + } + } + } + """ + + let output = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [weak self] in + func innerFunc() { + self?.test() + } + + guard let self else { + return + } + + test() + + func innerFunc() { + self.test() + } + + test() + } + } + } + """ + + testFormatting(for: input, output, rule: .redundantSelf, + options: FormatOptions(swiftVersion: "5.8")) + } + + func testRedundantSelfAfterScopedImport() { + let input = """ + import struct Foundation.Date + + struct Foo { + let foo: String + init(bar: String) { + self.foo = bar + } + } + """ + let output = """ + import struct Foundation.Date + + struct Foo { + let foo: String + init(bar: String) { + foo = bar + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfNotConfusedByParameterPack() { + let input = """ + func pairUp(firstPeople: repeat each T, secondPeople: repeat each U) -> (repeat (first: each T, second: each U)) { + (repeat (each firstPeople, each secondPeople)) + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfNotConfusedByStaticAfterSwitch() { + let input = """ + public final class MyClass { + private static func privateStaticFunction1() -> Bool { + switch Result(catching: { try someThrowingFunction() }) { + case .success: + return true + case .failure: + return false + } + } + + private static func privateStaticFunction2() -> Bool { + return false + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.enumNamespaces]) + } + + func testRedundantSelfNotConfusedByMainActor() { + let input = """ + class Test { + private var p: Int + + func f() { + self.f2( + closure: { @MainActor [weak self] p in + print(p) + } + ) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoMistakeProtocolClassModifierForClassFunction() { + let input = "protocol Foo: class {}\nfunc bar() {}" + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: FormatRules.all)) + } + + func testRedundantSelfParsingBug3() { + let input = """ + final class ViewController { + private func bottomBarModels() -> [BarModeling] { + if let url = URL(string: "..."){ + // ... + } + + models.append( + Footer.barModel( + content: FooterContent( + primaryTitleText: "..."), + style: style) + .setBehaviors { context in + context.view.primaryButtonState = self.isLoading ? .waiting : .normal + context.view.primaryActionHandler = { [weak self] _ in + self?.acceptButtonWasTapped() + } + }) + } + + } + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } + + func testRedundantSelfParsingBug4() { + let input = """ + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let row: Row = promotionSections[indexPath.section][indexPath.row] else { return UITableViewCell() } + let cell = tableView.dequeueReusable(RowTableViewCell.self, forIndexPath: indexPath) + cell.update(row: row) + return cell + } + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } + + func testRedundantSelfParsingBug5() { + let input = """ + Button.primary( + title: "Title", + tapHandler: { [weak self] in + self?.dismissBlock? { + // something + } + } + ) + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } + + func testRedundantSelfParsingBug6() { + let input = """ + if let foo = bar, foo.tracking[jsonDict: "something"] != nil {} + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } +} diff --git a/Tests/Rules/RedundantStaticSelfTests.swift b/Tests/Rules/RedundantStaticSelfTests.swift new file mode 100644 index 00000000..9fd2ebf1 --- /dev/null +++ b/Tests/Rules/RedundantStaticSelfTests.swift @@ -0,0 +1,226 @@ +// +// RedundantStaticSelfTests.swift +// SwiftFormatTests +// +// Created by Šimon Javora on 4/29/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantStaticSelfTests: XCTestCase { + func testRedundantStaticSelfInStaticVar() { + let input = "enum E { static var x: Int { Self.y } }" + let output = "enum E { static var x: Int { y } }" + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInStaticMethod() { + let input = "enum E { static func foo() { Self.bar() } }" + let output = "enum E { static func foo() { bar() } }" + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfOnNextLine() { + let input = """ + enum E { + static func foo() { + Self + .bar() + } + } + """ + let output = """ + enum E { + static func foo() { + bar() + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfWithReturn() { + let input = "enum E { static func foo() { return Self.bar() } }" + let output = "enum E { static func foo() { return bar() } }" + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInConditional() { + let input = """ + enum E { + static func foo() { + if Bool.random() { + Self.bar() + } + } + } + """ + let output = """ + enum E { + static func foo() { + if Bool.random() { + bar() + } + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInNestedFunction() { + let input = """ + enum E { + static func foo() { + func bar() { + Self.foo() + } + } + } + """ + let output = """ + enum E { + static func foo() { + func bar() { + foo() + } + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInNestedType() { + let input = """ + enum Outer { + enum Inner { + static func foo() {} + static func bar() { Self.foo() } + } + } + """ + let output = """ + enum Outer { + enum Inner { + static func foo() {} + static func bar() { foo() } + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testStaticSelfNotRemovedWhenUsedAsImplicitInitializer() { + let input = "enum E { static func foo() { Self().bar() } }" + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testStaticSelfNotRemovedWhenUsedAsExplicitInitializer() { + let input = "enum E { static func foo() { Self.init().bar() } }" + testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.redundantInit]) + } + + func testPreservesStaticSelfInFunctionAfterStaticVar() { + let input = """ + enum MyFeatureCacheStrategy { + case networkOnly + case cacheFirst + + static let defaultCacheAge = TimeInterval.minutes(5) + + func requestStrategy() -> SingleRequestStrategy { + switch self { + case .networkOnly: + return .networkOnly(writeResultToCache: true) + case .cacheFirst: + return .cacheFirst(maxCacheAge: Self.defaultCacheAge) + } + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.propertyType]) + } + + func testPreserveStaticSelfInInstanceFunction() { + let input = """ + enum Foo { + static var value = 0 + + func f() { + Self.value = value + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfForShadowedProperty() { + let input = """ + enum Foo { + static var value = 0 + + static func f(value: Int) { + Self.value = value + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfInGetter() { + let input = """ + enum Foo { + static let foo: String = "foo" + + var sharedFoo: String { + Self.foo + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testRemoveStaticSelfInStaticGetter() { + let input = """ + public enum Foo { + static let foo: String = "foo" + + static var getFoo: String { + Self.foo + } + } + """ + let output = """ + public enum Foo { + static let foo: String = "foo" + + static var getFoo: String { + foo + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfInGuardLet() { + let input = """ + class LocationDeeplink: Deeplink { + convenience init?(warnRegion: String) { + guard let value = Self.location(for: warnRegion) else { + return nil + } + self.init(location: value) + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfInSingleLineClassInit() { + let input = """ + class A { static let defaultName = "A"; let name: String; init() { name = Self.defaultName }} + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } +} diff --git a/Tests/Rules/RedundantTypeTests.swift b/Tests/Rules/RedundantTypeTests.swift new file mode 100644 index 00000000..28589b03 --- /dev/null +++ b/Tests/Rules/RedundantTypeTests.swift @@ -0,0 +1,713 @@ +// +// RedundantTypeTests.swift +// SwiftFormatTests +// +// Created by Facundo Menzella on 8/20/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantTypeTests: XCTestCase { + func testVarRedundantTypeRemoval() { + let input = "var view: UIView = UIView()" + let output = "var view = UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testVarRedundantArrayTypeRemoval() { + let input = "var foo: [String] = [String]()" + let output = "var foo = [String]()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testVarRedundantDictionaryTypeRemoval() { + let input = "var foo: [String: Int] = [String: Int]()" + let output = "var foo = [String: Int]()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testLetRedundantGenericTypeRemoval() { + let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" + let output = "let relay = BehaviourRelay(value: nil)" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testVarNonRedundantTypeDoesNothing() { + let input = "var view: UIView = UINavigationBar()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testLetRedundantTypeRemoval() { + let input = "let view: UIView = UIView()" + let output = "let view = UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testLetNonRedundantTypeDoesNothing() { + let input = "let view: UIView = UINavigationBar()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testTypeNoRedundancyDoesNothing() { + let input = "let foo: Bar = 5" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testClassTwoVariablesNoRedundantTypeDoesNothing() { + let input = """ + final class LGWebSocketClient: WebSocketClient, WebSocketLibraryDelegate { + var webSocket: WebSocketLibraryProtocol + var timeoutIntervalForRequest: TimeInterval = LGCoreKitConstants.websocketTimeOutTimeInterval + } + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testRedundantTypeRemovedIfValueOnNextLine() { + let input = """ + let view: UIView + = UIView() + """ + let output = """ + let view + = UIView() + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypeRemovedIfValueOnNextLine2() { + let input = """ + let view: UIView = + UIView() + """ + let output = """ + let view = + UIView() + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testAllRedundantTypesRemovedInCommaDelimitedDeclaration() { + let input = "var foo: Int = 0, bar: Int = 0" + let output = "var foo = 0, bar = 0" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypeRemovalWithComment() { + let input = "var view: UIView /* view */ = UIView()" + let output = "var view /* view */ = UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypeRemovalWithComment2() { + let input = "var view: UIView = /* view */ UIView()" + let output = "var view = /* view */ UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testNonRedundantTernaryConditionTypeNotRemoved() { + let input = "let foo: Bar = Bar.baz() ? .bar1 : .bar2" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testTernaryConditionAfterLetNotTreatedAsPartOfExpression() { + let input = """ + let foo: Bar = Bar.baz() + baz ? bar2() : bar2() + """ + let output = """ + let foo = Bar.baz() + baz ? bar2() : bar2() + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testNoRemoveRedundantTypeIfVoid() { + let input = "let foo: Void = Void()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.void]) + } + + func testNoRemoveRedundantTypeIfVoid2() { + let input = "let foo: () = ()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.void]) + } + + func testNoRemoveRedundantTypeIfVoid3() { + let input = "let foo: [Void] = [Void]()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testNoRemoveRedundantTypeIfVoid4() { + let input = "let foo: Array = Array()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.typeSugar]) + } + + func testNoRemoveRedundantTypeIfVoid5() { + let input = "let foo: Void? = Void?.none" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testNoRemoveRedundantTypeIfVoid6() { + let input = "let foo: Optional = Optional.none" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.typeSugar]) + } + + func testRedundantTypeWithLiterals() { + let input = """ + let a1: Bool = true + let a2: Bool = false + + let b1: String = "foo" + let b2: String = "\\(b1)" + + let c1: Int = 1 + let c2: Int = 1.0 + + let d1: Double = 3.14 + let d2: Double = 3 + + let e1: [Double] = [3.14] + let e2: [Double] = [3] + + let f1: [String: Int] = ["foo": 5] + let f2: [String: Int?] = ["foo": nil] + """ + let output = """ + let a1 = true + let a2 = false + + let b1 = "foo" + let b2 = "\\(b1)" + + let c1 = 1 + let c2: Int = 1.0 + + let d1 = 3.14 + let d2: Double = 3 + + let e1 = [3.14] + let e2: [Double] = [3] + + let f1 = ["foo": 5] + let f2: [String: Int?] = ["foo": nil] + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypePreservesLiteralRepresentableTypes() { + let input = """ + let a: MyBoolRepresentable = true + let b: MyStringRepresentable = "foo" + let c: MyIntRepresentable = 1 + let d: MyDoubleRepresentable = 3.14 + let e: MyArrayRepresentable = ["bar"] + let f: MyDictionaryRepresentable = ["baz": 1] + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testPreservesTypeWithIfExpressionInSwift5_8() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testPreservesNonRedundantTypeWithIfExpression() { + let input = """ + let foo: Foo = if condition { + Foo("foo") + } else { + FooSubclass("bar") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantTypeWithIfExpression_inferred() { + let input = """ + let foo: Foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let output = """ + let foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantTypeWithIfExpression_explicit() { + let input = """ + let foo: Foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let output = """ + let foo: Foo = if condition { + .init("foo") + } else { + .init("bar") + } + """ + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) + } + + func testRedundantTypeWithNestedIfExpression_inferred() { + let input = """ + let foo: Foo = if condition { + switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + Foo("baaz") + } + } else { + Foo("quux") + } + """ + let output = """ + let foo = if condition { + switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + Foo("baaz") + } + } else { + Foo("quux") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantTypeWithNestedIfExpression_explicit() { + let input = """ + let foo: Foo = if condition { + switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + Foo("baaz") + } + } else { + Foo("quux") + } + """ + let output = """ + let foo: Foo = if condition { + switch condition { + case true: + if condition { + .init("foo") + } else { + .init("bar") + } + + case false: + .init("baaz") + } + } else { + .init("quux") + } + """ + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) + } + + func testRedundantTypeWithLiteralsInIfExpression() { + let input = """ + let foo: String = if condition { + "foo" + } else { + "bar" + } + """ + let output = """ + let foo = if condition { + "foo" + } else { + "bar" + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + // --redundanttype explicit + + func testVarRedundantTypeRemovalExplicitType() { + let input = "var view: UIView = UIView()" + let output = "var view: UIView = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testVarRedundantTypeRemovalExplicitType2() { + let input = "var view: UIView = UIView /* foo */()" + let output = "var view: UIView = .init /* foo */()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.spaceAroundComments, .propertyType]) + } + + func testLetRedundantGenericTypeRemovalExplicitType() { + let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" + let output = "let relay: BehaviourRelay = .init(value: nil)" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { + let input = "let relay: Foo = Foo\n .default" + let output = "let relay: Foo = \n .default" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.trailingSpace, .propertyType]) + } + + func testVarNonRedundantTypeDoesNothingExplicitType() { + let input = "var view: UIView = UINavigationBar()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testLetRedundantTypeRemovalExplicitType() { + let input = "let view: UIView = UIView()" + let output = "let view: UIView = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { + let input = """ + let view: UIView + = UIView() + """ + let output = """ + let view: UIView + = .init() + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { + let input = """ + let view: UIView = + UIView() + """ + let output = """ + let view: UIView = + .init() + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithCommentExplicitType() { + let input = "var view: UIView /* view */ = UIView()" + let output = "var view: UIView /* view */ = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithComment2ExplicitType() { + let input = "var view: UIView = /* view */ UIView()" + let output = "var view: UIView = /* view */ .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithStaticMember() { + let input = """ + let session: URLSession = URLSession.default + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let output = """ + let session: URLSession = .default + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithStaticFunc() { + let input = """ + let session: URLSession = URLSession.default() + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let output = """ + let session: URLSession = .default() + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingWithChainedMember() { + let input = "let session: URLSession = URLSession.default.makeCopy()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { + let input = "let session: URLSession = URLSession.default.makeCopy()" + let output = "let session: URLSession = .default.makeCopy()" + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingWithChainedMember2() { + let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingWithChainedMember3() { + let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { + let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" + let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingIfLet() { + let input = "if let foo: Foo = Foo() {}" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingGuardLet() { + let input = "guard let foo: Foo = Foo() else {}" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingIfLetAfterComma() { + let input = "if check == true, let foo: Foo = Foo() {}" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeWorksAfterIf() { + let input = """ + if foo {} + let foo: Foo = Foo() + """ + let output = """ + if foo {} + let foo: Foo = .init() + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeIfVoid() { + let input = "let foo: [Void] = [Void]()" + let output = "let foo: [Void] = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeWithIntegerLiteralNotMangled() { + let input = "let foo: Int = 1.toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeWithFloatLiteralNotMangled() { + let input = "let foo: Double = 1.0.toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeWithArrayLiteralNotMangled() { + let input = "let foo: [Int] = [1].toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeWithBoolLiteralNotMangled() { + let input = "let foo: Bool = false.toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeInModelClassNotStripped() { + // See: https://github.com/nicklockwood/SwiftFormat/issues/1649 + let input = """ + @Model + class FooBar { + var created: Date = Date.now + } + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options) + } + + // --redundanttype infer-locals-only + + func testRedundantTypeinferLocalsOnly() { + let input = """ + let globalFoo: Foo = Foo() + + struct SomeType { + let instanceFoo: Foo = Foo() + + func method() { + let localFoo: Foo = Foo() + let localString: String = "foo" + } + + let instanceString: String = "foo" + } + + let globalString: String = "foo" + """ + + let output = """ + let globalFoo: Foo = .init() + + struct SomeType { + let instanceFoo: Foo = .init() + + func method() { + let localFoo = Foo() + let localString = "foo" + } + + let instanceString: String = "foo" + } + + let globalString: String = "foo" + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testClassWithWhereNotMistakenForLocalScope() { + let input = """ + final class Foo where Bar: Equatable { + var isFoo: Bool = false + var fooName: String = "name" + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: .redundantType, options: options) + } +} diff --git a/Tests/Rules/RedundantTypedThrowsTests.swift b/Tests/Rules/RedundantTypedThrowsTests.swift new file mode 100644 index 00000000..d4e1ede1 --- /dev/null +++ b/Tests/Rules/RedundantTypedThrowsTests.swift @@ -0,0 +1,61 @@ +// +// RedundantTypedThrowsTests.swift +// SwiftFormatTests +// +// Created by Miguel Jimenez on 6/8/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantTypedThrowsTests: XCTestCase { + func testRemovesRedundantNeverTypeThrows() { + let input = """ + func foo() throws(Never) -> Int { + 0 + } + """ + + let output = """ + func foo() -> Int { + 0 + } + """ + + let options = FormatOptions(swiftVersion: "6.0") + testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) + } + + func testRemovesRedundantAnyErrorTypeThrows() { + let input = """ + func foo() throws(any Error) -> Int { + throw MyError.foo + } + """ + + let output = """ + func foo() throws -> Int { + throw MyError.foo + } + """ + + let options = FormatOptions(swiftVersion: "6.0") + testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) + } + + func testDontRemovesNonRedundantErrorTypeThrows() { + let input = """ + func bar() throws(BarError) -> Foo { + throw .foo + } + + func foo() throws(Error) -> Int { + throw MyError.foo + } + """ + + let options = FormatOptions(swiftVersion: "6.0") + testFormatting(for: input, rule: .redundantTypedThrows, options: options) + } +} diff --git a/Tests/Rules/RedundantVoidReturnTypeTests.swift b/Tests/Rules/RedundantVoidReturnTypeTests.swift new file mode 100644 index 00000000..591ad0a9 --- /dev/null +++ b/Tests/Rules/RedundantVoidReturnTypeTests.swift @@ -0,0 +1,115 @@ +// +// RedundantVoidReturnTypeTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/3/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantVoidReturnTypeTests: XCTestCase { + func testRemoveRedundantVoidReturnType() { + let input = "func foo() -> Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidReturnType2() { + let input = "func foo() ->\n Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantSwiftDotVoidReturnType() { + let input = "func foo() -> Swift.Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantSwiftDotVoidReturnType2() { + let input = "func foo() -> Swift\n .Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantEmptyReturnType() { + let input = "func foo() -> () {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidTupleReturnType() { + let input = "func foo() -> (Void) {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testNoRemoveCommentFollowingRedundantVoidReturnType() { + let input = "func foo() -> Void /* void */ {}" + let output = "func foo() /* void */ {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testNoRemoveRequiredVoidReturnType() { + let input = "typealias Foo = () -> Void" + testFormatting(for: input, rule: .redundantVoidReturnType) + } + + func testNoRemoveChainedVoidReturnType() { + let input = "func foo() -> () -> Void {}" + testFormatting(for: input, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidInClosureArguments() { + let input = "{ (foo: Bar) -> Void in foo() }" + let output = "{ (foo: Bar) in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantEmptyReturnTypeInClosureArguments() { + let input = "{ (foo: Bar) -> () in foo() }" + let output = "{ (foo: Bar) in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidInClosureArguments2() { + let input = "methodWithTrailingClosure { foo -> Void in foo() }" + let output = "methodWithTrailingClosure { foo in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantSwiftDotVoidInClosureArguments2() { + let input = "methodWithTrailingClosure { foo -> Swift.Void in foo() }" + let output = "methodWithTrailingClosure { foo in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testNoRemoveRedundantVoidInClosureArgument() { + let input = "{ (foo: Bar) -> Void in foo() }" + let options = FormatOptions(closureVoidReturn: .preserve) + testFormatting(for: input, rule: .redundantVoidReturnType, options: options) + } + + func testRemoveRedundantVoidInProtocolDeclaration() { + let input = """ + protocol Foo { + func foo() -> Void + func bar() -> () + var baz: Int { get } + func bazz() -> ( ) + } + """ + + let output = """ + protocol Foo { + func foo() + func bar() + var baz: Int { get } + func bazz() + } + """ + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } +} diff --git a/Tests/Rules/SemicolonsTests.swift b/Tests/Rules/SemicolonsTests.swift new file mode 100644 index 00000000..a9134ae0 --- /dev/null +++ b/Tests/Rules/SemicolonsTests.swift @@ -0,0 +1,77 @@ +// +// SemicolonsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/24/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SemicolonsTests: XCTestCase { + func testSemicolonRemovedAtEndOfLine() { + let input = "print(\"hello\");\n" + let output = "print(\"hello\")\n" + testFormatting(for: input, output, rule: .semicolons) + } + + func testSemicolonRemovedAtStartOfLine() { + let input = "\n;print(\"hello\")" + let output = "\nprint(\"hello\")" + testFormatting(for: input, output, rule: .semicolons) + } + + func testSemicolonRemovedAtEndOfProgram() { + let input = "print(\"hello\");" + let output = "print(\"hello\")" + testFormatting(for: input, output, rule: .semicolons) + } + + func testSemicolonRemovedAtStartOfProgram() { + let input = ";print(\"hello\")" + let output = "print(\"hello\")" + testFormatting(for: input, output, rule: .semicolons) + } + + func testIgnoreInlineSemicolon() { + let input = "print(\"hello\"); print(\"goodbye\")" + let options = FormatOptions(allowInlineSemicolons: true) + testFormatting(for: input, rule: .semicolons, options: options) + } + + func testReplaceInlineSemicolon() { + let input = "print(\"hello\"); print(\"goodbye\")" + let output = "print(\"hello\")\nprint(\"goodbye\")" + let options = FormatOptions(allowInlineSemicolons: false) + testFormatting(for: input, output, rule: .semicolons, options: options) + } + + func testReplaceSemicolonFollowedByComment() { + let input = "print(\"hello\"); // comment\nprint(\"goodbye\")" + let output = "print(\"hello\") // comment\nprint(\"goodbye\")" + let options = FormatOptions(allowInlineSemicolons: true) + testFormatting(for: input, output, rule: .semicolons, options: options) + } + + func testSemicolonNotReplacedAfterReturn() { + let input = "return;\nfoo()" + testFormatting(for: input, rule: .semicolons) + } + + func testSemicolonReplacedAfterReturnIfEndOfScope() { + let input = "do { return; }" + let output = "do { return }" + testFormatting(for: input, output, rule: .semicolons) + } + + func testRequiredSemicolonNotRemovedAfterInferredVar() { + let input = """ + func foo() { + @Environment(\\.colorScheme) var colorScheme; + print(colorScheme) + } + """ + testFormatting(for: input, rule: .semicolons) + } +} diff --git a/Tests/Rules/SortDeclarationsTests.swift b/Tests/Rules/SortDeclarationsTests.swift new file mode 100644 index 00000000..c4eff1dc --- /dev/null +++ b/Tests/Rules/SortDeclarationsTests.swift @@ -0,0 +1,289 @@ +// +// SortDeclarationsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 11/22/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortDeclarationsTests: XCTestCase { + func testSortEnumBody() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsellB + case fooFeature( + fooConfiguration: Foo, + barConfiguration: Bar + ) + case barFeature // Trailing comment -- bar feature + /// Leading comment -- upsell A + case upsellA( + fooConfiguration: Foo, + barConfiguration: Bar + ) + } + + enum NextType { + case foo + case bar + } + """ + + let output = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature // Trailing comment -- bar feature + case fooFeature( + fooConfiguration: Foo, + barConfiguration: Bar + ) + /// Leading comment -- upsell A + case upsellA( + fooConfiguration: Foo, + barConfiguration: Bar + ) + case upsellB + } + + enum NextType { + case foo + case bar + } + """ + + testFormatting(for: input, output, rule: .sortDeclarations) + } + + func testSortEnumBodyWithOnlyOneCase() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsellB + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testSortEnumBodyWithoutCase() { + let input = """ + // swiftformat:sort + enum FeatureFlags {} + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testNoSortUnannotatedType() { + let input = """ + enum FeatureFlags { + case upsellB + case fooFeature + case barFeature + case upsellA + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testPreservesSortedBody() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + case upsellB + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testSortsTypeBody() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsellB + case fooFeature + case barFeature + case upsellA + } + """ + + let output = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + case upsellB + } + """ + + testFormatting(for: input, output, rule: .sortDeclarations, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testSortClassWithMixedDeclarationTypes() { + let input = """ + // swiftformat:sort + class Foo { + let quuxProperty = Quux() + let barProperty = Bar() + + var fooComputedProperty: Foo { + Foo() + } + + func baazFunction() -> Baaz { + Baaz() + } + } + """ + + let output = """ + // swiftformat:sort + class Foo { + func baazFunction() -> Baaz { + Baaz() + } + let barProperty = Bar() + + var fooComputedProperty: Foo { + Foo() + } + + let quuxProperty = Quux() + } + """ + + testFormatting(for: input, [output], + rules: [.sortDeclarations, .consecutiveBlankLines], + exclude: [.blankLinesBetweenScopes, .propertyType]) + } + + func testSortBetweenDirectiveCommentsInType() { + let input = """ + enum FeatureFlags { + // swiftformat:sort:begin + case upsellB + case fooFeature + case barFeature + case upsellA + // swiftformat:sort:end + + var anUnsortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + enum FeatureFlags { + // swiftformat:sort:begin + case barFeature + case fooFeature + case upsellA + case upsellB + // swiftformat:sort:end + + var anUnsortedProperty: Foo { + Foo() + } + } + """ + + testFormatting(for: input, output, rule: .sortDeclarations) + } + + func testSortTopLevelDeclarations() { + let input = """ + let anUnsortedGlobal = 0 + + // swiftformat:sort:begin + let sortThisGlobal = 1 + public let thisGlobalIsSorted = 2 + private let anotherSortedGlobal = 5 + let sortAllOfThem = 8 + // swiftformat:sort:end + + let anotherUnsortedGlobal = 9 + """ + + let output = """ + let anUnsortedGlobal = 0 + + // swiftformat:sort:begin + private let anotherSortedGlobal = 5 + let sortAllOfThem = 8 + let sortThisGlobal = 1 + public let thisGlobalIsSorted = 2 + // swiftformat:sort:end + + let anotherUnsortedGlobal = 9 + """ + + testFormatting(for: input, output, rule: .sortDeclarations) + } + + func testSortDeclarationsSortsByNamePattern() { + let input = """ + enum Namespace {} + + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let output = """ + enum Namespace {} + + extension Namespace { + static let baaz = "baaz" + public static let bar = "bar" + static let foo = "foo" + } + """ + + let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Namespace"]) + testFormatting(for: input, [output], rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) + } + + func testSortDeclarationsWontSortByNamePatternInComment() { + let input = """ + enum Namespace {} + + /// Constants + /// enum Constants + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Constants"]) + testFormatting(for: input, rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) + } + + func testSortDeclarationsUsesLocalizedCompare() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsella + case upsellA + case upsellb + case upsellB + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } +} diff --git a/Tests/Rules/SortImportsTests.swift b/Tests/Rules/SortImportsTests.swift new file mode 100644 index 00000000..eb340ea1 --- /dev/null +++ b/Tests/Rules/SortImportsTests.swift @@ -0,0 +1,214 @@ +// +// SortImportsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/13/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortImportsTests: XCTestCase { + func testSortImportsSimpleCase() { + let input = "import Foo\nimport Bar" + let output = "import Bar\nimport Foo" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportsKeepsPreviousCommentWithImport() { + let input = "import Foo\n// important comment\n// (very important)\nimport Bar" + let output = "// important comment\n// (very important)\nimport Bar\nimport Foo" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testSortImportsKeepsPreviousCommentWithImport2() { + let input = "// important comment\n// (very important)\nimport Foo\nimport Bar" + let output = "import Bar\n// important comment\n// (very important)\nimport Foo" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testSortImportsDoesntMoveHeaderComment() { + let input = "// header comment\n\nimport Foo\nimport Bar" + let output = "// header comment\n\nimport Bar\nimport Foo" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportsDoesntMoveHeaderCommentFollowedByImportComment() { + let input = "// header comment\n\n// important comment\nimport Foo\nimport Bar" + let output = "// header comment\n\nimport Bar\n// important comment\nimport Foo" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testSortImportsOnSameLine() { + let input = "import Foo; import Bar\nimport Baz" + let output = "import Baz\nimport Foo; import Bar" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportsWithSemicolonAndCommentOnSameLine() { + let input = "import Foo; // foobar\nimport Bar\nimport Baz" + let output = "import Bar\nimport Baz\nimport Foo; // foobar" + testFormatting(for: input, output, rule: .sortImports, exclude: [.semicolons]) + } + + func testSortImportEnum() { + let input = "import enum Foo.baz\nimport Foo.bar" + let output = "import Foo.bar\nimport enum Foo.baz" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportFunc() { + let input = "import func Foo.baz\nimport Foo.bar" + let output = "import Foo.bar\nimport func Foo.baz" + testFormatting(for: input, output, rule: .sortImports) + } + + func testAlreadySortImportsDoesNothing() { + let input = "import Bar\nimport Foo" + testFormatting(for: input, rule: .sortImports) + } + + func testPreprocessorSortImports() { + let input = "#if os(iOS)\n import Foo2\n import Bar2\n#else\n import Foo1\n import Bar1\n#endif\nimport Foo3\nimport Bar3" + let output = "#if os(iOS)\n import Bar2\n import Foo2\n#else\n import Bar1\n import Foo1\n#endif\nimport Bar3\nimport Foo3" + testFormatting(for: input, output, rule: .sortImports) + } + + func testTestableSortImports() { + let input = "@testable import Foo3\nimport Bar3" + let output = "import Bar3\n@testable import Foo3" + testFormatting(for: input, output, rule: .sortImports) + } + + func testLengthSortImports() { + let input = "import Foo\nimport Module\nimport Bar3" + let output = "import Foo\nimport Bar3\nimport Module" + let options = FormatOptions(importGrouping: .length) + testFormatting(for: input, output, rule: .sortImports, options: options) + } + + func testTestableImportsWithTestableOnPreviousLine() { + let input = "@testable\nimport Foo3\nimport Bar3" + let output = "import Bar3\n@testable\nimport Foo3" + testFormatting(for: input, output, rule: .sortImports) + } + + func testTestableImportsWithGroupingTestableBottom() { + let input = "@testable import Bar\nimport Foo\n@testable import UIKit" + let output = "import Foo\n@testable import Bar\n@testable import UIKit" + let options = FormatOptions(importGrouping: .testableLast) + testFormatting(for: input, output, rule: .sortImports, options: options) + } + + func testTestableImportsWithGroupingTestableTop() { + let input = "@testable import Bar\nimport Foo\n@testable import UIKit" + let output = "@testable import Bar\n@testable import UIKit\nimport Foo" + let options = FormatOptions(importGrouping: .testableFirst) + testFormatting(for: input, output, rule: .sortImports, options: options) + } + + func testCaseInsensitiveSortImports() { + let input = "import Zlib\nimport lib" + let output = "import lib\nimport Zlib" + testFormatting(for: input, output, rule: .sortImports) + } + + func testCaseInsensitiveCaseDifferingSortImports() { + let input = "import c\nimport B\nimport A.a\nimport A.A" + let output = "import A.A\nimport A.a\nimport B\nimport c" + testFormatting(for: input, output, rule: .sortImports) + } + + func testNoDeleteCodeBetweenImports() { + let input = "import Foo\nfunc bar() {}\nimport Bar" + testFormatting(for: input, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testNoDeleteCodeBetweenImports2() { + let input = "import Foo\nimport Bar\nfoo = bar\nimport Bar" + let output = "import Bar\nimport Foo\nfoo = bar\nimport Bar" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testNoDeleteCodeBetweenImports3() { + let input = """ + import Z + + // one + + #if FLAG + print("hi") + #endif + + import A + """ + testFormatting(for: input, rule: .sortImports) + } + + func testSortContiguousImports() { + let input = "import Foo\nimport Bar\nfunc bar() {}\nimport Quux\nimport Baz" + let output = "import Bar\nimport Foo\nfunc bar() {}\nimport Baz\nimport Quux" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testNoMangleImportsPrecededByComment() { + let input = """ + // evil comment + + #if canImport(Foundation) + import Foundation + #if canImport(UIKit) && canImport(AVFoundation) + import UIKit + import AVFoundation + #endif + #endif + """ + let output = """ + // evil comment + + #if canImport(Foundation) + import Foundation + #if canImport(UIKit) && canImport(AVFoundation) + import AVFoundation + import UIKit + #endif + #endif + """ + testFormatting(for: input, output, rule: .sortImports) + } + + func testNoMangleFileHeaderNotFollowedByLinebreak() { + let input = """ + // + // Code.swift + // Module + // + // Created by Someone on 4/30/20. + // + import AModuleUI + import AModule + import AModuleHelper + import SomeOtherModule + """ + let output = """ + // + // Code.swift + // Module + // + // Created by Someone on 4/30/20. + // + import AModule + import AModuleHelper + import AModuleUI + import SomeOtherModule + """ + testFormatting(for: input, output, rule: .sortImports) + } +} diff --git a/Tests/Rules/SortSwitchCasesTests.swift b/Tests/Rules/SortSwitchCasesTests.swift new file mode 100644 index 00000000..03468d19 --- /dev/null +++ b/Tests/Rules/SortSwitchCasesTests.swift @@ -0,0 +1,340 @@ +// +// SortSwitchCasesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/13/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortSwitchCasesTests: XCTestCase { + func testSortedSwitchCaseNestedSwitchOneCaseDoesNothing() { + let input = """ + switch result { + case let .success(value): + switch result { + case .success: + print("success") + case .value: + print("value") + } + + case .failure: + guard self.bar else { + print(self.bar) + return + } + print(self.bar) + } + """ + + testFormatting(for: input, rule: .sortSwitchCases, exclude: [.redundantSelf]) + } + + func testSortedSwitchCaseMultilineWithOneComment() { + let input = """ + switch self { + case let .type, // something + let .conditionalCompilation: + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, + let .type: // something + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchCaseMultilineWithComments() { + let input = """ + switch self { + case let .type, // typeComment + let .conditionalCompilation: // conditionalCompilationComment + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, // conditionalCompilationComment + let .type: // typeComment + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: [.indent]) + } + + func testSortedSwitchCaseMultilineWithCommentsAndMoreThanOneCasePerLine() { + let input = """ + switch self { + case let .type, // typeComment + let .type1, .type2, + let .conditionalCompilation: // conditionalCompilationComment + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, // conditionalCompilationComment + let .type, // typeComment + let .type1, + .type2: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchCaseMultiline() { + let input = """ + switch self { + case let .type, + let .conditionalCompilation: + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, + let .type: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchCaseMultipleAssociatedValues() { + let input = """ + switch self { + case let .b(whatever, whatever2), .a(whatever): + break + } + """ + let output = """ + switch self { + case .a(whatever), let .b(whatever, whatever2): + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchCaseOneLineWithoutSpaces() { + let input = """ + switch self { + case .b,.a: + break + } + """ + let output = """ + switch self { + case .a,.b: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases, .spaceAroundOperators]) + } + + func testSortedSwitchCaseLet() { + let input = """ + switch self { + case let .b(whatever), .a(whatever): + break + } + """ + let output = """ + switch self { + case .a(whatever), let .b(whatever): + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchCaseOneCaseDoesNothing() { + let input = """ + switch self { + case "a": + break + } + """ + testFormatting(for: input, rule: .sortSwitchCases) + } + + func testSortedSwitchStrings() { + let input = """ + switch self { + case "GET", "POST", "PUT", "DELETE": + break + } + """ + let output = """ + switch self { + case "DELETE", "GET", "POST", "PUT": + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchWhereConditionNotLastCase() { + let input = """ + switch self { + case .b, .c, .a where isTrue: + break + } + """ + testFormatting(for: input, + rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchWhereConditionLastCase() { + let input = """ + switch self { + case .b, .c where isTrue, .a: + break + } + """ + let output = """ + switch self { + case .a, .b, .c where isTrue: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortNumericSwitchCases() { + let input = """ + switch foo { + case 12, 3, 5, 7, 8, 10, 1: + break + } + """ + let output = """ + switch foo { + case 1, 3, 5, 7, 8, 10, 12: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchTuples() { + let input = """ + switch foo { + case (.foo, _), + (.bar, _), + (.baz, _), + (_, .foo): + } + """ + let output = """ + switch foo { + case (_, .foo), + (.bar, _), + (.baz, _), + (.foo, _): + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchTuples2() { + let input = """ + switch self { + case (.quux, .bar), + (_, .foo), + (_, .bar), + (_, .baz), + (.foo, .bar): + } + """ + let output = """ + switch self { + case (_, .bar), + (_, .baz), + (_, .foo), + (.foo, .bar), + (.quux, .bar): + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortSwitchCasesShortestFirst() { + let input = """ + switch foo { + case let .fooAndBar(baz, quux), + let .foo(baz): + } + """ + let output = """ + switch foo { + case let .foo(baz), + let .fooAndBar(baz, quux): + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortHexLiteralCasesInAscendingOrder() { + let input = """ + switch value { + case 0x30 ... 0x39, // 0-9 + 0x0300 ... 0x036F, + 0x1DC0 ... 0x1DFF, + 0x20D0 ... 0x20FF, + 0xFE20 ... 0xFE2F: + return true + default: + return false + } + """ + testFormatting(for: input, rule: .sortSwitchCases) + } + + func testMixedOctalHexIntAndBinaryLiteralCasesInAscendingOrder() { + let input = """ + switch value { + case 0o3, + 0x20, + 110, + 0b1111110: + return true + default: + return false + } + """ + testFormatting(for: input, rule: .sortSwitchCases) + } + + func testSortSwitchCasesNoUnwrapReturn() { + let input = """ + switch self { + case .b, .a, .c, .e, .d: + return nil + } + """ + let output = """ + switch self { + case .a, .b, .c, .d, .e: + return nil + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } +} diff --git a/Tests/Rules/SortTypealiasesTests.swift b/Tests/Rules/SortTypealiasesTests.swift new file mode 100644 index 00000000..0015056d --- /dev/null +++ b/Tests/Rules/SortTypealiasesTests.swift @@ -0,0 +1,178 @@ +// +// SortTypealiasesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 5/6/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortTypealiasesTests: XCTestCase { + func testSortSingleLineTypealias() { + let input = """ + typealias Placeholders = Foo & Bar & Quux & Baaz + """ + + let output = """ + typealias Placeholders = Baaz & Bar & Foo & Quux + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortMultilineTypealias() { + let input = """ + typealias Placeholders = Foo & Bar + & Quux & Baaz + """ + + let output = """ + typealias Placeholders = Baaz & Bar + & Foo & Quux + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortMultilineTypealiasWithComments() { + let input = """ + typealias Placeholders = Foo & Bar // Comment about Bar + // Comment about Quux + & Quux & Baaz // Comment about Baaz + """ + + let output = """ + typealias Placeholders = Baaz // Comment about Baaz + & Bar // Comment about Bar + & Foo + // Comment about Quux + & Quux + """ + + testFormatting(for: input, [output], rules: [.sortTypealiases, .indent, .trailingSpace]) + } + + func testSortWrappedMultilineTypealias1() { + let input = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let output = """ + typealias Dependencies = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortWrappedMultilineTypealias2() { + let input = """ + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let output = """ + typealias Dependencies + = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortWrappedMultilineTypealiasWithComments() { + let input = """ + typealias Dependencies + // Comment about FooProviding + = FooProviding + // Comment about BarProviding + & BarProviding + & QuuxProviding // Comment about QuuxProviding + // Comment about BaazProviding + & BaazProviding // Comment about BaazProviding + """ + + let output = """ + typealias Dependencies + // Comment about BaazProviding + = BaazProviding // Comment about BaazProviding + // Comment about BarProviding + & BarProviding + // Comment about FooProviding + & FooProviding + & QuuxProviding // Comment about QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortTypealiasesWithAssociatedTypes() { + let input = """ + typealias Collections + = Collection + & Collection + & Collection + & Collection + """ + + let output = """ + typealias Collections + = Collection + & Collection + & Collection + & Collection + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortTypeAliasesAndRemoveDuplicates() { + let input = """ + typealias Placeholders = Foo & Bar & Quux & Baaz & Bar + + typealias Dependencies1 + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + & FooProviding + + typealias Dependencies2 + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + & BaazProviding + """ + + let output = """ + typealias Placeholders = Baaz & Bar & Foo & Quux + + typealias Dependencies1 + = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + + typealias Dependencies2 + = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } +} diff --git a/Tests/Rules/SpaceAroundBracesTests.swift b/Tests/Rules/SpaceAroundBracesTests.swift new file mode 100644 index 00000000..8109a3a7 --- /dev/null +++ b/Tests/Rules/SpaceAroundBracesTests.swift @@ -0,0 +1,64 @@ +// +// SpaceAroundBracesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundBracesTests: XCTestCase { + func testSpaceAroundTrailingClosure() { + let input = "if x{ y }else{ z }" + let output = "if x { y } else { z }" + testFormatting(for: input, output, rule: .spaceAroundBraces, + exclude: [.wrapConditionalBodies]) + } + + func testNoSpaceAroundClosureInsiderParens() { + let input = "foo({ $0 == 5 })" + testFormatting(for: input, rule: .spaceAroundBraces, + exclude: [.trailingClosures]) + } + + func testNoExtraSpaceAroundBracesAtStartOrEndOfFile() { + let input = "{ foo }" + testFormatting(for: input, rule: .spaceAroundBraces) + } + + func testNoSpaceAfterPrefixOperator() { + let input = "let foo = ..{ bar }" + testFormatting(for: input, rule: .spaceAroundBraces) + } + + func testNoSpaceBeforePostfixOperator() { + let input = "let foo = { bar }.." + testFormatting(for: input, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterOptionalProperty() { + let input = "var: Foo?{}" + let output = "var: Foo? {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterImplicitlyUnwrappedProperty() { + let input = "var: Foo!{}" + let output = "var: Foo! {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterNumber() { + let input = "if x = 5{}" + let output = "if x = 5 {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterString() { + let input = "if x = \"\"{}" + let output = "if x = \"\" {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } +} diff --git a/Tests/Rules/SpaceAroundBracketsTests.swift b/Tests/Rules/SpaceAroundBracketsTests.swift new file mode 100644 index 00000000..7efae618 --- /dev/null +++ b/Tests/Rules/SpaceAroundBracketsTests.swift @@ -0,0 +1,106 @@ +// +// SpaceAroundBracketsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundBracketsTests: XCTestCase { + func testSubscriptNoAddSpacing() { + let input = "foo[bar] = baz" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testSubscriptRemoveSpacing() { + let input = "foo [bar] = baz" + let output = "foo[bar] = baz" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testArrayLiteralSpacing() { + let input = "foo = [bar, baz]" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testAsArrayCastingSpacing() { + let input = "foo as[String]" + let output = "foo as [String]" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAsOptionalArrayCastingSpacing() { + let input = "foo as? [String]" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testIsArrayTestingSpacing() { + let input = "if foo is[String] {}" + let output = "if foo is [String] {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testKeywordAsIdentifierBracketSpacing() { + let input = "if foo.is[String] {}" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testSpaceBeforeTupleIndexSubscript() { + let input = "foo.1 [2]" + let output = "foo.1[2]" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testRemoveSpaceBetweenBracketAndParen() { + let input = "let foo = bar[5] ()" + let output = "let foo = bar[5]()" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testRemoveSpaceBetweenBracketAndParenInsideClosure() { + let input = "let foo = bar { [Int] () }" + let output = "let foo = bar { [Int]() }" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAddSpaceBetweenCaptureListAndParen() { + let input = "let foo = bar { [self](foo: Int) in foo }" + let output = "let foo = bar { [self] (foo: Int) in foo }" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAddSpaceBetweenInoutAndStringArray() { + let input = "func foo(arg _: inout[String]) {}" + let output = "func foo(arg _: inout [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAddSpaceBetweenConsumingAndStringArray() { + let input = "func foo(arg _: consuming[String]) {}" + let output = "func foo(arg _: consuming [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets, + exclude: [.noExplicitOwnership]) + } + + func testAddSpaceBetweenBorrowingAndStringArray() { + let input = "func foo(arg _: borrowing[String]) {}" + let output = "func foo(arg _: borrowing [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets, + exclude: [.noExplicitOwnership]) + } + + func testAddSpaceBetweenSendingAndStringArray() { + let input = "func foo(arg _: sending[String]) {}" + let output = "func foo(arg _: sending [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testSpaceNotRemovedBetweenAsOperatorAndBracket() { + // https://github.com/nicklockwood/SwiftFormat/issues/1846 + let input = "@Test(arguments: [kSecReturnRef, kSecReturnAttributes] as [String])" + testFormatting(for: input, rule: .spaceAroundBrackets) + } +} diff --git a/Tests/Rules/SpaceAroundCommentsTests.swift b/Tests/Rules/SpaceAroundCommentsTests.swift new file mode 100644 index 00000000..a0111e1d --- /dev/null +++ b/Tests/Rules/SpaceAroundCommentsTests.swift @@ -0,0 +1,36 @@ +// +// SpaceAroundCommentsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/31/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundCommentsTests: XCTestCase { + func testSpaceAroundCommentInParens() { + let input = "(/* foo */)" + let output = "( /* foo */ )" + testFormatting(for: input, output, rule: .spaceAroundComments, + exclude: [.redundantParens]) + } + + func testNoSpaceAroundCommentAtStartAndEndOfFile() { + let input = "/* foo */" + testFormatting(for: input, rule: .spaceAroundComments) + } + + func testNoSpaceAroundCommentBeforeComma() { + let input = "(foo /* foo */ , bar)" + let output = "(foo /* foo */, bar)" + testFormatting(for: input, output, rule: .spaceAroundComments) + } + + func testSpaceAroundSingleLineComment() { + let input = "func foo() {// comment\n}" + let output = "func foo() { // comment\n}" + testFormatting(for: input, output, rule: .spaceAroundComments) + } +} diff --git a/Tests/Rules/SpaceAroundGenericsTests.swift b/Tests/Rules/SpaceAroundGenericsTests.swift new file mode 100644 index 00000000..82c5a988 --- /dev/null +++ b/Tests/Rules/SpaceAroundGenericsTests.swift @@ -0,0 +1,28 @@ +// +// SpaceAroundGenericsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundGenericsTests: XCTestCase { + func testSpaceAroundGenerics() { + let input = "Foo >" + let output = "Foo>" + testFormatting(for: input, output, rule: .spaceAroundGenerics) + } + + func testSpaceAroundGenericsFollowedByAndOperator() { + let input = "if foo is Foo && baz {}" + testFormatting(for: input, rule: .spaceAroundGenerics, exclude: [.andOperator]) + } + + func testSpaceAroundGenericResultBuilder() { + let input = "func foo(@SomeResultBuilder builder: () -> Void) {}" + testFormatting(for: input, rule: .spaceAroundGenerics) + } +} diff --git a/Tests/Rules/SpaceAroundOperatorsTests.swift b/Tests/Rules/SpaceAroundOperatorsTests.swift new file mode 100644 index 00000000..99cdfaaa --- /dev/null +++ b/Tests/Rules/SpaceAroundOperatorsTests.swift @@ -0,0 +1,754 @@ +// +// SpaceAroundOperatorsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/22/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundOperatorsTests: XCTestCase { + func testSpaceAfterColon() { + let input = "let foo:Bar = 5" + let output = "let foo: Bar = 5" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenOptionalAndDefaultValue() { + let input = "let foo: String?=nil" + let output = "let foo: String? = nil" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenImplictlyUnwrappedOptionalAndDefaultValue() { + let input = "let foo: String!=nil" + let output = "let foo: String! = nil" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpacePreservedBetweenOptionalTryAndDot() { + let input = "let foo: Int = try? .init()" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpacePreservedBetweenForceTryAndDot() { + let input = "let foo: Int = try! .init()" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceBetweenOptionalAndDefaultValueInFunction() { + let input = "func foo(bar _: String?=nil) {}" + let output = "func foo(bar _: String? = nil) {}" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoSpaceAddedAfterColonInSelector() { + let input = "@objc(foo:bar:)" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceAfterColonInSwitchCase() { + let input = "switch x { case .y:break }" + let output = "switch x { case .y: break }" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceAfterColonInSwitchDefault() { + let input = "switch x { default:break }" + let output = "switch x { default: break }" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceAfterComma() { + let input = "let foo = [1,2,3]" + let output = "let foo = [1, 2, 3]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenColonAndEnumValue() { + let input = "[.Foo:.Bar]" + let output = "[.Foo: .Bar]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenCommaAndEnumValue() { + let input = "[.Foo,.Bar]" + let output = "[.Foo, .Bar]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoRemoveSpaceAroundEnumInBrackets() { + let input = "[ .red ]" + testFormatting(for: input, rule: .spaceAroundOperators, + exclude: [.spaceInsideBrackets]) + } + + func testSpaceBetweenSemicolonAndEnumValue() { + let input = "statement;.Bar" + let output = "statement; .Bar" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpacePreservedBetweenEqualsAndEnumValue() { + let input = "foo = .Bar" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testNoSpaceBeforeColon() { + let input = "let foo : Bar = 5" + let output = "let foo: Bar = 5" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpacePreservedBeforeColonInTernary() { + let input = "foo ? bar : baz" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpacePreservedAroundEnumValuesInTernary() { + let input = "foo ? .Bar : .Baz" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceBeforeColonInNestedTernary() { + let input = "foo ? (hello + a ? b: c) : baz" + let output = "foo ? (hello + a ? b : c) : baz" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoSpaceBeforeComma() { + let input = "let foo = [1 , 2 , 3]" + let output = "let foo = [1, 2, 3]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceAtStartOfLine() { + let input = "print(foo\n ,bar)" + let output = "print(foo\n , bar)" + testFormatting(for: input, output, rule: .spaceAroundOperators, + exclude: [.leadingDelimiters]) + } + + func testSpaceAroundInfixMinus() { + let input = "foo-bar" + let output = "foo - bar" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoSpaceAroundPrefixMinus() { + let input = "foo + -bar" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceAroundLessThan() { + let input = "foo [ + String: String + ] + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypeDeclaration4() { + let input = """ + func foo() -> [String: [ + String: Int + ]] + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypeDeclaration5() { + let input = """ + let foo = [String: [ + String: Int + ]]() + """ + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) + } + + func testTrailingCommaNotAddedToTypeDeclaration6() { + let input = """ + let foo = [String: [ + (Foo<[ + String + ]>, [ + Int + ]) + ]]() + """ + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) + } + + func testTrailingCommaNotAddedToTypeDeclaration7() { + let input = """ + func foo() -> Foo<[String: [ + String: Int + ]]> + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypeDeclaration8() { + let input = """ + extension Foo { + var bar: [ + Int + ] { + fatalError() + } + } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypealias() { + let input = """ + typealias Foo = [ + Int + ] + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToCaptureList() { + let input = """ + let foo = { [ + self + ] in } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToCaptureListWithComment() { + let input = """ + let foo = { [ + self // captures self + ] in } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToCaptureListWithMainActor() { + let input = """ + let closure = { @MainActor [ + foo = state.foo, + baz = state.baz + ] _ in } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToArrayExtension() { + let input = """ + extension [ + Int + ] { + func foo() {} + } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + // trailingCommas = false + + func testCommaNotAddedToLastItem() { + let input = "[\n foo,\n bar\n]" + let options = FormatOptions(trailingCommas: false) + testFormatting(for: input, rule: .trailingCommas, options: options) + } + + func testCommaRemovedFromLastItem() { + let input = "[\n foo,\n bar,\n]" + let output = "[\n foo,\n bar\n]" + let options = FormatOptions(trailingCommas: false) + testFormatting(for: input, output, rule: .trailingCommas, options: options) + } +} diff --git a/Tests/Rules/TrailingSpaceTests.swift b/Tests/Rules/TrailingSpaceTests.swift new file mode 100644 index 00000000..5b8fc0d8 --- /dev/null +++ b/Tests/Rules/TrailingSpaceTests.swift @@ -0,0 +1,58 @@ +// +// TrailingSpaceTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 11/24/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class TrailingSpaceTests: XCTestCase { + // truncateBlankLines = true + + func testTrailingSpace() { + let input = "foo \nbar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceAtEndOfFile() { + let input = "foo " + let output = "foo" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceInMultilineComments() { + let input = "/* foo \n bar */" + let output = "/* foo\n bar */" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceInSingleLineComments() { + let input = "// foo \n// bar " + let output = "// foo\n// bar" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTruncateBlankLine() { + let input = "foo {\n // bar\n \n // baz\n}" + let output = "foo {\n // bar\n\n // baz\n}" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceInArray() { + let input = "let foo = [\n 1,\n \n 2,\n]" + let output = "let foo = [\n 1,\n\n 2,\n]" + testFormatting(for: input, output, rule: .trailingSpace, exclude: [.redundantSelf]) + } + + // truncateBlankLines = false + + func testNoTruncateBlankLine() { + let input = "foo {\n // bar\n \n // baz\n}" + let options = FormatOptions(truncateBlankLines: false) + testFormatting(for: input, rule: .trailingSpace, options: options) + } +} diff --git a/Tests/Rules/TypeSugarTests.swift b/Tests/Rules/TypeSugarTests.swift new file mode 100644 index 00000000..fea20bac --- /dev/null +++ b/Tests/Rules/TypeSugarTests.swift @@ -0,0 +1,238 @@ +// +// TypeSugarTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 2/2/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class TypeSugarTests: XCTestCase { + // arrays + + func testArrayTypeConvertedToSugar() { + let input = "var foo: Array" + let output = "var foo: [String]" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftArrayTypeConvertedToSugar() { + let input = "var foo: Swift.Array" + let output = "var foo: [String]" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testArrayNestedTypeAliasNotConvertedToSugar() { + let input = "typealias Indices = Array.Indices" + testFormatting(for: input, rule: .typeSugar) + } + + func testArrayTypeReferenceConvertedToSugar() { + let input = "let type = Array.Type" + let output = "let type = [Foo].Type" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftArrayTypeReferenceConvertedToSugar() { + let input = "let type = Swift.Array.Type" + let output = "let type = [Foo].Type" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testArraySelfReferenceConvertedToSugar() { + let input = "let type = Array.self" + let output = "let type = [Foo].self" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftArraySelfReferenceConvertedToSugar() { + let input = "let type = Swift.Array.self" + let output = "let type = [Foo].self" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testArrayDeclarationNotConvertedToSugar() { + let input = "struct Array {}" + testFormatting(for: input, rule: .typeSugar) + } + + func testExtensionTypeSugar() { + let input = """ + extension Array {} + extension Optional {} + extension Dictionary {} + extension Optional>>> {} + """ + + let output = """ + extension [Foo] {} + extension Foo? {} + extension [Foo: Bar] {} + extension [[Foo: [Bar]]]? {} + """ + testFormatting(for: input, output, rule: .typeSugar) + } + + // dictionaries + + func testDictionaryTypeConvertedToSugar() { + let input = "var foo: Dictionary" + let output = "var foo: [String: Int]" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftDictionaryTypeConvertedToSugar() { + let input = "var foo: Swift.Dictionary" + let output = "var foo: [String: Int]" + testFormatting(for: input, output, rule: .typeSugar) + } + + // optionals + + func testOptionalPropertyTypeNotConvertedToSugarByDefault() { + let input = "var bar: Optional" + testFormatting(for: input, rule: .typeSugar) + } + + func testOptionalTypeConvertedToSugar() { + let input = "var foo: Optional" + let output = "var foo: String?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testSwiftOptionalTypeConvertedToSugar() { + let input = "var foo: Swift.Optional" + let output = "var foo: String?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalClosureParenthesizedConvertedToSugar() { + let input = "var foo: Optional<(Int) -> String>" + let output = "var foo: ((Int) -> String)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalTupleWrappedInParensConvertedToSugar() { + let input = "let foo: Optional<(foo: Int, bar: String)>" + let output = "let foo: (foo: Int, bar: String)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalComposedProtocolWrappedInParensConvertedToSugar() { + let input = "let foo: Optional" + let output = "let foo: (UIView & Foo)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testSwiftOptionalClosureParenthesizedConvertedToSugar() { + let input = "var foo: Swift.Optional<(Int) -> String>" + let output = "var foo: ((Int) -> String)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testStrippingSwiftNamespaceInOptionalTypeWhenConvertedToSugar() { + let input = "Swift.Optional" + let output = "String?" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testStrippingSwiftNamespaceDoesNotStripPreviousSwiftNamespaceReferences() { + let input = "let a: Swift.String = Optional" + let output = "let a: Swift.String = String?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalTypeInsideCaseConvertedToSugar() { + let input = "if case .some(Optional.some(let foo)) = bar else {}" + let output = "if case .some(Any?.some(let foo)) = bar else {}" + testFormatting(for: input, output, rule: .typeSugar, exclude: [.hoistPatternLet]) + } + + func testSwitchCaseOptionalNotReplaced() { + let input = """ + switch foo { + case Optional.none: + } + """ + testFormatting(for: input, rule: .typeSugar) + } + + func testCaseOptionalNotReplaced2() { + let input = "if case Optional.none = foo {}" + testFormatting(for: input, rule: .typeSugar) + } + + func testUnwrappedOptionalSomeParenthesized() { + let input = "func foo() -> Optional> {}" + let output = "func foo() -> (some Publisher)? {}" + testFormatting(for: input, output, rule: .typeSugar) + } + + // swift parser bug + + func testAvoidSwiftParserBugWithClosuresInsideArrays() { + let input = "var foo = Array<(_ image: Data?) -> Void>()" + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) + } + + func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { + let input = "var foo = Dictionary Void>()" + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) + } + + func testAvoidSwiftParserBugWithClosuresInsideOptionals() { + let input = "var foo = Optional<(_ image: Data?) -> Void>()" + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) + } + + func testDontOverApplyBugWorkaround() { + let input = "var foo: Array<(_ image: Data?) -> Void>" + let output = "var foo: [(_ image: Data?) -> Void]" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testDontOverApplyBugWorkaround2() { + let input = "var foo: Dictionary Void>" + let output = "var foo: [String: (_ image: Data?) -> Void]" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testDontOverApplyBugWorkaround3() { + let input = "var foo: Optional<(_ image: Data?) -> Void>" + let output = "var foo: ((_ image: Data?) -> Void)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testDontOverApplyBugWorkaround4() { + let input = "var foo = Array<(image: Data?) -> Void>()" + let output = "var foo = [(image: Data?) -> Void]()" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) + } + + func testDontOverApplyBugWorkaround5() { + let input = "var foo = Array<(Data?) -> Void>()" + let output = "var foo = [(Data?) -> Void]()" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) + } + + func testDontOverApplyBugWorkaround6() { + let input = "var foo = Dictionary Void>>()" + let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) + } +} diff --git a/Tests/Rules/UnusedArgumentsTests.swift b/Tests/Rules/UnusedArgumentsTests.swift new file mode 100644 index 00000000..19d6ba01 --- /dev/null +++ b/Tests/Rules/UnusedArgumentsTests.swift @@ -0,0 +1,1209 @@ +// +// UnusedArgumentsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/3/17. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class UnusedArgumentsTests: XCTestCase { + // closures + + func testUnusedTypedClosureArguments() { + let input = "let foo = { (bar: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" + let output = "let foo = { (_: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedUntypedClosureArguments() { + let input = "let foo = { bar, baz in\n print(\"Hello \\(baz)\")\n}" + let output = "let foo = { _, baz in\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testNoRemoveClosureReturnType() { + let input = "let foo = { () -> Foo.Bar in baz() }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureThrows() { + let input = "let foo = { () throws in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureTypedThrows() { + let input = "let foo = { () throws(Foo) in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureGenericReturnTypes() { + let input = "let foo = { () -> Promise in bar }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureTupleReturnTypes() { + let input = "let foo = { () -> (Int, Int) in (5, 6) }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureGenericArgumentTypes() { + let input = "let foo = { (_: Foo) in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveFunctionNameBeforeForLoop() { + let input = "{\n func foo() -> Int {}\n for a in b {}\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testClosureTypeInClosureArgumentsIsNotMangled() { + let input = "{ (foo: (Int) -> Void) in }" + let output = "{ (_: (Int) -> Void) in }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedUnnamedClosureArguments() { + let input = "{ (_ foo: Int, _ bar: Int) in }" + let output = "{ (_: Int, _: Int) in }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedInoutClosureArgumentsNotMangled() { + let input = "{ (foo: inout Foo, bar: inout Bar) in }" + let output = "{ (_: inout Foo, _: inout Bar) in }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMalformedFunctionNotMisidentifiedAsClosure() { + let input = "func foo() { bar(5) {} in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments() { + let input = """ + forEach { foo, bar in + guard let foo = foo, let bar = bar else { + return + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedPartUsedArguments() { + let input = """ + forEach { foo, bar in + guard let foo = baz, bar == baz else { + return + } + } + """ + let output = """ + forEach { _, bar in + guard let foo = baz, bar == baz else { + return + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedParameterUsedInSameGuard() { + let input = """ + forEach { foo in + guard let foo = bar, baz = foo else { + return + } + } + """ + let output = """ + forEach { _ in + guard let foo = bar, baz = foo else { + return + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testParameterUsedInForIn() { + let input = """ + forEach { foos in + for foo in foos { + print(foo) + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInWhereClause() { + let input = """ + forEach { foo in + if bar where foo { + print(bar) + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInSwitchCase() { + let input = """ + forEach { foo in + switch bar { + case let baz: + foo = baz + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInStringInterpolation() { + let input = """ + forEach { foo in + print("\\(foo)") + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedClosureArgument() { + let input = """ + _ = Parser { input in + let parser = Parser.with(input) + return parser + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty, .propertyType]) + } + + func testShadowedClosureArgument2() { + let input = """ + _ = foo { input in + let input = ["foo": "Foo", "bar": "Bar"][input] + return input + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testUnusedPropertyWrapperArgument() { + let input = """ + ForEach($list.notes) { $note in + Text(note.foobar) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedThrowingClosureArgument() { + let input = "foo = { bar throws in \"\" }" + let output = "foo = { _ throws in \"\" }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedTypedThrowingClosureArgument() { + let input = "foo = { bar throws(Foo) in \"\" }" + let output = "foo = { _ throws(Foo) in \"\" }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUsedThrowingClosureArgument() { + let input = "let foo = { bar throws in bar + \"\" }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUsedTypedThrowingClosureArgument() { + let input = "let foo = { bar throws(Foo) in bar + \"\" }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedTrailingAsyncClosureArgument() { + let input = """ + app.get { foo async in + print("No foo") + } + """ + let output = """ + app.get { _ async in + print("No foo") + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedTrailingAsyncClosureArgument2() { + let input = """ + app.get { foo async -> String in + "No foo" + } + """ + let output = """ + app.get { _ async -> String in + "No foo" + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedTrailingAsyncClosureArgument3() { + let input = """ + app.get { (foo: String) async -> String in + "No foo" + } + """ + let output = """ + app.get { (_: String) async -> String in + "No foo" + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUsedTrailingAsyncClosureArgument() { + let input = """ + app.get { foo async -> String in + "\\(foo)" + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testTrailingAsyncClosureArgumentAlreadyMarkedUnused() { + let input = "app.get { _ async in 5 }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedTrailingClosureArgumentCalledAsync() { + let input = """ + app.get { async -> String in + "No async" + } + """ + let output = """ + app.get { _ -> String in + "No async" + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testClosureArgumentUsedInGuardNotRemoved() { + let input = """ + bar(for: quux) { _, _, foo in + guard + let baz = quux.baz, + foo.contains(where: { $0.baz == baz }) + else { + return + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testClosureArgumentUsedInIfNotRemoved() { + let input = """ + foo = { reservations, _ in + if let reservations, eligibleToShow( + reservations, + accountService: accountService + ) { + coordinator.startFlow() + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + // init + + func testParameterUsedInInit() { + let input = """ + init(m: Rotation) { + let x = sqrt(max(0, m)) / 2 + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedParametersShadowedInTupleAssignment() { + let input = """ + init(x: Int, y: Int, v: Vector) { + let (x, y) = v + } + """ + let output = """ + init(x _: Int, y _: Int, v: Vector) { + let (x, y) = v + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUsedParametersShadowedInAssignmentFromFunctionCall() { + let input = """ + init(r: Double) { + let r = max(abs(r), epsilon) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArgumentInSwitch() { + let input = """ + init(_ action: Action, hub: Hub) { + switch action { + case let .get(hub, key): + self = .get(key, hub) + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInSwitchCaseAfterShadowing() { + let input = """ + func issue(name: String) -> String { + switch self { + case .b(let name): return name + case .a: return name + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.hoistPatternLet]) + } + + // functions + + func testMarkUnusedFunctionArgument() { + let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + let output = "func foo(bar _: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedArgumentsInNonVoidFunction() { + let input = "func foo(bar: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" + let output = "func foo(bar _: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedArgumentsInThrowsFunction() { + let input = "func foo(bar: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" + let output = "func foo(bar _: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedArgumentsInOptionalReturningFunction() { + let input = "func foo(bar: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" + let output = "func foo(bar _: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testNoMarkUnusedArgumentsInProtocolFunction() { + let input = "protocol Foo {\n func foo(bar: Int) -> Int\n var bar: Int { get }\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedUnnamedFunctionArgument() { + let input = "func foo(_ foo: Int) {}" + let output = "func foo(_: Int) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedInoutFunctionArgumentIsNotMangled() { + let input = "func foo(_ foo: inout Foo) {}" + let output = "func foo(_: inout Foo) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedInternallyRenamedFunctionArgument() { + let input = "func foo(foo bar: Int) {}" + let output = "func foo(foo _: Int) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testNoMarkProtocolFunctionArgument() { + let input = "func foo(foo bar: Int)\nvar bar: Bool { get }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testMembersAreNotArguments() { + let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(bar.baz)\")\n}" + let output = "func foo(bar: Int, baz _: String) {\n print(\"Hello \\(bar.baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testLabelsAreNotArguments() { + let input = "func foo(bar: Int, baz: String) {\n bar: while true { print(baz) }\n}" + let output = "func foo(bar _: Int, baz: String) {\n bar: while true { print(baz) }\n}" + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.wrapLoopBodies]) + } + + func testDictionaryLiteralsRuinEverything() { + let input = "func foo(bar: Int, baz: Int) {\n let quux = [bar: 1, baz: 2]\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testOperatorArgumentsAreUnnamed() { + let input = "func == (lhs: Int, rhs: Int) { false }" + let output = "func == (_: Int, _: Int) { false }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedtFailableInitArgumentsAreNotMangled() { + let input = "init?(foo: Bar) {}" + let output = "init?(foo _: Bar) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testTreatEscapedArgumentsAsUsed() { + let input = "func foo(default: Int) -> Int {\n return `default`\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testPartiallyMarkedUnusedArguments() { + let input = "func foo(bar: Bar, baz _: Baz) {}" + let output = "func foo(bar _: Bar, baz _: Baz) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testPartiallyMarkedUnusedArguments2() { + let input = "func foo(bar _: Bar, baz: Baz) {}" + let output = "func foo(bar _: Bar, baz _: Baz) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnownedUnsafeNotStripped() { + let input = """ + func foo() { + var num = 0 + Just(1) + .sink { [unowned(unsafe) self] in + num += $0 + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUnusedArguments() { + let input = """ + func foo(bar: String, baz: Int) { + let bar = "bar", baz = 5 + print(bar, baz) + } + """ + let output = """ + func foo(bar _: String, baz _: Int) { + let bar = "bar", baz = 5 + print(bar, baz) + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedUsedArguments2() { + let input = """ + func foo(things: [String], form: Form) { + let form = FormRequest( + things: things, + form: form + ) + print(form) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments3() { + let input = """ + func zoomTo(locations: [Foo], count: Int) { + let num = count + guard num > 0, locations.count >= count else { + return + } + print(locations) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments4() { + let input = """ + func foo(bar: Int) { + if let bar = baz { + return + } + print(bar) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments5() { + let input = """ + func doSomething(with number: Int) { + if let number = Int?(123), + number == 456 + { + print("Not likely") + } + + if number == 180 { + print("Bullseye!") + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArgumentInSwitchCase() { + let input = """ + func foo(bar baz: Foo) -> Foo? { + switch (a, b) { + case (0, _), + (_, nil): + return .none + case let (1, baz?): + return .bar(baz) + default: + return baz + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.sortSwitchCases]) + } + + func testTryArgumentNotMarkedUnused() { + let input = """ + func foo(bar: String) throws -> String? { + let bar = + try parse(bar) + return bar + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testTryAwaitArgumentNotMarkedUnused() { + let input = """ + func foo(bar: String) async throws -> String? { + let bar = try + await parse(bar) + return bar + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testTypedTryAwaitArgumentNotMarkedUnused() { + let input = """ + func foo(bar: String) async throws(Foo) -> String? { + let bar = try + await parse(bar) + return bar + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testConditionalIfLetMarkedAsUnused() { + let input = """ + func foo(bar: UIViewController) { + if let bar = baz { + bar.loadViewIfNeeded() + } + } + """ + let output = """ + func foo(bar _: UIViewController) { + if let bar = baz { + bar.loadViewIfNeeded() + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testConditionAfterIfCaseHoistedLetNotMarkedUnused() { + let input = """ + func isLoadingFirst(for tabID: String) -> Bool { + if case let .loading(.first(loadingTabID, _)) = requestState.status, loadingTabID == tabID { + return true + } else { + return false + } + + print(tabID) + } + """ + let options = FormatOptions(hoistPatternLet: true) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused2() { + let input = """ + func isLoadingFirst(for tabID: String) -> Bool { + if case .loading(.first(let loadingTabID, _)) = requestState.status, loadingTabID == tabID { + return true + } else { + return false + } + + print(tabID) + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused3() { + let input = """ + private func isFocusedView(formDataID: FormDataID) -> Bool { + guard + case .selected(let selectedFormDataID) = currentState.selectedFormItemAction, + selectedFormDataID == formDataID + else { + return false + } + + return true + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused4() { + let input = """ + private func totalRowContent(priceItemsCount: Int, priceBreakdownStyle: PriceBreakdownStyle) { + if + case .all(let shouldCollapseByDefault, _) = priceBreakdownStyle, + priceItemsCount > 0 + { + // .. + } + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused5() { + let input = """ + private mutating func clearPendingRemovals(itemIDs: Set) { + for change in changes { + if case .removal(itemID: let itemID) = change, !itemIDs.contains(itemID) { + // .. + } + } + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testSecondConditionAfterTupleMarkedUnused() { + let input = """ + func foobar(bar: Int) { + let (foo, baz) = (1, 2), bar = 3 + print(foo, bar, baz) + } + """ + let output = """ + func foobar(bar _: Int) { + let (foo, baz) = (1, 2), bar = 3 + print(foo, bar, baz) + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedParamsInTupleAssignment() { + let input = """ + func foobar(_ foo: Int, _ bar: Int, _ baz: Int, _ quux: Int) { + let ((foo, bar), baz) = ((foo, quux), bar) + print(foo, bar, baz, quux) + } + """ + let output = """ + func foobar(_ foo: Int, _ bar: Int, _: Int, _ quux: Int) { + let ((foo, bar), baz) = ((foo, quux), bar) + print(foo, bar, baz, quux) + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedIfLetNotMarkedAsUnused() { + let input = """ + func method(_ foo: Int?, _ bar: String?) { + if let foo = foo, let bar = bar {} + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShorthandIfLetNotMarkedAsUnused() { + let input = """ + func method(_ foo: Int?, _ bar: String?) { + if let foo, let bar {} + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShorthandLetMarkedAsUnused() { + let input = """ + func method(_ foo: Int?, _ bar: Int?) { + var foo, bar: Int? + } + """ + let output = """ + func method(_: Int?, _: Int?) { + var foo, bar: Int? + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedClosureNotMarkedUnused() { + let input = """ + func foo(bar: () -> Void) { + let bar = { + print("log") + bar() + } + bar() + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedClosureMarkedUnused() { + let input = """ + func foo(bar: () -> Void) { + let bar = { + print("log") + } + bar() + } + """ + let output = """ + func foo(bar _: () -> Void) { + let bar = { + print("log") + } + bar() + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testViewBuilderAnnotationDoesntBreakUnusedArgDetection() { + let input = """ + struct Foo { + let content: View + + public init( + responsibleFileID: StaticString = #fileID, + @ViewBuilder content: () -> View) + { + self.content = content() + } + } + """ + let output = """ + struct Foo { + let content: View + + public init( + responsibleFileID _: StaticString = #fileID, + @ViewBuilder content: () -> View) + { + self.content = content() + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments, + exclude: [.braces, .wrapArguments]) + } + + func testArgumentUsedInDictionaryLiteral() { + let input = """ + class MyClass { + func testMe(value: String) { + let value = [ + "key": value + ] + print(value) + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.trailingCommas]) + } + + func testArgumentUsedAfterIfDefInsideSwitchBlock() { + let input = """ + func test(string: String) { + let number = 5 + switch number { + #if DEBUG + case 1: + print("ONE") + #endif + default: + print("NOT ONE") + } + print(string) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUsedConsumingArgument() { + let input = """ + func close(file: consuming FileHandle) { + file.close() + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testUsedConsumingBorrowingArguments() { + let input = """ + func foo(a: consuming Foo, b: borrowing Bar) { + consume(a) + borrow(b) + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testUnusedConsumingArgument() { + let input = """ + func close(file: consuming FileHandle) { + print("no-op") + } + """ + let output = """ + func close(file _: consuming FileHandle) { + print("no-op") + } + """ + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testUnusedConsumingBorrowingArguments() { + let input = """ + func foo(a: consuming Foo, b: borrowing Bar) { + print("no-op") + } + """ + let output = """ + func foo(a _: consuming Foo, b _: borrowing Bar) { + print("no-op") + } + """ + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testFunctionArgumentUsedInGuardNotRemoved() { + let input = """ + func scrollViewDidEndDecelerating(_ visibleDayRange: DayRange) { + guard + store.state.request.isIdle, + let nextDayToLoad = store.state.request.nextCursor?.lowerBound, + visibleDayRange.upperBound.distance(to: nextDayToLoad) < 30 + else { + return + } + + store.handle(.loadNext) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testFunctionArgumentUsedInGuardNotRemoved2() { + let input = """ + func convert( + filter: Filter, + accounts: [Account], + outgoingTotal: MulticurrencyTotal? + ) -> History? { + guard + let firstParameter = incomingTotal?.currency, + let secondParameter = outgoingTotal?.currency, + isFilter(filter, accounts: accounts) + else { + return nil + } + return History(firstParameter, secondParameter) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testFunctionArgumentUsedInGuardNotRemoved3() { + let input = """ + public func flagMessage(_ message: Message) { + model.withState { state in + guard + let flagMessageFeature, + shouldAllowFlaggingMessage( + message, + thread: state.thread) + else { return } + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.wrapArguments, .wrapConditionalBodies, .indent]) + } + + // functions (closure-only) + + func testNoMarkFunctionArgument() { + let input = "func foo(_ bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + let options = FormatOptions(stripUnusedArguments: .closureOnly) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + // functions (unnamed-only) + + func testNoMarkNamedFunctionArgument() { + let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + let options = FormatOptions(stripUnusedArguments: .unnamedOnly) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testRemoveUnnamedFunctionArgument() { + let input = "func foo(_ foo: Int) {}" + let output = "func foo(_: Int) {}" + let options = FormatOptions(stripUnusedArguments: .unnamedOnly) + testFormatting(for: input, output, rule: .unusedArguments, options: options) + } + + func testNoRemoveInternalFunctionArgumentName() { + let input = "func foo(foo bar: Int) {}" + let options = FormatOptions(stripUnusedArguments: .unnamedOnly) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + // init + + func testMarkUnusedInitArgument() { + let input = "init(bar: Int, baz: String) {\n self.baz = baz\n}" + let output = "init(bar _: Int, baz: String) {\n self.baz = baz\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + // subscript + + func testMarkUnusedSubscriptArgument() { + let input = "subscript(foo: Int, baz: String) -> String {\n return get(baz)\n}" + let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedUnnamedSubscriptArgument() { + let input = "subscript(_ foo: Int, baz: String) -> String {\n return get(baz)\n}" + let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedNamedSubscriptArgument() { + let input = "subscript(foo foo: Int, baz: String) -> String {\n return get(baz)\n}" + let output = "subscript(foo _: Int, baz: String) -> String {\n return get(baz)\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedArgumentWithClosureShadowingParamName() { + let input = """ + func test(foo: Foo) { + let foo = { + if foo.bar { + baaz + } else { + bar + } + }() + print(foo) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedArgumentWithConditionalAssignmentShadowingParamName() { + let input = """ + func test(foo: Foo) { + let foo = + if foo.bar { + baaz + } else { + bar + } + print(foo) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedArgumentWithSwitchAssignmentShadowingParamName() { + let input = """ + func test(foo: Foo) { + let foo = + switch foo.bar { + case true: + baaz + case false: + bar + } + print(foo) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedArgumentWithConditionalAssignmentNotShadowingParamName() { + let input = """ + func test(bar: Bar) { + let quux = + if foo { + bar + } else { + baaz + } + print(quux) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testIssue1688_1() { + let input = #""" + func urlTestContains(path: String, strict _: Bool = true) -> Bool { + let path = if path.hasSuffix("/") { path } else { "\(path)/" } + + return false + } + """# + testFormatting(for: input, rule: .unusedArguments, exclude: [.wrapConditionalBodies]) + } + + func testIssue1688_2() { + let input = """ + enum Sample { + func invite(lang: String, randomValue: Int) -> String { + let flag: String? = if randomValue > 0 { "hello" } else { nil } + + let lang = if let flag { flag } else { lang } + + return lang + } + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.wrapConditionalBodies, .redundantProperty]) + } + + func testIssue1694() { + let input = """ + listenForUpdates() { [weak self] update, error in + guard let update, error == nil else { + return + } + self?.configure(update) + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantParens]) + } + + func testIssue1696() { + let input = """ + func someFunction(with parameter: Int) -> Int { + let parameter = max( + 200, + parameter + ) + return parameter + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testArgumentUsedInsideMultilineStringLiteral() { + // https://github.com/nicklockwood/SwiftFormat/issues/1847 + let input = #""" + public func foo(message: String = "hi") { + let message = + """ + Message: \(message) + """ + print(message) + } + """# + testFormatting(for: input, rule: .unusedArguments) + } + + func testArgumentUsedInsideMultilineStringLiteral2() { + let input = #""" + func foo(message: String) { + let message = + """ + \(1 + 1) + Message: \(message) + """ + print(message) + } + """# + testFormatting(for: input, rule: .unusedArguments) + } + + func testArgumentUsedInsideMultilineArrayLiteral() { + let input = #""" + func foo(message: String) { + let message = [ + message, + ] + print(message) + } + """# + testFormatting(for: input, rule: .unusedArguments) + } + + func test1850() { + let input = """ + init(a3: A42.ID) { + a15.a22 + .sink { + Task { + switch a23.a27 { + #if !A34 + case .a35: + A31.a32(.a33(.a34Failed)) + #endif + case .a36: + break + } + } + } + .store(in: &a14) + + if a4.a57 == nil { + a51 = a3.a.b?.a54 + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } +} diff --git a/Tests/Rules/UnusedPrivateDeclarationTests.swift b/Tests/Rules/UnusedPrivateDeclarationTests.swift new file mode 100644 index 00000000..13bd534e --- /dev/null +++ b/Tests/Rules/UnusedPrivateDeclarationTests.swift @@ -0,0 +1,380 @@ +// +// UnusedPrivateDeclarationTests.swift +// SwiftFormatTests +// +// Created by Manny Lopez on 7/17/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class UnusedPrivateDeclarationTests: XCTestCase { + func testRemoveUnusedPrivate() { + let input = """ + struct Foo { + private var foo = "foo" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveUsedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveMultipleUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + fileprivate var baz = "baz" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testRemoveMixedUsedAndUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + fileprivate var baz = "baz" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + let output = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveFilePrivateUsedInSameStruct() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + + func useFoo() { + print(foo) + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedFilePrivateInNestedStruct() { + let input = """ + struct Foo { + var bar = "bar" + + struct Inner { + fileprivate var foo = "foo" + } + } + """ + let output = """ + struct Foo { + var bar = "bar" + + struct Inner { + } + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) + } + + func testDoNotRemoveFilePrivateUsedInNestedStruct() { + let input = """ + struct Foo { + var bar = "bar" + + struct Inner { + fileprivate var foo = "foo" + func useFoo() { + print(foo) + } + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedFileprivateFunction() { + let input = """ + struct Foo { + var bar = "bar" + + fileprivate func sayHi() { + print("hi") + } + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, [output], rules: [.unusedPrivateDeclaration, .blankLinesAtEndOfScope]) + } + + func testDoNotRemoveUnusedFileprivateOperatorDefinition() { + let input = """ + private class Foo: Equatable { + fileprivate static func == (_: Foo, _: Foo) -> Bool { + return true + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemovePrivateDeclarationButDoNotRemoveUnusedPrivateType() { + let input = """ + private struct Foo { + private func bar() { + print("test") + } + } + """ + let output = """ + private struct Foo { + } + """ + + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) + } + + func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { + let input = """ + private extension Foo { + private func doSomething() {} + func anotherFunction() {} + } + """ + let output = """ + private extension Foo { + func anotherFunction() {} + } + """ + + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testRemovesPrivateTypealias() { + let input = """ + enum Foo { + struct Bar {} + private typealias Baz = Bar + } + """ + let output = """ + enum Foo { + struct Bar {} + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testDoesntRemoveFileprivateInit() { + let input = """ + struct Foo { + fileprivate init() {} + static let foo = Foo() + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.propertyType]) + } + + func testCanDisableUnusedPrivateDeclarationRule() { + let input = """ + private enum Foo { + // swiftformat:disable:next unusedPrivateDeclaration + fileprivate static func bar() {} + } + """ + + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemovePropertyWrapperPrefixesIfUsed() { + let input = """ + struct ContentView: View { + public init() { + _showButton = .init(initialValue: false) + } + + @State private var showButton: Bool + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemoveUnderscoredDeclarationIfUsed() { + let input = """ + struct Foo { + private var _showButton: Bool = true + print(_showButton) + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemoveBacktickDeclarationIfUsed() { + let input = """ + struct Foo { + fileprivate static var `default`: Bool = true + func printDefault() { + print(Foo.default) + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemoveBacktickUsage() { + let input = """ + struct Foo { + fileprivate static var foo = true + func printDefault() { + print(Foo.`foo`) + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.redundantBackticks]) + } + + func testDoNotRemovePreservedPrivateDeclarations() { + let input = """ + enum Foo { + private static let registryAssociation = false + } + """ + let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) + testFormatting(for: input, rule: .unusedPrivateDeclaration, options: options) + } + + func testDoNotRemoveOverridePrivateMethodDeclarations() { + let input = """ + class Poodle: Dog { + override private func makeNoise() { + print("Yip!") + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveOverridePrivatePropertyDeclarations() { + let input = """ + class Poodle: Dog { + override private var age: Int { + 7 + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveObjcPrivatePropertyDeclaration() { + let input = """ + struct Foo { + @objc + private var bar = "bar" + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveObjcPrivateFunctionDeclaration() { + let input = """ + struct Foo { + @objc + private func doSomething() {} + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveIBActionPrivateFunctionDeclaration() { + let input = """ + class FooViewController: UIViewController { + @IBAction private func buttonPressed(_: UIButton) { + print("Button pressed!") + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedRecursivePrivateDeclaration() { + let input = """ + struct Planet { + private typealias Dependencies = UniverseBuilderProviding // unused + private var mass: Double // unused + private func distance(to: Planet) { } // unused + private func gravitationalForce(between other: Planet) -> Double { + (G * mass * other.mass) / distance(to: other).squared() + } // unused + + var ageInBillionYears: Double { + ageInMillionYears / 1000 + } + } + """ + let output = """ + struct Planet { + var ageInBillionYears: Double { + ageInMillionYears / 1000 + } + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclarations) + } +} diff --git a/Tests/Rules/VoidTests.swift b/Tests/Rules/VoidTests.swift new file mode 100644 index 00000000..0468c36b --- /dev/null +++ b/Tests/Rules/VoidTests.swift @@ -0,0 +1,261 @@ +// +// VoidTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 10/19/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class VoidTests: XCTestCase { + func testEmptyParensReturnValueConvertedToVoid() { + let input = "() -> ()" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testSpacedParensReturnValueConvertedToVoid() { + let input = "() -> ( \n)" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testParensContainingCommentNotConvertedToVoid() { + let input = "() -> ( /* Hello World */ )" + testFormatting(for: input, rule: .void) + } + + func testParensNotConvertedToVoidIfLocalOverrideExists() { + let input = """ + struct Void {} + let foo = () -> () + print(foo) + """ + testFormatting(for: input, rule: .void) + } + + func testParensRemovedAroundVoid() { + let input = "() -> (Void)" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testVoidArgumentConvertedToEmptyParens() { + let input = "Void -> Void" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testVoidArgumentInParensNotConvertedToEmptyParens() { + let input = "(Void) -> Void" + testFormatting(for: input, rule: .void) + } + + func testAnonymousVoidArgumentNotConvertedToEmptyParens() { + let input = "{ (_: Void) -> Void in }" + testFormatting(for: input, rule: .void, exclude: [.redundantVoidReturnType]) + } + + func testFuncWithAnonymousVoidArgumentNotStripped() { + let input = "func foo(_: Void) -> Void" + testFormatting(for: input, rule: .void) + } + + func testFunctionThatReturnsAFunction() { + let input = "(Void) -> Void -> ()" + let output = "(Void) -> () -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testFunctionThatReturnsAFunctionThatThrows() { + let input = "(Void) -> Void throws -> ()" + let output = "(Void) -> () throws -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testFunctionThatReturnsAFunctionThatHasTypedThrows() { + let input = "(Void) -> Void throws(Foo) -> ()" + let output = "(Void) -> () throws(Foo) -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testChainOfFunctionsIsNotChanged() { + let input = "() -> () -> () -> Void" + testFormatting(for: input, rule: .void) + } + + func testChainOfFunctionsWithThrowsIsNotChanged() { + let input = "() -> () throws -> () throws -> Void" + testFormatting(for: input, rule: .void) + } + + func testChainOfFunctionsWithTypedThrowsIsNotChanged() { + let input = "() -> () throws(Foo) -> () throws(Foo) -> Void" + testFormatting(for: input, rule: .void) + } + + func testVoidThrowsIsNotMangled() { + let input = "(Void) throws -> Void" + testFormatting(for: input, rule: .void) + } + + func testVoidTypedThrowsIsNotMangled() { + let input = "(Void) throws(Foo) -> Void" + testFormatting(for: input, rule: .void) + } + + func testEmptyClosureArgsNotMangled() { + let input = "{ () in }" + testFormatting(for: input, rule: .void) + } + + func testEmptyClosureReturnValueConvertedToVoid() { + let input = "{ () -> () in }" + let output = "{ () -> Void in }" + testFormatting(for: input, output, rule: .void, exclude: [.redundantVoidReturnType]) + } + + func testAnonymousVoidClosureNotChanged() { + let input = "{ (_: Void) in }" + testFormatting(for: input, rule: .void, exclude: [.unusedArguments]) + } + + func testVoidLiteralConvertedToParens() { + let input = "foo(Void())" + let output = "foo(())" + testFormatting(for: input, output, rule: .void) + } + + func testVoidLiteralConvertedToParens2() { + let input = "let foo = Void()" + let output = "let foo = ()" + testFormatting(for: input, output, rule: .void) + } + + func testVoidLiteralReturnValueConvertedToParens() { + let input = """ + func foo() { + return Void() + } + """ + let output = """ + func foo() { + return () + } + """ + testFormatting(for: input, output, rule: .void) + } + + func testVoidLiteralReturnValueConvertedToParens2() { + let input = "{ _ in Void() }" + let output = "{ _ in () }" + testFormatting(for: input, output, rule: .void) + } + + func testNamespacedVoidLiteralNotConverted() { + // TODO: it should actually be safe to convert Swift.Void - only unsafe for other namespaces + let input = "let foo = Swift.Void()" + testFormatting(for: input, rule: .void) + } + + func testMalformedFuncDoesNotCauseInvalidOutput() throws { + let input = "func baz(Void) {}" + testFormatting(for: input, rule: .void) + } + + func testEmptyParensInGenericsConvertedToVoid() { + let input = "Foo<(), ()>" + let output = "Foo" + testFormatting(for: input, output, rule: .void) + } + + func testCaseVoidNotUnwrapped() { + let input = "case some(Void)" + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypeNotConverted() { + let input = """ + struct Void {} + let foo = Void() + print(foo) + """ + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypeForwardReferenceNotConverted() { + let input = """ + let foo = Void() + print(foo) + struct Void {} + """ + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypealiasNotConverted() { + let input = """ + typealias Void = MyVoid + let foo = Void() + print(foo) + """ + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypealiasForwardReferenceNotConverted() { + let input = """ + let foo = Void() + print(foo) + typealias Void = MyVoid + """ + testFormatting(for: input, rule: .void) + } + + // useVoid = false + + func testUseVoidOptionFalse() { + let input = "(Void) -> Void" + let output = "(()) -> ()" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, output, rule: .void, options: options) + } + + func testNamespacedVoidNotConverted() { + let input = "() -> Swift.Void" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testTypealiasVoidNotConverted() { + let input = "public typealias Void = ()" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testVoidClosureReturnValueConvertedToEmptyTuple() { + let input = "{ () -> Void in }" + let output = "{ () -> () in }" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, output, rule: .void, options: options, exclude: [.redundantVoidReturnType]) + } + + func testNoConvertVoidSelfToTuple() { + let input = "Void.self" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testNoConvertVoidTypeToTuple() { + let input = "Void.Type" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testCaseVoidConvertedToTuple() { + let input = "case some(Void)" + let output = "case some(())" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, output, rule: .void, options: options) + } +} diff --git a/Tests/Rules/WrapArgumentsTests.swift b/Tests/Rules/WrapArgumentsTests.swift new file mode 100644 index 00000000..6492e0f7 --- /dev/null +++ b/Tests/Rules/WrapArgumentsTests.swift @@ -0,0 +1,2167 @@ +// +// WrapArgumentsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 11/23/16. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapArgumentsTests: XCTestCase { + func testIndentFirstElementWhenApplyingWrap() { + let input = """ + let foo = Set([ + Thing(), + Thing(), + ]) + """ + let output = """ + let foo = Set([ + Thing(), + Thing(), + ]) + """ + testFormatting(for: input, output, rule: .wrapArguments, exclude: [.propertyType]) + } + + func testWrapArgumentsDoesntIndentTrailingComment() { + let input = """ + foo( // foo + bar: Int + ) + """ + let output = """ + foo( // foo + bar: Int + ) + """ + testFormatting(for: input, output, rule: .wrapArguments) + } + + func testWrapArgumentsDoesntIndentClosingBracket() { + let input = """ + [ + "foo": [ + ], + ] + """ + testFormatting(for: input, rule: .wrapArguments) + } + + func testWrapParametersDoesNotAffectFunctionDeclaration() { + let input = "foo(\n bar _: Int,\n baz _: String\n)" + let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapParametersClosureAfterParameterListDoesNotWrapClosureArguments() { + let input = """ + func foo() {} + bar = (baz: 5, quux: 7, + quuz: 10) + """ + let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapParametersNotSetWrapArgumentsAfterFirstDefaultsToAfterFirst() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersNotSetWrapArgumentsBeforeFirstDefaultsToBeforeFirst() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersNotSetWrapArgumentsPreserveDefaultsToPreserve() { + let input = "func foo(\n bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnSameLine() { + let input = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnNextLine() { + let input = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnSameLineAndForce() { + let input = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine, callSiteClosingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnNextLineAndForce() { + let input = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionCallClosingParenOnNextLineAndForce() { + let input = """ + foo( + bar: 42, + baz: "foo" + ) + """ + let output = """ + foo( + bar: 42, + baz: "foo") + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testIndentMultilineStringWhenWrappingArguments() { + let input = """ + foobar(foo: \"\"" + baz + \"\"", + bar: \"\"" + baz + \"\"") + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testHandleXcodeTokenApplyingWrap() { + let input = """ + test(image: \u{003c}#T##UIImage#>, name: "Name") + """ + + let output = """ + test( + image: \u{003c}#T##UIImage#>, + name: "Name" + ) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testIssue1530() { + let input = """ + extension DRAutoWeatherReadRequestResponse { + static let mock = DRAutoWeatherReadRequestResponse( + offlineFirstWeather: DRAutoWeatherReadRequestResponse.DROfflineFirstWeather( + daily: .mockWeatherID, hourly: [] + ) + ) + } + """ + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) + } + + // MARK: wrapParameters + + // MARK: preserve + + func testAfterFirstPreserved() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testAfterFirstPreservedIndentFixed() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testAfterFirstPreservedNewlineRemoved() { + let input = "func foo(bar _: Int,\n baz _: String\n) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testBeforeFirstPreserved() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testBeforeFirstPreservedIndentFixed() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testBeforeFirstPreservedNewlineAdded() { + let input = "func foo(\n bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersAfterMultilineComment() { + let input = """ + /** + Some function comment. + */ + func barFunc( + _ firstParam: FirstParamType, + secondParam: SecondParamType + ) + """ + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst + + func testBeforeFirstConvertedToAfterFirst() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoWrapInnerArguments() { + let input = "func foo(\n bar _: Int,\n baz _: foo(bar, baz)\n) {}" + let output = "func foo(bar _: Int,\n baz _: foo(bar, baz)) {}" + let options = FormatOptions(wrapParameters: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst, maxWidth + + func testWrapAfterFirstIfMaxLengthExceeded() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let output = """ + func foo(bar: Int, + baz: String) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + func testWrapAfterFirstIfMaxLengthExceeded2() { + let input = """ + func foo(bar: Int, baz: String, quux: Bool) -> Bool {} + """ + let output = """ + func foo(bar: Int, + baz: String, + quux: Bool) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + func testWrapAfterFirstIfMaxLengthExceeded3() { + let input = """ + func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} + """ + let output = """ + func foo(bar: Int, baz: String, + aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + func testWrapAfterFirstIfMaxLengthExceeded3WithWrap() { + let input = """ + func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} + """ + let output = """ + func foo(bar: Int, baz: String, + aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) + -> Bool {} + """ + let output2 = """ + func foo(bar: Int, baz: String, + aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) + -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) + testFormatting(for: input, [output, output2], + rules: [.wrapArguments, .wrap], + options: options, exclude: [.unusedArguments]) + } + + func testWrapAfterFirstIfMaxLengthExceeded4WithWrap() { + let input = """ + func foo(bar: String, baz: String, quux: Bool) -> Bool {} + """ + let output = """ + func foo(bar: String, + baz: String, + quux: Bool) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], + options: options, exclude: [.unusedArguments]) + } + + func testWrapAfterFirstIfMaxLengthExceededInClassScopeWithWrap() { + let input = """ + class TestClass { + func foo(bar: String, baz: String, quux: Bool) -> Bool {} + } + """ + let output = """ + class TestClass { + func foo(bar: String, + baz: String, + quux: Bool) + -> Bool {} + } + """ + let output2 = """ + class TestClass { + func foo(bar: String, + baz: String, + quux: Bool) + -> Bool {} + } + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) + testFormatting(for: input, [output, output2], + rules: [.wrapArguments, .wrap], + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersListInClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: (Int, + Int, + String) -> Int = { _, _, _ in + 0 + } + """ + let output2 = """ + var mathFunction: (Int, + Int, + String) + -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 30) + testFormatting(for: input, [output, output2], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersAfterFirstIfMaxLengthExceededInReturnType() { + let input = """ + func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} + """ + let output2 = """ + func foo(bar: Int, baz: String, + quux: Bool) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 50) + testFormatting(for: input, [input, output2], rules: [.wrapArguments], + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersAfterFirstWithSeparatedArgumentLabels() { + let input = """ + func foo(with + bar: Int, and + baz: String, and + quux: Bool + ) -> LongReturnType {} + """ + let output = """ + func foo(with bar: Int, + and baz: String, + and quux: Bool) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, + options: options, exclude: [.unusedArguments]) + } + + // MARK: beforeFirst + + func testWrapAfterFirstConvertedToWrapBefore() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testLinebreakInsertedAtEndOfWrappedFunction() { + let input = "func foo(\n bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testAfterFirstConvertedToBeforeFirst() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersListBeforeFirstInClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInThrowingClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) throws -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) throws -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInTypedThrowingClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) throws(Foo) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) throws(Foo) -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInRethrowingClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) rethrows -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) rethrows -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParams() { + let input = """ + func foo(bar: Int, baz: (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: Int, baz: ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParamsAfterWrappedClosure() { + let input = """ + func foo(bar: Int, baz: (Int, + Bool, String) -> Int, quux: String) -> Int {} + """ + let output = """ + func foo(bar: Int, baz: ( + Int, + Bool, + String + ) -> Int, quux: String) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInEscapingClosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: @escaping (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: @escaping ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInNoEscapeClosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: @noescape (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: @noescape ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInEscapingAutoclosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: @escaping @autoclosure (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: @escaping @autoclosure ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + // MARK: beforeFirst, maxWidth + + func testWrapBeforeFirstIfMaxLengthExceeded() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let output = """ + func foo( + bar: Int, + baz: String + ) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testNoWrapBeforeFirstIfMaxLengthNotExceeded() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 42) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testNoWrapGenericsIfClosingBracketWithinMaxWidth() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let output = """ + func foo( + bar: Int, + baz: String + ) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testWrapAlreadyWrappedArgumentsIfMaxLengthExceeded() { + let input = """ + func foo( + bar: Int, baz: String, quux: Bool + ) -> Bool {} + """ + let output = """ + func foo( + bar: Int, baz: String, + quux: Bool + ) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 26) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersBeforeFirstIfMaxLengthExceededInReturnType() { + let input = """ + func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} + """ + let output2 = """ + func foo( + bar: Int, + baz: String, + quux: Bool + ) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 50) + testFormatting(for: input, [input, output2], rules: [.wrapArguments], + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersBeforeFirstWithSeparatedArgumentLabels() { + let input = """ + func foo(with + bar: Int, and + baz: String + ) -> LongReturnType {} + """ + let output = """ + func foo( + with bar: Int, + and baz: String + ) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInClosureTypeWithMaxWidth() { + let input = """ + var mathFunction: (Int, Int, String) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) + testFormatting(for: input, [output], rules: [.wrapArguments], + options: options) + } + + func testNoWrapBeforeFirstMaxWidthNotExceededWithLineBreakSinceLastEndOfArgumentScope() { + let input = """ + class Foo { + func foo() { + bar() + } + + func bar(foo: String, bar: Int) { + quux() + } + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 37) + testFormatting(for: input, rule: .wrapArguments, + options: options, exclude: [.unusedArguments]) + } + + func testNoWrapSubscriptWithSingleElement() { + let input = "guard let foo = bar[0] {}" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 20) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapArrayWithSingleElement() { + let input = "let foo = [0]" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 11) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapDictionaryWithSingleElement() { + let input = "let foo = [bar: baz]" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 15) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapImageLiteral() { + let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapColorLiteral() { + let input = """ + if let color = #colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1) {} + """ + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testWrapArgumentsNoIndentBlankLines() { + let input = """ + let foo = [ + + bar, + + ] + """ + let options = FormatOptions(wrapCollections: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + // MARK: closingParenPosition = true + + func testParenOnSameLineWhenWrapAfterFirstConvertedToWrapBefore() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testParenOnSameLineWhenWrapBeforeFirstUnchanged() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(\n bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testParenOnSameLineWhenWrapBeforeFirstPreserved() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(\n bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + // MARK: indent with tabs + + func testTabIndentWrappedFunctionWithSmartTabs() { + let input = """ + func foo(bar: Int, + baz: Int) {} + """ + let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testTabIndentWrappedFunctionWithoutSmartTabs() { + let input = """ + func foo(bar: Int, + baz: Int) {} + """ + let output = """ + func foo(bar: Int, + \t\t\t\t baz: Int) {} + """ + let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, + tabWidth: 2, smartTabs: false) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + // MARK: - wrapArguments --wrapArguments + + func testWrapArgumentsDoesNotAffectFunctionDeclaration() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapArgumentsDoesNotAffectInit() { + let input = "init(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapArgumentsDoesNotAffectSubscript() { + let input = "subscript(\n bar _: Int,\n baz _: String\n) -> Int {}" + let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst + + func testWrapArgumentsConvertBeforeFirstToAfterFirst() { + let input = """ + foo( + bar _: Int, + baz _: String + ) + """ + let output = """ + foo(bar _: Int, + baz _: String) + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testCorrectWrapIndentForNestedArguments() { + let input = "foo(\nbar: (\nx: 0,\ny: 0\n),\nbaz: (\nx: 0,\ny: 0\n)\n)" + let output = "foo(bar: (x: 0,\n y: 0),\n baz: (x: 0,\n y: 0))" + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoRemoveLinebreakAfterCommentInArguments() { + let input = "a(b // comment\n)" + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoRemoveLinebreakAfterCommentInArguments2() { + let input = """ + foo(bar: bar + // , + // baz: baz + ) {} + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.indent]) + } + + func testConsecutiveCodeCommentsNotIndented() { + let input = """ + foo(bar: bar, + // bar, + // baz, + quux) + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst maxWidth + + func testWrapArgumentsAfterFirst() { + let input = """ + foo(bar: Int, baz: String, quux: Bool) + """ + let output = """ + foo(bar: Int, + baz: String, + quux: Bool) + """ + let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + // MARK: beforeFirst + + func testClosureInsideParensNotWrappedOntoNextLine() { + let input = "foo({\n bar()\n})" + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.trailingClosures]) + } + + func testNoMangleCommentedLinesWhenWrappingArguments() { + let input = """ + foo(bar: bar + // , + // baz: baz + ) {} + """ + let output = """ + foo( + bar: bar + // , + // baz: baz + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoMangleCommentedLinesWhenWrappingArgumentsWithNoCommas() { + let input = """ + foo(bar: bar + // baz: baz + ) {} + """ + let output = """ + foo( + bar: bar + // baz: baz + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + // MARK: preserve + + func testWrapArgumentsDoesNotAffectLessThanOperator() { + let input = """ + func foo() { + guard foo < bar.count else { return nil } + } + """ + let options = FormatOptions(wrapArguments: .preserve) + testFormatting(for: input, rule: .wrapArguments, + options: options, exclude: [.wrapConditionalBodies]) + } + + // MARK: - --wrapArguments, --wrapParameter + + // MARK: beforeFirst + + func testNoMistakeTernaryExpressionForArguments() { + let input = """ + (foo ? + bar : + baz) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.redundantParens]) + } + + // MARK: beforeFirst, maxWidth : string interpolation + + func testNoWrapBeforeFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(interpolation) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 40) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeFirstArgumentInStringInterpolation2() { + let input = """ + "a very long string literal with \\(interpolation) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 50) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeFirstArgumentInStringInterpolation3() { + let input = """ + "a very long string literal with \\(interpolated, variables) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 40) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeNestedFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(foo(interpolated)) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 45) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeNestedFirstArgumentInStringInterpolation2() { + let input = """ + "a very long string literal with \\(foo(interpolated, variables)) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 45) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapProtocolFuncParametersBeforeFirst() { + let input = """ + protocol Foo { + public func stringify(_ value: T, label: String) -> (T, String) + } + """ + let output = """ + protocol Foo { + public func stringify( + _ value: T, + label: String + ) -> (T, String) + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) + testFormatting(for: input, output, rule: .wrapArguments, + options: options) + } + + // MARK: afterFirst maxWidth : string interpolation + + func testNoWrapAfterFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(interpolated) inside" + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapParameters: .afterFirst, + maxWidth: 46) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapAfterFirstArgumentInStringInterpolation2() { + let input = """ + "a very long string literal with \\(interpolated, variables) inside" + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapParameters: .afterFirst, + maxWidth: 50) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapAfterNestedFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(foo(interpolated, variables)) inside" + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapParameters: .afterFirst, + maxWidth: 55) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // macros + + func testWrapMacroParametersBeforeFirst() { + let input = """ + @freestanding(expression) + public macro stringify(_ value: T, label: String) -> (T, String) + """ + let output = """ + @freestanding(expression) + public macro stringify( + _ value: T, + label: String + ) -> (T, String) + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) + testFormatting(for: input, output, rule: .wrapArguments, + options: options) + } + + // MARK: - wrapArguments --wrapCollections + + // MARK: beforeFirst + + func testNoDoubleSpaceAddedToWrappedArray() { + let input = "[ foo,\n bar ]" + let output = "[\n foo,\n bar\n]" + let options = FormatOptions(trailingCommas: false, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .spaceInsideBrackets], + options: options) + } + + func testTrailingCommasAddedToWrappedArray() { + let input = "[foo,\n bar]" + let output = "[\n foo,\n bar,\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testTrailingCommasAddedToWrappedNestedDictionary() { + let input = "[foo: [bar: baz,\n bar2: baz2]]" + let output = "[foo: [\n bar: baz,\n bar2: baz2,\n]]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testTrailingCommasAddedToSingleLineNestedDictionary() { + let input = "[\n foo: [bar: baz, bar2: baz2]]" + let output = "[\n foo: [bar: baz, bar2: baz2],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testTrailingCommasAddedToWrappedNestedDictionaries() { + let input = "[foo: [bar: baz,\n bar2: baz2],\n foo2: [bar: baz,\n bar2: baz2]]" + let output = "[\n foo: [\n bar: baz,\n bar2: baz2,\n ],\n foo2: [\n bar: baz,\n bar2: baz2,\n ],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testSpaceAroundEnumValuesInArray() { + let input = "[\n .foo,\n .bar, .baz,\n]" + let options = FormatOptions(wrapCollections: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: beforeFirst maxWidth + + func testWrapCollectionOnOneLineBeforeFirstWidthExceededInChainedFunctionCallAfterCollection() { + let input = """ + let foo = ["bar", "baz"].quux(quuz) + """ + let output2 = """ + let foo = ["bar", "baz"] + .quux(quuz) + """ + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 26) + testFormatting(for: input, [input, output2], + rules: [.wrapArguments], options: options) + } + + // MARK: afterFirst + + func testTrailingCommaRemovedInWrappedArray() { + let input = "[\n .foo,\n .bar,\n .baz,\n]" + let output = "[.foo,\n .bar,\n .baz]" + let options = FormatOptions(wrapCollections: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoRemoveLinebreakAfterCommentInElements() { + let input = "[a, // comment\n]" + let options = FormatOptions(wrapCollections: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapCollectionsConsecutiveCodeCommentsNotIndented() { + let input = """ + let a = [foo, + // bar, + // baz, + quux] + """ + let options = FormatOptions(wrapCollections: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapCollectionsConsecutiveCodeCommentsNotIndentedInWrapBeforeFirst() { + let input = """ + let a = [ + foo, + // bar, + // baz, + quux, + ] + """ + let options = FormatOptions(wrapCollections: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: preserve + + func testNoBeforeFirstPreservedAndTrailingCommaIgnoredInMultilineNestedDictionary() { + let input = "[foo: [bar: baz,\n bar2: baz2]]" + let output = "[foo: [bar: baz,\n bar2: baz2]]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionary() { + let input = "[\n foo: [bar: baz, bar2: baz2]]" + let output = "[\n foo: [bar: baz, bar2: baz2],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionaryWithOneNestedItem() { + let input = "[\n foo: [bar: baz]]" + let output = "[\n foo: [bar: baz],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + // MARK: - wrapArguments --wrapCollections & --wrapArguments + + // MARK: beforeFirst maxWidth + + func testWrapArgumentsBeforeFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { + let input = """ + foo(bar: ["baz", "quux"], quuz: corge) + """ + let output = """ + foo( + bar: ["baz", "quux"], + quuz: corge + ) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapCollections: .beforeFirst, + maxWidth: 26) + testFormatting(for: input, [output], + rules: [.wrapArguments], options: options) + } + + // MARK: afterFirst maxWidth + + func testWrapArgumentsAfterFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { + let input = """ + foo(bar: ["baz", "quux"], quuz: corge) + """ + let output = """ + foo(bar: ["baz", "quux"], + quuz: corge) + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapCollections: .beforeFirst, + maxWidth: 26) + testFormatting(for: input, [output], + rules: [.wrapArguments], options: options) + } + + // MARK: - wrapArguments Multiple Wraps On Same Line + + func testWrapAfterFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { + let input = """ + foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) + """ + let output = """ + foo.bar(baz: [qux, quux]) + .quuz([corge: grault], + garply: waldo) + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapCollections: .afterFirst, + maxWidth: 28) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], options: options) + } + + func testWrapAfterFirstWrapCollectionsBeforeFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { + let input = """ + foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) + """ + let output = """ + foo.bar(baz: [qux, quux]) + .quuz([corge: grault], + garply: waldo) + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapCollections: .beforeFirst, + maxWidth: 28) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], options: options) + } + + func testNoMangleNestedFunctionCalls() { + let input = """ + points.append(.curve( + quadraticBezier(p0.position.x, Double(p1.x), Double(p2.x), t), + quadraticBezier(p0.position.y, Double(p1.y), Double(p2.y), t) + )) + """ + let output = """ + points.append(.curve( + quadraticBezier( + p0.position.x, + Double(p1.x), + Double(p2.x), + t + ), + quadraticBezier( + p0.position.y, + Double(p1.y), + Double(p2.y), + t + ) + )) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 40) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], options: options) + } + + func testWrapArguments_typealias_beforeFirst() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 40) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_multipleTypealiases_beforeFirst() { + let input = """ + enum Namespace { + typealias DependenciesA = FooProviding & BarProviding + typealias DependenciesB = BaazProviding & QuuxProviding + } + """ + + let output = """ + enum Namespace { + typealias DependenciesA + = FooProviding + & BarProviding + typealias DependenciesB + = BaazProviding + & QuuxProviding + } + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 45) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_afterFirst() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 40) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_multipleTypealiases_afterFirst() { + let input = """ + enum Namespace { + typealias DependenciesA = FooProviding & BarProviding + typealias DependenciesB = BaazProviding & QuuxProviding + } + """ + + let output = """ + enum Namespace { + typealias DependenciesA = FooProviding + & BarProviding + typealias DependenciesB = BaazProviding + & QuuxProviding + } + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 45) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & BaazProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 100) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & + BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently2() { + let input = """ + enum Namespace { + typealias Dependencies = FooProviding & BarProviding + & BaazProviding & QuuxProviding + } + """ + + let output = """ + enum Namespace { + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + } + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently3() { + let input = """ + typealias Dependencies + = FooProviding & BarProviding & + BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently4() { + let input = """ + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistentlyWithComment() { + let input = """ + typealias Dependencies = FooProviding & BarProviding // trailing comment 1 + // Inline Comment 1 + & BaazProviding & QuuxProviding // trailing comment 2 + """ + + let output = """ + typealias Dependencies + = FooProviding + & BarProviding // trailing comment 1 + // Inline Comment 1 + & BaazProviding + & QuuxProviding // trailing comment 2 + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_singleTypePreserved() { + let input = """ + typealias Dependencies = FooProviding + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 10) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.wrap]) + } + + func testWrapArguments_typealias_preservesCommentsBetweenTypes() { + let input = """ + typealias Dependencies + // We use `FooProviding` because `FooFeature` depends on `Foo` + = FooProviding + // We use `BarProviding` because `BarFeature` depends on `Bar` + & BarProviding + // We use `BaazProviding` because `BaazFeature` depends on `Baaz` + & BaazProviding + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_preservesCommentsAfterTypes() { + let input = """ + typealias Dependencies + = FooProviding // We use `FooProviding` because `FooFeature` depends on `Foo` + & BarProviding // We use `BarProviding` because `BarFeature` depends on `Bar` + & BaazProviding // We use `BaazProviding` because `BaazFeature` depends on `Baaz` + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_withAssociatedType() { + let input = """ + typealias Collections = Collection & Collection & Collection & Collection + """ + + let output = """ + typealias Collections + = Collection + & Collection + & Collection + & Collection + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 50) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + // MARK: - -return wrap-if-multiline + + func testWrapReturnOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapReturnAndEffectOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) async -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) + async -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnAndEffectOnSingleLineFunctionDeclaration() { + let input = """ + func singleLineFunction() async throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnAndTypedEffectOnSingleLineFunctionDeclaration() { + let input = """ + func singleLineFunction() async throws(Foo) -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapEffectOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) async throws + -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) + async throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testUnwrapEffectOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) + async throws -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) async throws + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .never + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapArgumentsDoesntBreakFunctionDeclaration_issue_1776() { + let input = """ + struct OpenAPIController: RouteCollection { + let info = InfoObject(title: "Swagger {{cookiecutter.service_name}} - OpenAPI", + description: "{{cookiecutter.description}}", + contact: .init(email: "{{cookiecutter.email}}"), + version: Version(0, 0, 1)) + func boot(routes: RoutesBuilder) throws { + routes.get("swagger", "swagger.json") { + $0.application.routes.openAPI(info: info) + } + .excludeFromOpenAPI() + } + } + """ + + let options = FormatOptions(wrapEffects: .never) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) + } + + func testWrapEffectsNeverPreservesComments() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) + // Comment here between the parameters and effects + async throws -> String {} + """ + + let options = FormatOptions(closingParenPosition: .sameLine, wrapEffects: .never) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapReturnOnMultilineFunctionDeclarationWithAfterFirst() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) -> String {} + """ + + let output = """ + func multilineFunction(foo _: String, + bar _: String) + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting( + for: input, output, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testWrapReturnOnMultilineThrowingFunctionDeclarationWithAfterFirst() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) throws -> String {} + """ + + let output = """ + func multilineFunction(foo _: String, + bar _: String) throws + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting( + for: input, output, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testWrapReturnAndEffectOnMultilineThrowingFunctionDeclarationWithAfterFirst() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) throws -> String {} + """ + + let output = """ + func multilineFunction(foo _: String, + bar _: String) + throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting( + for: input, output, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testDoesntWrapReturnOnMultilineThrowingFunction() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) + throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting( + for: input, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testDoesntWrapReturnOnSingleLineFunctionDeclaration() { + let input = """ + func multilineFunction(foo _: String, bar _: String) -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineArray() { + let input = """ + final class Foo { + private static let array = [ + "one", + ] + + private func singleLine() -> String {} + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineMethodCall() { + let input = """ + public final class Foo { + public var multiLineMethodCall = Foo.multiLineMethodCall( + bar: bar, + baz: baz) + + func singleLine() -> String { + return "method body" + } + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) + } + + func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) -> String + {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: wrapConditions before-first + + func testWrapConditionsBeforeFirstPreservesMultilineStatements() { + let input = """ + if + let unwrappedFoo = Foo( + bar: bar, + baz: baz), + unwrappedFoo.elements + .compactMap({ $0 }) + .filter({ + if $0.matchesCondition { + return true + } else { + return false + } + }).isEmpty, + let bar = unwrappedFoo.bar, + let baz = unwrappedFoo.bar? + .first(where: { $0.isBaz }), + let unwrappedFoo2 = Foo( + bar: bar2, + baz: baz2), + let quux = baz.quux + {} + """ + testFormatting( + for: input, rules: [.wrapArguments, .indent], + options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), + exclude: [.propertyType] + ) + } + + func testWrapConditionsBeforeFirst() { + let input = """ + if let foo = foo, + let bar = bar, + foo == bar {} + + else if foo != bar, + let quux = quux {} + + if let baz = baz {} + + guard baz.filter({ $0 == foo }), + let bar = bar else {} + + while let foo = foo, + let bar = bar {} + """ + let output = """ + if + let foo = foo, + let bar = bar, + foo == bar {} + + else if + foo != bar, + let quux = quux {} + + if let baz = baz {} + + guard + baz.filter({ $0 == foo }), + let bar = bar else {} + + while + let foo = foo, + let bar = bar {} + """ + testFormatting( + for: input, output, rule: .wrapArguments, + options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), + exclude: [.wrapConditionalBodies] + ) + } + + func testWrapConditionsBeforeFirstWhereShouldPreserveExisting() { + let input = """ + else {} + + else + {} + + if foo == bar + {} + + guard let foo = bar else + {} + + guard let foo = bar + else {} + """ + testFormatting( + for: input, rule: .wrapArguments, + options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), + exclude: [.elseOnSameLine, .wrapConditionalBodies] + ) + } + + func testWrapConditionsAfterFirst() { + let input = """ + if + let foo = foo, + let bar = bar, + foo == bar {} + + else if + foo != bar, + let quux = quux {} + + else {} + + if let baz = baz {} + + guard + baz.filter({ $0 == foo }), + let bar = bar else {} + + while + let foo = foo, + let bar = bar {} + """ + let output = """ + if let foo = foo, + let bar = bar, + foo == bar {} + + else if foo != bar, + let quux = quux {} + + else {} + + if let baz = baz {} + + guard baz.filter({ $0 == foo }), + let bar = bar else {} + + while let foo = foo, + let bar = bar {} + """ + testFormatting( + for: input, output, rule: .wrapArguments, + options: FormatOptions(indent: " ", wrapConditions: .afterFirst), + exclude: [.wrapConditionalBodies] + ) + } + + func testWrapConditionsAfterFirstWhenFirstLineIsComment() { + let input = """ + guard + // Apply this rule to any function-like declaration + ["func", "init", "subscript"].contains(keyword.string), + // Opaque generic parameter syntax is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + // Validate that this is a generic method using angle bracket syntax, + // and find the indices for all of the key tokens + let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), + let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), + let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), + let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + genericSignatureStartIndex < paramListStartIndex, + genericSignatureEndIndex < paramListStartIndex, + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), + let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) + else { return } + """ + let output = """ + guard // Apply this rule to any function-like declaration + ["func", "init", "subscript"].contains(keyword.string), + // Opaque generic parameter syntax is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + // Validate that this is a generic method using angle bracket syntax, + // and find the indices for all of the key tokens + let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), + let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), + let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), + let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + genericSignatureStartIndex < paramListStartIndex, + genericSignatureEndIndex < paramListStartIndex, + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), + let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) + else { return } + """ + testFormatting( + for: input, [output], rules: [.wrapArguments, .indent], + options: FormatOptions(wrapConditions: .afterFirst), + exclude: [.wrapConditionalBodies] + ) + } +} diff --git a/Tests/Rules/WrapAttributesTests.swift b/Tests/Rules/WrapAttributesTests.swift new file mode 100644 index 00000000..0b74ae4d --- /dev/null +++ b/Tests/Rules/WrapAttributesTests.swift @@ -0,0 +1,645 @@ +// +// WrapAttributesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 7/26/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapAttributesTests: XCTestCase { + func testPreserveWrappedFuncAttributeByDefault() { + let input = """ + @objc + func foo() {} + """ + testFormatting(for: input, rule: .wrapAttributes) + } + + func testPreserveUnwrappedFuncAttributeByDefault() { + let input = """ + @objc func foo() {} + """ + testFormatting(for: input, rule: .wrapAttributes) + } + + func testWrapFuncAttribute() { + let input = """ + @available(iOS 14.0, *) func foo() {} + """ + let output = """ + @available(iOS 14.0, *) + func foo() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapInitAttribute() { + let input = """ + @available(iOS 14.0, *) init() {} + """ + let output = """ + @available(iOS 14.0, *) + init() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testMultipleAttributesNotSeparated() { + let input = """ + @objc @IBAction func foo {} + """ + let output = """ + @objc @IBAction + func foo {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, + options: options, exclude: [.redundantObjc]) + } + + func testFuncAttributeStaysWrapped() { + let input = """ + @available(iOS 14.0, *) + func foo() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testUnwrapFuncAttribute() { + let input = """ + @available(iOS 14.0, *) + func foo() {} + """ + let output = """ + @available(iOS 14.0, *) func foo() {} + """ + let options = FormatOptions(funcAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testUnwrapFuncAttribute2() { + let input = """ + class MyClass: NSObject { + @objc + func myFunction() { + print("Testing") + } + } + """ + let output = """ + class MyClass: NSObject { + @objc func myFunction() { + print("Testing") + } + } + """ + let options = FormatOptions(funcAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testFuncAttributeStaysUnwrapped() { + let input = """ + @objc func foo() {} + """ + let options = FormatOptions(funcAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testVarAttributeIsNotWrapped() { + let input = """ + @IBOutlet var foo: UIView? + + @available(iOS 14.0, *) + func foo() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapTypeAttribute() { + let input = """ + @available(iOS 14.0, *) class Foo {} + """ + let output = """ + @available(iOS 14.0, *) + class Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting( + for: input, + output, + rule: .wrapAttributes, + options: options + ) + } + + func testWrapExtensionAttribute() { + let input = """ + @available(iOS 14.0, *) extension Foo {} + """ + let output = """ + @available(iOS 14.0, *) + extension Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting( + for: input, + output, + rule: .wrapAttributes, + options: options + ) + } + + func testTypeAttributeStaysWrapped() { + let input = """ + @available(iOS 14.0, *) + struct Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testUnwrapTypeAttribute() { + let input = """ + @available(iOS 14.0, *) + enum Foo {} + """ + let output = """ + @available(iOS 14.0, *) enum Foo {} + """ + let options = FormatOptions(typeAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testTypeAttributeStaysUnwrapped() { + let input = """ + @objc class Foo {} + """ + let options = FormatOptions(typeAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testTestableImportIsNotWrapped() { + let input = """ + @testable import Framework + + @available(iOS 14.0, *) + class Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testModifiersDontAffectAttributeWrapping() { + let input = """ + @objc override public func foo {} + """ + let output = """ + @objc + override public func foo {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testClassFuncAttributeTreatedAsFunction() { + let input = """ + @objc class func foo {} + """ + let output = """ + @objc + class func foo {} + """ + let options = FormatOptions(funcAttributes: .prevLine, fragment: true) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testClassFuncAttributeNotTreatedAsType() { + let input = """ + @objc class func foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine, fragment: true) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testClassAttributeNotMistakenForClassLet() { + let input = """ + @objc final class MyClass: NSObject {} + let myClass = MyClass() + """ + let output = """ + @objc + final class MyClass: NSObject {} + let myClass = MyClass() + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testClassImportAttributeNotTreatedAsType() { + let input = """ + @testable import class Framework.Foo + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapPrivateSetComputedVarAttributes() { + let input = """ + @objc private(set) dynamic var foo = Foo() + """ + let output = """ + @objc + private(set) dynamic var foo = Foo() + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testWrapPrivateSetVarAttributes() { + let input = """ + @objc private(set) dynamic var foo = Foo() + """ + let output = """ + @objc + private(set) dynamic var foo = Foo() + """ + let options = FormatOptions(varAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testDontWrapPrivateSetVarAttributes() { + let input = """ + @objc + private(set) dynamic var foo = Foo() + """ + let output = """ + @objc private(set) dynamic var foo = Foo() + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testWrapConvenienceInitAttribute() { + let input = """ + @objc public convenience init() {} + """ + let output = """ + @objc + public convenience init() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapPropertyWrapperAttributeVarAttributes() { + let input = """ + @OuterType.Wrapper var foo: Int + """ + let output = """ + @OuterType.Wrapper + var foo: Int + """ + let options = FormatOptions(varAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapPropertyWrapperAttribute() { + let input = """ + @OuterType.Wrapper var foo: Int + """ + let output = """ + @OuterType.Wrapper + var foo: Int + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testDontWrapPropertyWrapperAttribute() { + let input = """ + @OuterType.Wrapper + var foo: Int + """ + let output = """ + @OuterType.Wrapper var foo: Int + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapGenericPropertyWrapperAttribute() { + let input = """ + @OuterType.Generic var foo: WrappedType + """ + let output = """ + @OuterType.Generic + var foo: WrappedType + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapGenericPropertyWrapperAttribute2() { + let input = """ + @OuterType.Generic.Foo var foo: WrappedType + """ + let output = """ + @OuterType.Generic.Foo + var foo: WrappedType + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testAttributeOnComputedProperty() { + let input = """ + extension SectionContainer: ContentProviding where Section: ContentProviding { + @_disfavoredOverload + public var content: Section.Content { + section.content + } + } + """ + + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapAvailableAttributeUnderMaxWidth() { + let input = """ + @available(*, unavailable, message: "This property is deprecated.") + var foo: WrappedType + """ + let output = """ + @available(*, unavailable, message: "This property is deprecated.") var foo: WrappedType + """ + let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testDoesntWrapAvailableAttributeWithLongMessage() { + // Unwrapping this attribute would just cause it to wrap in a different way: + // + // @available( + // *, + // unavailable, + // message: "This property is deprecated. It has a really long message." + // ) var foo: WrappedType + // + // so instead leave it un-wrapped to preserve the existing formatting. + let input = """ + @available(*, unavailable, message: "This property is deprecated. It has a really long message.") + var foo: WrappedType + """ + let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testDoesntWrapComplexAttribute() { + let input = """ + @Option( + name: ["myArgument"], + help: "Long help text for my example arg from Swift argument parser") + var foo: WrappedType + """ + let options = FormatOptions(closingParenPosition: .sameLine, varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testDoesntWrapComplexMultilineAttribute() { + let input = """ + @available(*, deprecated, message: "Deprecated!") + var foo: WrappedType + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapsComplexAttribute() { + let input = """ + @available(*, deprecated, message: "Deprecated!") var foo: WrappedType + """ + + let output = """ + @available(*, deprecated, message: "Deprecated!") + var foo: WrappedType + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapAttributesIndentsLineCorrectly() { + let input = """ + class Foo { + @objc var foo = Foo() + } + """ + let output = """ + class Foo { + @objc + var foo = Foo() + } + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testComplexAttributesException() { + let input = """ + @Environment(\\.myEnvironmentVar) var foo: Foo + + @SomeCustomAttr(argument: true) var foo: Foo + + @available(*, deprecated) var foo: Foo + """ + + let output = """ + @Environment(\\.myEnvironmentVar) var foo: Foo + + @SomeCustomAttr(argument: true) var foo: Foo + + @available(*, deprecated) + var foo: Foo + """ + + let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine, complexAttributesExceptions: ["@SomeCustomAttr"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testMixedComplexAndSimpleAttributes() { + let input = """ + /// Simple attributes stay on a single line: + @State private var warpDriveEnabled: Bool + + @ObservedObject private var lifeSupportService: LifeSupportService + + @Environment(\\.controlPanelStyle) private var controlPanelStyle + + @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration + + /// Complex attributes are wrapped: + @AppStorage("ControlPanelState", store: myCustomUserDefaults) private var controlPanelState: ControlPanelState + + @Tweak(name: "Aspect ratio") private var aspectRatio = AspectRatio.stretch + + @available(*, unavailable) var saturn5Builder: Saturn5Builder + + @available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder + """ + + let output = """ + /// Simple attributes stay on a single line: + @State private var warpDriveEnabled: Bool + + @ObservedObject private var lifeSupportService: LifeSupportService + + @Environment(\\.controlPanelStyle) private var controlPanelStyle + + @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration + + /// Complex attributes are wrapped: + @AppStorage("ControlPanelState", store: myCustomUserDefaults) + private var controlPanelState: ControlPanelState + + @Tweak(name: "Aspect ratio") + private var aspectRatio = AspectRatio.stretch + + @available(*, unavailable) + var saturn5Builder: Saturn5Builder + + @available(*, unavailable, message: "No longer in production") + var saturn5Builder: Saturn5Builder + """ + + let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testEscapingClosureNotMistakenForComplexAttribute() { + let input = """ + func foo(_ fooClosure: @escaping () throws -> Void) { + try fooClosure() + } + """ + + let options = FormatOptions(complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testEscapingTypedThrowClosureNotMistakenForComplexAttribute() { + let input = """ + func foo(_ fooClosure: @escaping () throws(Foo) -> Void) { + try fooClosure() + } + """ + + let options = FormatOptions(complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapOrDontWrapMultipleDeclarationsInClass() { + let input = """ + class Foo { + @objc + var foo = Foo() + + @available(*, unavailable) + var bar: Bar + + @available(*, unavailable) + var myComputedFoo: String { + "myComputedFoo" + } + + @Environment(\\.myEnvironmentVar) + var foo + + @State + var myStoredFoo: String = "myStoredFoo" { + didSet { + print(newValue) + } + } + } + """ + let output = """ + class Foo { + @objc var foo = Foo() + + @available(*, unavailable) + var bar: Bar + + @available(*, unavailable) + var myComputedFoo: String { + "myComputedFoo" + } + + @Environment(\\.myEnvironmentVar) var foo + + @State var myStoredFoo: String = "myStoredFoo" { + didSet { + print(newValue) + } + } + } + """ + let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testWrapOrDontAttributesInSwiftUIView() { + let input = """ + struct MyView: View { + @State var textContent: String + + var body: some View { + childView + } + + @ViewBuilder + var childView: some View { + Text(verbatim: textContent) + } + } + """ + + let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapAttributesInSwiftUIView() { + let input = """ + struct MyView: View { + @State var textContent: String + @Environment(\\.myEnvironmentVar) var environmentVar + + var body: some View { + childView + } + + @ViewBuilder var childView: some View { + Text(verbatim: textContent) + } + } + """ + + let options = FormatOptions(varAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testInlineMainActorAttributeNotWrapped() { + let input = """ + var foo: @MainActor (Foo) -> Void + var bar: @MainActor (Bar) -> Void + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } +} diff --git a/Tests/Rules/WrapConditionalBodiesTests.swift b/Tests/Rules/WrapConditionalBodiesTests.swift new file mode 100644 index 00000000..0dfd6a10 --- /dev/null +++ b/Tests/Rules/WrapConditionalBodiesTests.swift @@ -0,0 +1,284 @@ +// +// WrapConditionalBodiesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 11/6/21. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapConditionalBodiesTests: XCTestCase { + func testGuardReturnWraps() { + let input = "guard let foo = bar else { return }" + let output = """ + guard let foo = bar else { + return + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testEmptyGuardReturnWithSpaceDoesNothing() { + let input = "guard let foo = bar else { }" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testEmptyGuardReturnWithoutSpaceDoesNothing() { + let input = "guard let foo = bar else {}" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testGuardReturnWithValueWraps() { + let input = "guard let foo = bar else { return baz }" + let output = """ + guard let foo = bar else { + return baz + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardBodyWithClosingBraceAlreadyOnNewlineWraps() { + let input = """ + guard foo else { return + } + """ + let output = """ + guard foo else { + return + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardContinueWithNoSpacesToCleanupWraps() { + let input = "guard let foo = bar else {continue}" + let output = """ + guard let foo = bar else { + continue + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardReturnWrapsSemicolonDelimitedStatements() { + let input = "guard let foo = bar else { var baz = 0; let boo = 1; fatalError() }" + let output = """ + guard let foo = bar else { + var baz = 0; let boo = 1; fatalError() + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardReturnWrapsSemicolonDelimitedStatementsWithNoSpaces() { + let input = "guard let foo = bar else {var baz=0;let boo=1;fatalError()}" + let output = """ + guard let foo = bar else { + var baz=0;let boo=1;fatalError() + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies, + exclude: [.spaceAroundOperators]) + } + + func testGuardReturnOnNewlineUnchanged() { + let input = """ + guard let foo = bar else { + return + } + """ + testFormatting(for: input, rule: .wrapConditionalBodies) + } + + func testGuardCommentSameLineUnchanged() { + let input = """ + guard let foo = bar else { // Test comment + return + } + """ + testFormatting(for: input, rule: .wrapConditionalBodies) + } + + func testGuardMultilineCommentSameLineUnchanged() { + let input = "guard let foo = bar else { /* Test comment */ return }" + let output = """ + guard let foo = bar else { /* Test comment */ + return + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardTwoMultilineCommentsSameLine() { + let input = "guard let foo = bar else { /* Test comment 1 */ return /* Test comment 2 */ }" + let output = """ + guard let foo = bar else { /* Test comment 1 */ + return /* Test comment 2 */ + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testNestedGuardElseIfStatementsPutOnNewline() { + let input = "guard let foo = bar else { if qux { return quux } else { return quuz } }" + let output = """ + guard let foo = bar else { + if qux { + return quux + } else { + return quuz + } + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testNestedGuardElseGuardStatementPutOnNewline() { + let input = "guard let foo = bar else { guard qux else { return quux } }" + let output = """ + guard let foo = bar else { + guard qux else { + return quux + } + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardWithClosureOnlyWrapsElseBody() { + let input = "guard foo { $0.bar } else { return true }" + let output = """ + guard foo { $0.bar } else { + return true + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testIfElseReturnsWrap() { + let input = "if foo { return bar } else if baz { return qux } else { return quux }" + let output = """ + if foo { + return bar + } else if baz { + return qux + } else { + return quux + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testIfElseBodiesWrap() { + let input = "if foo { bar } else if baz { qux } else { quux }" + let output = """ + if foo { + bar + } else if baz { + qux + } else { + quux + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testIfElsesWithClosuresDontWrapClosures() { + let input = "if foo { $0.bar } { baz } else if qux { $0.quux } { quuz } else { corge }" + let output = """ + if foo { $0.bar } { + baz + } else if qux { $0.quux } { + quuz + } else { + corge + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testEmptyIfElseBodiesWithSpaceDoNothing() { + let input = "if foo { } else if baz { } else { }" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testEmptyIfElseBodiesWithoutSpaceDoNothing() { + let input = "if foo {} else if baz {} else {}" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testGuardElseBraceStartingOnDifferentLine() { + let input = """ + guard foo else + { return bar } + """ + let output = """ + guard foo else + { + return bar + } + """ + + testFormatting(for: input, output, rule: .wrapConditionalBodies, + exclude: [.braces, .indent, .elseOnSameLine]) + } + + func testIfElseBracesStartingOnDifferentLines() { + let input = """ + if foo + { return bar } + else if baz + { return qux } + else + { return quux } + """ + let output = """ + if foo + { + return bar + } + else if baz + { + return qux + } + else + { + return quux + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies, + exclude: [.braces, .indent, .elseOnSameLine]) + } + + func testInsideStringLiteralDoesNothing() { + let input = """ + "\\(list.map { if $0 % 2 == 0 { return 0 } else { return 1 } })" + """ + testFormatting(for: input, rule: .wrapConditionalBodies) + } + + func testInsideMultilineStringLiteral() { + let input = """ + let foo = \""" + \\(list.map { if $0 % 2 == 0 { return 0 } else { return 1 } }) + \""" + """ + let output = """ + let foo = \""" + \\(list.map { if $0 % 2 == 0 { + return 0 + } else { + return 1 + } }) + \""" + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } +} diff --git a/Tests/Rules/WrapEnumCasesTests.swift b/Tests/Rules/WrapEnumCasesTests.swift new file mode 100644 index 00000000..b69b709a --- /dev/null +++ b/Tests/Rules/WrapEnumCasesTests.swift @@ -0,0 +1,229 @@ +// +// WrapEnumCasesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/28/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapEnumCasesTests: XCTestCase { + func testMultilineEnumCases() { + let input = """ + enum Enum1: Int { + case a = 0, p = 2, c, d + case e, k + case m(String, String) + } + """ + let output = """ + enum Enum1: Int { + case a = 0 + case p = 2 + case c + case d + case e + case k + case m(String, String) + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testMultilineEnumCasesWithNestedEnumsDoesNothing() { + let input = """ + public enum SearchTerm: Decodable, Equatable { + case term(name: String) + case category(category: Category) + + enum CodingKeys: String, CodingKey { + case name + case type + case categoryID = "category_id" + case attributes + } + } + """ + testFormatting(for: input, rule: .wrapEnumCases) + } + + func testEnumCaseSplitOverMultipleLines() { + let input = """ + enum Foo { + case bar( + x: String, + y: Int + ), baz + } + """ + let output = """ + enum Foo { + case bar( + x: String, + y: Int + ) + case baz + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testEnumCasesAlreadyWrappedOntoMultipleLines() { + let input = """ + enum Foo { + case bar, + baz, + quux + } + """ + let output = """ + enum Foo { + case bar + case baz + case quux + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testEnumCasesIfValuesWithoutValuesDoesNothing() { + let input = """ + enum Foo { + case bar, baz, quux + } + """ + testFormatting(for: input, rule: .wrapEnumCases, + options: FormatOptions(wrapEnumCases: .withValues)) + } + + func testEnumCasesIfValuesWithRawValuesAndNestedEnum() { + let input = """ + enum Foo { + case bar = 1, baz, quux + + enum Foo2 { + case bar, baz, quux + } + } + """ + let output = """ + enum Foo { + case bar = 1 + case baz + case quux + + enum Foo2 { + case bar, baz, quux + } + } + """ + testFormatting( + for: input, + output, + rule: .wrapEnumCases, + options: FormatOptions(wrapEnumCases: .withValues) + ) + } + + func testEnumCasesIfValuesWithAssociatedValues() { + let input = """ + enum Foo { + case bar(a: Int), baz, quux + } + """ + let output = """ + enum Foo { + case bar(a: Int) + case baz + case quux + } + """ + testFormatting( + for: input, + output, + rule: .wrapEnumCases, + options: FormatOptions(wrapEnumCases: .withValues) + ) + } + + func testEnumCasesWithCommentsAlreadyWrappedOntoMultipleLines() { + let input = """ + enum Foo { + case bar, // bar + baz, // baz + quux // quux + } + """ + let output = """ + enum Foo { + case bar // bar + case baz // baz + case quux // quux + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testNoWrapEnumStatementAllOnOneLine() { + let input = "enum Foo { bar, baz }" + testFormatting(for: input, rule: .wrapEnumCases) + } + + func testNoConfuseIfCaseWithEnum() { + let input = """ + enum Foo { + case foo + case bar(value: [Int]) + } + + func baz() { + if case .foo = foo, + case .bar(let value) = bar, + value.isEmpty + { + print("") + } + } + """ + testFormatting(for: input, rule: .wrapEnumCases, + exclude: [.hoistPatternLet]) + } + + func testNoMangleUnindentedEnumCases() { + let input = """ + enum Foo { + case foo, bar + } + """ + let output = """ + enum Foo { + case foo + case bar + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) + } + + func testNoMangleEnumCaseOnOpeningLine() { + let input = """ + enum SortOrder { case + asc(String), desc(String) + } + """ + // TODO: improve formatting here + let output = """ + enum SortOrder { case + asc(String) + case desc(String) + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) + } + + func testNoWrapSingleLineEnumCases() { + let input = "enum Foo { case foo, bar }" + testFormatting(for: input, rule: .wrapEnumCases) + } +} diff --git a/Tests/Rules/WrapLoopBodiesTests.swift b/Tests/Rules/WrapLoopBodiesTests.swift new file mode 100644 index 00000000..b5d82efb --- /dev/null +++ b/Tests/Rules/WrapLoopBodiesTests.swift @@ -0,0 +1,42 @@ +// +// WrapLoopBodiesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 1/3/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapLoopBodiesTests: XCTestCase { + func testWrapForLoop() { + let input = "for foo in bar { print(foo) }" + let output = """ + for foo in bar { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapLoopBodies) + } + + func testWrapWhileLoop() { + let input = "while let foo = bar.next() { print(foo) }" + let output = """ + while let foo = bar.next() { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapLoopBodies) + } + + func testWrapRepeatWhileLoop() { + let input = "repeat { print(foo) } while condition()" + let output = """ + repeat { + print(foo) + } while condition() + """ + testFormatting(for: input, output, rule: .wrapLoopBodies) + } +} diff --git a/Tests/Rules/WrapMultilineConditionalAssignmentTests.swift b/Tests/Rules/WrapMultilineConditionalAssignmentTests.swift new file mode 100644 index 00000000..7c6887e2 --- /dev/null +++ b/Tests/Rules/WrapMultilineConditionalAssignmentTests.swift @@ -0,0 +1,148 @@ +// +// WrapMultilineConditionalAssignmentTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 11/18/23. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapMultilineConditionalAssignmentTests: XCTestCase { + func testWrapIfExpressionAssignment() { + let input = """ + let foo = if let bar { + bar + } else { + baaz + } + """ + + let output = """ + let foo = + if let bar { + bar + } else { + baaz + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testUnwrapsAssignmentOperatorInIfExpressionAssignment() { + let input = """ + let foo + = if let bar { + bar + } else { + baaz + } + """ + + let output = """ + let foo = + if let bar { + bar + } else { + baaz + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testUnwrapsAssignmentOperatorInIfExpressionFollowingComment() { + let input = """ + let foo + // In order to unwrap the `=` here it has to move it to + // before the comment, rather than simply unwrapping it. + = if let bar { + bar + } else { + baaz + } + """ + + let output = """ + let foo = + // In order to unwrap the `=` here it has to move it to + // before the comment, rather than simply unwrapping it. + if let bar { + bar + } else { + baaz + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testWrapIfAssignmentWithoutIntroducer() { + let input = """ + property = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + + let output = """ + property = + if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testWrapSwitchAssignmentWithoutIntroducer() { + let input = """ + property = switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let output = """ + property = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testWrapSwitchAssignmentWithComplexLValue() { + let input = """ + property?.foo!.bar["baaz"] = switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let output = """ + property?.foo!.bar["baaz"] = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } +} diff --git a/Tests/Rules/WrapMultilineStatementBracesTests.swift b/Tests/Rules/WrapMultilineStatementBracesTests.swift new file mode 100644 index 00000000..e3887018 --- /dev/null +++ b/Tests/Rules/WrapMultilineStatementBracesTests.swift @@ -0,0 +1,727 @@ +// +// WrapMultilineStatementBracesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/16/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapMultilineStatementBracesTests: XCTestCase { + func testMultilineIfBraceOnNextLine() { + let input = """ + if firstConditional, + array.contains(where: { secondConditional }) { + print("statement body") + } + """ + let output = """ + if firstConditional, + array.contains(where: { secondConditional }) + { + print("statement body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineFuncBraceOnNextLine() { + let input = """ + func method( + foo: Int, + bar: Int) { + print("function body") + } + """ + let output = """ + func method( + foo: Int, + bar: Int) + { + print("function body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + exclude: [.wrapArguments, .unusedArguments]) + } + + func testMultilineInitBraceOnNextLine() { + let input = """ + init(foo: Int, + bar: Int) { + print("function body") + } + """ + let output = """ + init(foo: Int, + bar: Int) + { + print("function body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + exclude: [.wrapArguments, .unusedArguments]) + } + + func testMultilineForLoopBraceOnNextLine() { + let input = """ + for foo in + [1, 2] { + print(foo) + } + """ + let output = """ + for foo in + [1, 2] + { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineForLoopBraceOnNextLine2() { + let input = """ + for foo in [ + 1, + 2, + ] { + print(foo) + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineForWhereLoopBraceOnNextLine() { + let input = """ + for foo in bar + where foo != baz { + print(foo) + } + """ + let output = """ + for foo in bar + where foo != baz + { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineGuardBraceOnNextLine() { + let input = """ + guard firstConditional, + array.contains(where: { secondConditional }) else { + print("statement body") + } + """ + let output = """ + guard firstConditional, + array.contains(where: { secondConditional }) else + { + print("statement body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + exclude: [.braces, .elseOnSameLine]) + } + + func testInnerMultilineIfBraceOnNextLine() { + let input = """ + if outerConditional { + if firstConditional, + array.contains(where: { secondConditional }) { + print("statement body") + } + } + """ + let output = """ + if outerConditional { + if firstConditional, + array.contains(where: { secondConditional }) + { + print("statement body") + } + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineIfBraceOnSameLine() { + let input = """ + if let object = Object([ + foo, + bar, + ]) { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: [.propertyType]) + } + + func testSingleLineIfBraceOnSameLine() { + let input = """ + if firstConditional { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testSingleLineGuardBrace() { + let input = """ + guard firstConditional else { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testGuardElseOnOwnLineBraceNotWrapped() { + let input = """ + guard let foo = bar, + bar == baz + else { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineGuardClosingBraceOnSameLine() { + let input = """ + guard let foo = bar, + let baz = quux else { return } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces, + exclude: [.wrapConditionalBodies]) + } + + func testMultilineGuardBraceOnSameLineAsElse() { + let input = """ + guard let foo = bar, + let baz = quux + else { + return + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineClassBrace() { + let input = """ + class Foo: BarProtocol, + BazProtocol + { + init() {} + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineClassBraceNotAppliedForXcodeIndentationMode() { + let input = """ + class Foo: BarProtocol, + BazProtocol { + init() {} + } + """ + let options = FormatOptions(xcodeIndentation: true) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, options: options) + } + + func testMultilineBraceAppliedToTrailingClosure_wrapBeforeFirst() { + let input = """ + UIView.animate( + duration: 10, + options: []) { + print() + } + """ + + let output = """ + UIView.animate( + duration: 10, + options: []) + { + print() + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.indent]) + } + + func testMultilineBraceAppliedToTrailingClosure2_wrapBeforeFirst() { + let input = """ + moveGradient( + to: defaultPosition, + isTouchDown: false, + animated: animated) { + self.isTouchDown = false + } + """ + + let output = """ + moveGradient( + to: defaultPosition, + isTouchDown: false, + animated: animated) + { + self.isTouchDown = false + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .indent, .braces, + ], options: options) + } + + func testMultilineBraceAppliedToGetterBody_wrapBeforeFirst() { + let input = """ + var items = Adaptive.adaptive( + compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) { + didSet { updateAccessoryViewSpacing() } + } + """ + + let output = """ + var items = Adaptive.adaptive( + compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) + { + didSet { updateAccessoryViewSpacing() } + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .indent, + ], options: options, exclude: [.propertyType]) + } + + func testMultilineBraceAppliedToTrailingClosure_wrapAfterFirst() { + let input = """ + UIView.animate(duration: 10, + options: []) { + print() + } + """ + + let output = """ + UIView.animate(duration: 10, + options: []) + { + print() + } + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.indent]) + } + + func testMultilineBraceAppliedToGetterBody_wrapAfterFirst() { + let input = """ + var items = Adaptive.adaptive(compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) + { + didSet { updateAccessoryViewSpacing() } + } + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options, exclude: [.propertyType]) + } + + func testMultilineBraceAppliedToSubscriptBody() { + let input = """ + public subscript( + key: Foo) + -> ServerDrivenLayoutContentPresenter? + { + get { foo[key] } + set { foo[key] = newValue } + } + """ + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.trailingClosures]) + } + + func testWrapsMultilineStatementConsistently() { + let input = """ + func aFunc( + one _: Int, + two _: Int) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int) + -> String + { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithEffects() { + let input = """ + func aFunc( + one _: Int, + two _: Int) async throws -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int) + async throws -> String + { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithArrayReturnType() { + let input = """ + public func aFunc( + one _: Int, + two _: Int) -> [String] { + ["one"] + } + """ + + let output = """ + public func aFunc( + one _: Int, + two _: Int) + -> [String] + { + ["one"] + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithComplexGenericReturnType() { + let input = """ + public func aFunc( + one _: Int, + two _: Int) throws -> some Collection { + ["one"] + } + """ + + let output = """ + public func aFunc( + one _: Int, + two _: Int) + throws -> some Collection + { + ["one"] + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithTuple() { + let input = """ + public func aFunc( + one: Int, + two: Int) -> (one: String, two: String) { + (one: String(one), two: String(two)) + } + """ + + let output = """ + public func aFunc( + one: Int, + two: Int) + -> (one: String, two: String) + { + (one: String(one), two: String(two)) + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently2() { + let input = """ + func aFunc( + one _: Int, + two _: Int) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int + ) -> String { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .balanced + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently2_withEffects() { + let input = """ + func aFunc( + one _: Int, + two _: Int) async throws -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int + ) async throws -> String { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .balanced, + wrapEffects: .never + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently2_withTypedEffects() { + let input = """ + func aFunc( + one _: Int, + two _: Int) async throws(Foo) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int + ) async throws(Foo) -> String { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .balanced, + wrapEffects: .never + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently3() { + let input = """ + func aFunc( + one _: Int, + two _: Int + ) -> String { + "one" + } + """ + + let options = FormatOptions( + // wrapMultilineStatementBraces: true, + wrapArguments: .beforeFirst, + closingParenPosition: .balanced + ) + + testFormatting(for: input, [], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently4() { + let input = """ + func aFunc( + one _: Int, + two _: Int + ) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int) -> String + { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapMultilineStatementConsistently5() { + let input = """ + foo( + one: 1, + two: 2).bar({ _ in + "one" + }) + """ + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.trailingClosures]) + } + + func testOpenBraceAfterEqualsInGuardNotWrapped() { + let input = """ + guard + let foo = foo, + let bar: String = { + nil + }() + else { return } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rules: [.wrapMultilineStatementBraces, .wrap], + options: options, exclude: [.indent, .redundantClosure, .wrapConditionalBodies]) + } + + func testWrapMultilineStatementBraceAfterWhereClauseWithTuple() { + let input = """ + extension Foo { + public func testWithWhereClause( + a: A, + b: B) + -> Outcome where + Outcome == (A, B) + { + return (a, b) + } + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rules: [.wrapMultilineStatementBraces, .braces], options: options) + } +} diff --git a/Tests/Rules/WrapSingleLineCommentsTests.swift b/Tests/Rules/WrapSingleLineCommentsTests.swift new file mode 100644 index 00000000..990dfa61 --- /dev/null +++ b/Tests/Rules/WrapSingleLineCommentsTests.swift @@ -0,0 +1,160 @@ +// +// WrapSingleLineCommentsTests.swift +// SwiftFormatTests +// +// Created by Max Desiatov on 8/11/22. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapSingleLineCommentsTests: XCTestCase { + func testWrapSingleLineComment() { + let input = """ + // a b cde fgh + """ + let output = """ + // a b + // cde + // fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 6)) + } + + func testWrapSingleLineCommentThatOverflowsByOneCharacter() { + let input = """ + // a b cde fg h + """ + let output = """ + // a b cde fg + // h + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 14)) + } + + func testNoWrapSingleLineCommentThatExactlyFits() { + let input = """ + // a b cde fg h + """ + + testFormatting(for: input, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 15)) + } + + func testWrapSingleLineCommentWithNoLeadingSpace() { + let input = """ + //a b cde fgh + """ + let output = """ + //a b + //cde + //fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 6), + exclude: [.spaceInsideComments]) + } + + func testWrapDocComment() { + let input = """ + /// a b cde fgh + """ + let output = """ + /// a b + /// cde + /// fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 7), exclude: [.docComments]) + } + + func testWrapDocLineCommentWithNoLeadingSpace() { + let input = """ + ///a b cde fgh + """ + let output = """ + ///a b + ///cde + ///fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 6), + exclude: [.spaceInsideComments, .docComments]) + } + + func testWrapSingleLineCommentWithIndent() { + let input = """ + func f() { + // a b cde fgh + let x = 1 + } + """ + let output = """ + func f() { + // a b cde + // fgh + let x = 1 + } + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 14), exclude: [.docComments]) + } + + func testWrapSingleLineCommentAfterCode() { + let input = """ + func f() { + foo.bar() // this comment is much much much too long + } + """ + let output = """ + func f() { + foo.bar() // this comment + // is much much much too + // long + } + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 29), exclude: [.wrap]) + } + + func testWrapDocCommentWithLongURL() { + let input = """ + /// See [Link](https://www.domain.com/pathextension/pathextension/pathextension/pathextension/pathextension/pathextension). + """ + + testFormatting(for: input, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 100), exclude: [.docComments]) + } + + func testWrapDocCommentWithLongURL2() { + let input = """ + /// Link to SDK documentation - https://docs.adyen.com/checkout/3d-secure/native-3ds2/api-integration#collect-the-3d-secure-2-device-fingerprint-from-an-ios-app + """ + + testFormatting(for: input, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 80)) + } + + func testWrapDocCommentWithMultipleLongURLs() { + let input = """ + /// Link to http://a-very-long-url-that-wont-fit-on-one-line, http://another-very-long-url-that-wont-fit-on-one-line + """ + let output = """ + /// Link to http://a-very-long-url-that-wont-fit-on-one-line, + /// http://another-very-long-url-that-wont-fit-on-one-line + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 40), exclude: [.docComments]) + } +} diff --git a/Tests/Rules/WrapSwitchCasesTests.swift b/Tests/Rules/WrapSwitchCasesTests.swift new file mode 100644 index 00000000..989f3925 --- /dev/null +++ b/Tests/Rules/WrapSwitchCasesTests.swift @@ -0,0 +1,53 @@ +// +// WrapSwitchCasesTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 8/28/20. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapSwitchCasesTests: XCTestCase { + func testMultilineSwitchCases() { + let input = """ + func foo() { + switch bar { + case .a(_), .b, "c": + print("") + case .d: + print("") + } + } + """ + let output = """ + func foo() { + switch bar { + case .a(_), + .b, + "c": + print("") + case .d: + print("") + } + } + """ + testFormatting(for: input, output, rule: .wrapSwitchCases) + } + + func testIfAfterSwitchCaseNotWrapped() { + let input = """ + switch foo { + case "foo": + print("") + default: + print("") + } + if let foo = bar, foo != .baz { + throw error + } + """ + testFormatting(for: input, rule: .wrapSwitchCases) + } +} diff --git a/Tests/Rules/WrapTests.swift b/Tests/Rules/WrapTests.swift new file mode 100644 index 00000000..d190126d --- /dev/null +++ b/Tests/Rules/WrapTests.swift @@ -0,0 +1,725 @@ +// +// WrapTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapTests: XCTestCase { + func testWrapIfStatement() { + let input = """ + if let foo = foo, let bar = bar, let baz = baz {} + """ + let output = """ + if let foo = foo, + let bar = bar, + let baz = baz {} + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapIfElseStatement() { + let input = """ + if let foo = foo {} else if let bar = bar {} + """ + let output = """ + if let foo = foo {} + else if let bar = + bar {} + """ + let output2 = """ + if let foo = foo {} + else if let bar = + bar {} + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapGuardStatement() { + let input = """ + guard let foo = foo, let bar = bar else { + break + } + """ + let output = """ + guard let foo = foo, + let bar = bar + else { + break + } + """ + let output2 = """ + guard let foo = foo, + let bar = bar + else { + break + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapClosure() { + let input = """ + let foo = { () -> Bool in true } + """ + let output = """ + let foo = + { () -> Bool in + true } + """ + let output2 = """ + let foo = + { () -> Bool in + true + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapClosure2() { + let input = """ + let foo = { bar, _ in bar } + """ + let output = """ + let foo = + { bar, _ in + bar } + """ + let output2 = """ + let foo = + { bar, _ in + bar + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapClosureWithAllmanBraces() { + let input = """ + let foo = { bar, _ in bar } + """ + let output = """ + let foo = + { bar, _ in + bar } + """ + let output2 = """ + let foo = + { bar, _ in + bar + } + """ + let options = FormatOptions(allmanBraces: true, maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapClosure3() { + let input = "let foo = bar { $0.baz }" + let output = """ + let foo = bar { + $0.baz } + """ + let output2 = """ + let foo = bar { + $0.baz + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth() { + let input = """ + func testFunc() -> ReturnType { + doSomething() + doSomething() + } + """ + let output = """ + func testFunc() + -> ReturnType { + doSomething() + doSomething() + } + """ + let options = FormatOptions(maxWidth: 25) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() { + let input = """ + func testFunc() -> ReturnType { + doSomething() + doSomething() + } + """ + let output = """ + func testFunc() + -> ReturnType { + doSomething() + doSomething() + } + """ + let output2 = """ + func testFunc() + -> ReturnType { + doSomething() + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 25) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth2() { + let input = """ + func testFunc() -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output = """ + func testFunc() + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 35) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() { + let input = """ + func testFunc() throws -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output = """ + func testFunc() throws + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output2 = """ + func testFunc() throws + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() { + let input = """ + func testFunc() throws(Foo) -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output = """ + func testFunc() throws(Foo) + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output2 = """ + func testFunc() throws(Foo) + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth3() { + let input = """ + func testFunc() -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc() + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 35) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() { + let input = """ + func testFunc() -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc() + -> (Bool, String) -> String? { + doSomething() + } + """ + let output2 = """ + func testFunc() + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth4() { + let input = """ + func testFunc(_: () -> Void) -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 35) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() { + let input = """ + func testFunc(_: () -> Void) -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) + -> (Bool, String) -> String? { + doSomething() + } + """ + let output2 = """ + func testFunc(_: () -> Void) + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapChainedFunctionAfterSubscriptCollection() { + let input = """ + let foo = bar["baz"].quuz() + """ + let output = """ + let foo = bar["baz"] + .quuz() + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapChainedFunctionInSubscriptCollection() { + let input = """ + let foo = bar[baz.quuz()] + """ + let output = """ + let foo = + bar[baz.quuz()] + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapThrowingFunctionIfReturnTypeExceedsMaxWidth() { + let input = """ + func testFunc(_: () -> Void) throws -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) throws + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 42) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() { + let input = """ + func testFunc(_: () -> Void) throws(Foo) -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) throws(Foo) + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 42) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testNoWrapInterpolatedStringLiteral() { + let input = """ + "a very long \\(string) literal" + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapAtUnspacedOperator() { + let input = "let foo = bar+baz+quux" + let output = "let foo =\n bar+baz+quux" + let options = FormatOptions(maxWidth: 15) + testFormatting(for: input, output, rule: .wrap, options: options, + exclude: [.spaceAroundOperators]) + } + + func testNoWrapAtUnspacedEquals() { + let input = "let foo=bar+baz+quux" + let options = FormatOptions(maxWidth: 15) + testFormatting(for: input, rule: .wrap, options: options, + exclude: [.spaceAroundOperators]) + } + + func testNoWrapSingleParameter() { + let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" + let output = """ + let fooBar = try unkeyedContainer + .decode(FooBar.self) + """ + let options = FormatOptions(maxWidth: 50) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapSingleParameter() { + let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" + let output = """ + let fooBar = try unkeyedContainer.decode( + FooBar.self + ) + """ + let options = FormatOptions(maxWidth: 50, noWrapOperators: [".", "="]) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapFunctionArrow() { + let input = "func foo() -> Int {}" + let output = """ + func foo() + -> Int {} + """ + let options = FormatOptions(maxWidth: 14) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoWrapFunctionArrow() { + let input = "func foo() -> Int {}" + let output = """ + func foo( + ) -> Int {} + """ + let options = FormatOptions(maxWidth: 14, noWrapOperators: ["->"]) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoCrashWrap() { + let input = """ + struct Foo { + func bar(a: Set, c: D) {} + } + """ + let output = """ + struct Foo { + func bar( + a: Set< + B + >, + c: D + ) {} + } + """ + let options = FormatOptions(maxWidth: 10) + testFormatting(for: input, output, rule: .wrap, options: options, + exclude: [.unusedArguments]) + } + + func testNoCrashWrap2() { + let input = """ + struct Test { + func webView(_: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + authenticationChallengeProcessor.process(challenge: challenge, completionHandler: completionHandler) + } + } + """ + let output = """ + struct Test { + func webView( + _: WKWebView, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, + URLCredential?) -> Void + ) { + authenticationChallengeProcessor.process( + challenge: challenge, + completionHandler: completionHandler + ) + } + } + """ + let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80) + testFormatting(for: input, output, rule: .wrap, options: options, + exclude: [.indent, .wrapArguments]) + } + + func testWrapColorLiteral() throws { + let input = """ + button.setTitleColor(#colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1), for: .normal) + """ + let options = FormatOptions(maxWidth: 80, assetLiteralWidth: .visualWidth) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testWrapImageLiteral() { + let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" + let options = FormatOptions(maxWidth: 40, assetLiteralWidth: .visualWidth) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapBeforeFirstArgumentInSingleLineStringInterpolation() { + let input = """ + "a very long string literal with \\(interpolation) inside" + """ + let options = FormatOptions(maxWidth: 40) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testWrapBeforeFirstArgumentInMultineStringInterpolation() { + let input = """ + \""" + a very long string literal with \\(interpolation) inside + \""" + """ + let output = """ + \""" + a very long string literal with \\( + interpolation + ) inside + \""" + """ + let options = FormatOptions(maxWidth: 40) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + // ternary expressions + + func testWrapSimpleTernaryOperator() { + let input = """ + let foo = fooCondition ? longValueThatContainsFoo : longValueThatContainsBar + """ + + let output = """ + let foo = fooCondition + ? longValueThatContainsFoo + : longValueThatContainsBar + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testRewrapsSimpleTernaryOperator() { + let input = """ + let foo = fooCondition ? longValueThatContainsFoo : + longValueThatContainsBar + """ + + let output = """ + let foo = fooCondition + ? longValueThatContainsFoo + : longValueThatContainsBar + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapComplexTernaryOperator() { + let input = """ + let foo = fooCondition ? Foo(property: value) : barContainer.getBar(using: barProvider) + """ + + let output = """ + let foo = fooCondition + ? Foo(property: value) + : barContainer.getBar(using: barProvider) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testRewrapsComplexTernaryOperator() { + let input = """ + let foo = fooCondition ? Foo(property: value) : + barContainer.getBar(using: barProvider) + """ + + let output = """ + let foo = fooCondition + ? Foo(property: value) + : barContainer.getBar(using: barProvider) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapsSimpleNestedTernaryOperator() { + let input = """ + let foo = fooCondition ? (barCondition ? a : b) : (baazCondition ? c : d) + """ + + let output = """ + let foo = fooCondition + ? (barCondition ? a : b) + : (baazCondition ? c : d) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapsDoubleNestedTernaryOperation() { + let input = """ + let foo = fooCondition ? barCondition ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult + """ + + let output = """ + let foo = fooCondition + ? barCondition + ? longTrueBarResult + : longFalseBarResult + : baazCondition + ? longTrueBaazResult + : longFalseBaazResult + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapsTripleNestedTernaryOperation() { + let input = """ + let foo = fooCondition ? barCondition ? quuxCondition ? longTrueQuuxResult : longFalseQuuxResult : barCondition2 ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult + """ + + let output = """ + let foo = fooCondition + ? barCondition + ? quuxCondition + ? longTrueQuuxResult + : longFalseQuuxResult + : barCondition2 + ? longTrueBarResult + : longFalseBarResult + : baazCondition + ? longTrueBaazResult + : longFalseBaazResult + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoWrapTernaryWrappedWithinChildExpression() { + let input = """ + func foo() { + return _skipString(string) ? .token( + string, Location(source: input, range: startIndex ..< index) + ) : nil + } + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapTernaryWrappedWithinChildExpression2() { + let input = """ + let types: [PolygonType] = plane.isEqual(to: plane) ? [] : vertices.map { + let t = plane.normal.dot($0.position) - plane.w + let type: PolygonType = (t < -epsilon) ? .back : (t > epsilon) ? .front : .coplanar + polygonType = PolygonType(rawValue: polygonType.rawValue | type.rawValue)! + return type + } + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapTernaryInsideStringLiteral() { + let input = """ + "\\(true ? "Some string literal" : "Some other string")" + """ + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testWrapTernaryInsideMultilineStringLiteral() { + let input = """ + let foo = \""" + \\(true ? "Some string literal" : "Some other string")" + \""" + """ + let output = """ + let foo = \""" + \\(true + ? "Some string literal" + : "Some other string")" + \""" + """ + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoCrashWrap3() throws { + let input = """ + override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { + let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext + context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size + return context + } + """ + let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 100) + let rules: [FormatRule] = [.wrap, .wrapArguments] + XCTAssertNoThrow(try format(input, rules: rules, options: options)) + } + + func testErrorNotReportedOnBlankLineAfterWrap() throws { + let input = """ + [ + abagdiasiudbaisndoanosdasdasdasdasdnaosnooanso(), + + bar(), + ] + """ + let options = FormatOptions(truncateBlankLines: false, maxWidth: 40) + let changes = try lint(input, rules: [.wrap, .indent], options: options) + XCTAssertEqual(changes, [.init(line: 2, rule: .wrap, filePath: nil)]) + } +} diff --git a/Tests/Rules/YodaConditionsTests.swift b/Tests/Rules/YodaConditionsTests.swift new file mode 100644 index 00000000..b8e7ad9d --- /dev/null +++ b/Tests/Rules/YodaConditionsTests.swift @@ -0,0 +1,293 @@ +// +// YodaConditionsTests.swift +// SwiftFormatTests +// +// Created by Nick Lockwood on 3/9/19. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class YodaConditionsTests: XCTestCase { + func testNumericLiteralEqualYodaCondition() { + let input = "5 == foo" + let output = "foo == 5" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNumericLiteralGreaterYodaCondition() { + let input = "5.1 > foo" + let output = "foo < 5.1" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testStringLiteralNotEqualYodaCondition() { + let input = "\"foo\" != foo" + let output = "foo != \"foo\"" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNilNotEqualYodaCondition() { + let input = "nil != foo" + let output = "foo != nil" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testTrueNotEqualYodaCondition() { + let input = "true != foo" + let output = "foo != true" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testEnumCaseNotEqualYodaCondition() { + let input = ".foo != foo" + let output = "foo != .foo" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testArrayLiteralNotEqualYodaCondition() { + let input = "[5, 6] != foo" + let output = "foo != [5, 6]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNestedArrayLiteralNotEqualYodaCondition() { + let input = "[5, [6, 7]] != foo" + let output = "foo != [5, [6, 7]]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testDictionaryLiteralNotEqualYodaCondition() { + let input = "[foo: 5, bar: 6] != foo" + let output = "foo != [foo: 5, bar: 6]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testSubscriptNotTreatedAsYodaCondition() { + let input = "foo[5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfParenthesizedExpressionNotTreatedAsYodaCondition() { + let input = "(foo + bar)[5] != baz" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfUnwrappedValueNotTreatedAsYodaCondition() { + let input = "foo![5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { + let input = "foo /* foo */ [5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfCollectionNotTreatedAsYodaCondition() { + let input = "[foo][5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfTrailingClosureNotTreatedAsYodaCondition() { + let input = "foo { [5] }[0] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfRhsNotMangledInYodaCondition() { + let input = "[1] == foo[0]" + let output = "foo[0] == [1]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testTupleYodaCondition() { + let input = "(5, 6) != bar" + let output = "bar != (5, 6)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testLabeledTupleYodaCondition() { + let input = "(foo: 5, bar: 6) != baz" + let output = "baz != (foo: 5, bar: 6)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNestedTupleYodaCondition() { + let input = "(5, (6, 7)) != baz" + let output = "baz != (5, (6, 7))" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testFunctionCallNotTreatedAsYodaCondition() { + let input = "foo(5) != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfParenthesizedExpressionNotTreatedAsYodaCondition() { + let input = "(foo + bar)(5) != baz" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfUnwrappedValueNotTreatedAsYodaCondition() { + let input = "foo!(5) != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { + let input = "foo /* foo */ (5) != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfRhsNotMangledInYodaCondition() { + let input = "(1, 2) == foo(0)" + let output = "foo(0) == (1, 2)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testTrailingClosureOnRhsNotMangledInYodaCondition() { + let input = "(1, 2) == foo { $0 }" + let output = "foo { $0 } == (1, 2)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInIfStatement() { + let input = "if 5 != foo {}" + let output = "if foo != 5 {}" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testSubscriptYodaConditionInIfStatementWithBraceOnNextLine() { + let input = "if [0] == foo.bar[0]\n{ baz() }" + let output = "if foo.bar[0] == [0]\n{ baz() }" + testFormatting(for: input, output, rule: .yodaConditions, + exclude: [.wrapConditionalBodies]) + } + + func testYodaConditionInSecondClauseOfIfStatement() { + let input = "if foo, 5 != bar {}" + let output = "if foo, bar != 5 {}" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInExpression() { + let input = "let foo = 5 < bar\nbaz()" + let output = "let foo = bar > 5\nbaz()" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInExpressionWithTrailingClosure() { + let input = "let foo = 5 < bar { baz() }" + let output = "let foo = bar { baz() } > 5" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInFunctionCall() { + let input = "foo(5 < bar)" + let output = "foo(bar > 5)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionFollowedByExpression() { + let input = "5 == foo + 6" + let output = "foo + 6 == 5" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPrefixExpressionYodaCondition() { + let input = "!false == foo" + let output = "foo == !false" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPrefixExpressionYodaCondition2() { + let input = "true == !foo" + let output = "!foo == true" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPostfixExpressionYodaCondition() { + let input = "5<*> == foo" + let output = "foo == 5<*>" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testDoublePostfixExpressionYodaCondition() { + let input = "5!! == foo" + let output = "foo == 5!!" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPostfixExpressionNonYodaCondition() { + let input = "5 == 5<*>" + testFormatting(for: input, rule: .yodaConditions) + } + + func testPostfixExpressionNonYodaCondition2() { + let input = "5<*> == 5" + testFormatting(for: input, rule: .yodaConditions) + } + + func testStringEqualsStringNonYodaCondition() { + let input = "\"foo\" == \"bar\"" + testFormatting(for: input, rule: .yodaConditions) + } + + func testConstantAfterNullCoalescingNonYodaCondition() { + let input = "foo.last ?? -1 < bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testNoMangleYodaConditionFollowedByAndOperator() { + let input = "5 <= foo && foo <= 7" + let output = "foo >= 5 && foo <= 7" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionFollowedByOrOperator() { + let input = "5 <= foo || foo <= 7" + let output = "foo >= 5 || foo <= 7" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionFollowedByParentheses() { + let input = "0 <= (foo + bar)" + let output = "(foo + bar) >= 0" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionInTernary() { + let input = "let z = 0 < y ? 3 : 4" + let output = "let z = y > 0 ? 3 : 4" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionInTernary2() { + let input = "let z = y > 0 ? 0 < x : 4" + let output = "let z = y > 0 ? x > 0 : 4" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionInTernary3() { + let input = "let z = y > 0 ? 3 : 0 < x" + let output = "let z = y > 0 ? 3 : x > 0" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testKeyPathNotMangledAndNotTreatedAsYodaCondition() { + let input = "\\.foo == bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testEnumCaseLessThanEnumCase() { + let input = "XCTAssertFalse(.never < .never)" + testFormatting(for: input, rule: .yodaConditions) + } + + // yodaSwap = literalsOnly + + func testNoSwapYodaDotMember() { + let input = "foo(where: .bar == baz)" + let options = FormatOptions(yodaSwap: .literalsOnly) + testFormatting(for: input, rule: .yodaConditions, options: options) + } +} diff --git a/Tests/RulesTests+General.swift b/Tests/RulesTests+General.swift deleted file mode 100644 index 5162c734..00000000 --- a/Tests/RulesTests+General.swift +++ /dev/null @@ -1,1366 +0,0 @@ -// -// RulesTests+General.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 02/10/2021. -// Copyright © 2021 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -private enum TestDateFormat: String { - case basic = "yyyy-MM-dd" - case time = "HH:mmZZZZZ" - case timestamp = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" -} - -private func createTestDate( - _ input: String, - _ format: TestDateFormat = .basic -) -> Date { - let formatter = DateFormatter() - formatter.dateFormat = format.rawValue - formatter.timeZone = .current - - return formatter.date(from: input)! -} - -class GeneralTests: RulesTests { - // MARK: - initCoderUnavailable - - func testInitCoderUnavailableEmptyFunction() { - let input = """ - struct A: UIView { - required init?(coder aDecoder: NSCoder) {} - } - """ - let output = """ - struct A: UIView { - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) {} - } - """ - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, - exclude: ["unusedArguments"]) - } - - func testInitCoderUnavailableFatalErrorNilDisabled() { - let input = """ - extension Module { - final class A: UIView { - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - } - """ - let output = """ - extension Module { - final class A: UIView { - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - } - """ - let options = FormatOptions(initCoderNil: false) - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, options: options) - } - - func testInitCoderUnavailableFatalErrorNilEnabled() { - let input = """ - extension Module { - final class A: UIView { - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - } - """ - let output = """ - extension Module { - final class A: UIView { - @available(*, unavailable) - required init?(coder _: NSCoder) { - nil - } - } - } - """ - let options = FormatOptions(initCoderNil: true) - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, options: options) - } - - func testInitCoderUnavailableAlreadyPresent() { - let input = """ - extension Module { - final class A: UIView { - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - } - } - """ - testFormatting(for: input, rule: FormatRules.initCoderUnavailable) - } - - func testInitCoderUnavailableImplemented() { - let input = """ - extension Module { - final class A: UIView { - required init?(coder aCoder: NSCoder) { - aCoder.doSomething() - } - } - } - """ - testFormatting(for: input, rule: FormatRules.initCoderUnavailable) - } - - func testPublicInitCoderUnavailable() { - let input = """ - class Foo: UIView { - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - """ - let output = """ - class Foo: UIView { - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - """ - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable) - } - - func testPublicInitCoderUnavailable2() { - let input = """ - class Foo: UIView { - required public init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - """ - let output = """ - class Foo: UIView { - @available(*, unavailable) - required public init?(coder _: NSCoder) { - nil - } - } - """ - let options = FormatOptions(initCoderNil: true) - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, - options: options, exclude: ["modifierOrder"]) - } - - // MARK: - trailingCommas - - func testCommaAddedToSingleItem() { - let input = "[\n foo\n]" - let output = "[\n foo,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) - } - - func testCommaAddedToLastItem() { - let input = "[\n foo,\n bar\n]" - let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) - } - - func testCommaAddedToDictionary() { - let input = "[\n foo: bar\n]" - let output = "[\n foo: bar,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) - } - - func testCommaNotAddedToInlineArray() { - let input = "[foo, bar]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testCommaNotAddedToInlineDictionary() { - let input = "[foo: bar]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testCommaNotAddedToSubscript() { - let input = "foo[bar]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testCommaAddedBeforeComment() { - let input = "[\n foo // comment\n]" - let output = "[\n foo, // comment\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) - } - - func testCommaNotAddedAfterComment() { - let input = "[\n foo, // comment\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testCommaNotAddedInsideEmptyArrayLiteral() { - let input = "foo = [\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testCommaNotAddedInsideEmptyDictionaryLiteral() { - let input = "foo = [:\n]" - let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, rule: FormatRules.trailingCommas, options: options) - } - - func testTrailingCommaRemovedInInlineArray() { - let input = "[foo,]" - let output = "[foo]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToSubscript() { - let input = "foo[\n bar\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToSubscript2() { - let input = "foo?[\n bar\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToSubscript3() { - let input = "foo()[\n bar\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToSubscriptInsideArrayLiteral() { - let input = """ - let array = [ - foo - .bar[ - 0 - ] - .baz, - ] - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaAddedToArrayLiteralInsideTuple() { - let input = """ - let arrays = ([ - foo - ], [ - bar - ]) - """ - let output = """ - let arrays = ([ - foo, - ], [ - bar, - ]) - """ - testFormatting(for: input, output, rule: FormatRules.trailingCommas) - } - - func testNoTrailingCommaAddedToArrayLiteralInsideTuple() { - let input = """ - let arrays = ([ - Int - ], [ - Int - ]).self - """ - testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) - } - - func testTrailingCommaNotAddedToTypeDeclaration() { - let input = """ - var foo: [ - Int: - String - ] - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration2() { - let input = """ - func foo(bar: [ - Int: - String - ]) - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration3() { - let input = """ - func foo() -> [ - String: String - ] - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration4() { - let input = """ - func foo() -> [String: [ - String: Int - ]] - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration5() { - let input = """ - let foo = [String: [ - String: Int - ]]() - """ - testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) - } - - func testTrailingCommaNotAddedToTypeDeclaration6() { - let input = """ - let foo = [String: [ - (Foo<[ - String - ]>, [ - Int - ]) - ]]() - """ - testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) - } - - func testTrailingCommaNotAddedToTypeDeclaration7() { - let input = """ - func foo() -> Foo<[String: [ - String: Int - ]]> - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration8() { - let input = """ - extension Foo { - var bar: [ - Int - ] { - fatalError() - } - } - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToTypealias() { - let input = """ - typealias Foo = [ - Int - ] - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToCaptureList() { - let input = """ - let foo = { [ - self - ] in } - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToCaptureListWithComment() { - let input = """ - let foo = { [ - self // captures self - ] in } - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToCaptureListWithMainActor() { - let input = """ - let closure = { @MainActor [ - foo = state.foo, - baz = state.baz - ] _ in } - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - func testTrailingCommaNotAddedToArrayExtension() { - let input = """ - extension [ - Int - ] { - func foo() {} - } - """ - testFormatting(for: input, rule: FormatRules.trailingCommas) - } - - // trailingCommas = false - - func testCommaNotAddedToLastItem() { - let input = "[\n foo,\n bar\n]" - let options = FormatOptions(trailingCommas: false) - testFormatting(for: input, rule: FormatRules.trailingCommas, options: options) - } - - func testCommaRemovedFromLastItem() { - let input = "[\n foo,\n bar,\n]" - let output = "[\n foo,\n bar\n]" - let options = FormatOptions(trailingCommas: false) - testFormatting(for: input, output, rule: FormatRules.trailingCommas, options: options) - } - - // MARK: - fileHeader - - func testStripHeader() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testStripHeaderWithWhenHeaderContainsUrl() { - let input = """ - // - // RulesTests+General.swift - // SwiftFormatTests - // - // Created by Nick Lockwood on 02/10/2021. - // Copyright © 2021 Nick Lockwood. All rights reserved. - // https://some.example.com - // - - /// func - func foo() {} - """ - let output = "/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testReplaceHeaderWhenFileContainsNoCode() { - let input = "// foobar" - let options = FormatOptions(fileHeader: "// foobar") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options, - exclude: ["linebreakAtEndOfFile"]) - } - - func testReplaceHeaderWhenFileContainsNoCode2() { - let input = "// foobar\n" - let options = FormatOptions(fileHeader: "// foobar") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testMultilineCommentHeader() { - let input = "/****************************/\n/* Created by Nick Lockwood */\n/****************************/\n\n\n/// func\nfunc foo() {}" - let output = "/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripHeaderWhenDisabled() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: .ignore) - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripComment() { - let input = "\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripPackageHeader() { - let input = "// swift-tools-version:4.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripFormatDirective() { - let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripFormatDirectiveAfterHeader() { - let input = "// header\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoReplaceFormatDirective() { - let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let output = "// Hello World\n\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "// Hello World") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testSetSingleLineHeader() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "// Hello World\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "// Hello World") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testSetMultilineHeader() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "// Hello\n// World\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "// Hello\n// World") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testSetMultilineHeaderWithMarkup() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "/*--- Hello ---*/\n/*--- World ---*/\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "/*--- Hello ---*/\n/*--- World ---*/") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripHeaderIfRuleDisabled() { - let input = "// swiftformat:disable fileHeader\n// test\n// swiftformat:enable fileHeader\n\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripHeaderIfNextRuleDisabled() { - let input = "// swiftformat:disable:next fileHeader\n// test\n\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testNoStripHeaderDocWithNewlineBeforeCode() { - let input = "/// Header doc\n\nclass Foo {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options, exclude: ["docComments"]) - } - - func testNoDuplicateHeaderIfMissingTrailingBlankLine() { - let input = "// Header comment\nclass Foo {}" - let output = "// Header comment\n\nclass Foo {}" - let options = FormatOptions(fileHeader: "Header comment") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testNoDuplicateHeaderContainingPossibleCommentDirective() { - let input = """ - // Copyright (c) 2010-2023 Foobar - // - // SPDX-License-Identifier: EPL-2.0 - - class Foo {} - """ - let output = """ - // Copyright (c) 2010-2024 Foobar - // - // SPDX-License-Identifier: EPL-2.0 - - class Foo {} - """ - let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// SPDX-License-Identifier: EPL-2.0") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testNoDuplicateHeaderContainingCommentDirective() { - let input = """ - // Copyright (c) 2010-2023 Foobar - // - // swiftformat:disable all - - class Foo {} - """ - let output = """ - // Copyright (c) 2010-2024 Foobar - // - // swiftformat:disable all - - class Foo {} - """ - let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// swiftformat:disable all") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderYearReplacement() { - let input = "let foo = bar" - let output: String = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy" - return "// Copyright © \(formatter.string(from: Date()))\n\nlet foo = bar" - }() - let options = FormatOptions(fileHeader: "// Copyright © {year}") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderCreationYearReplacement() { - let input = "let foo = bar" - let date = Date(timeIntervalSince1970: 0) - let output: String = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy" - return "// Copyright © \(formatter.string(from: date))\n\nlet foo = bar" - }() - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// Copyright © {created.year}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderAuthorReplacement() { - let name = "Test User" - let email = "test@email.com" - let input = "let foo = bar" - let output = "// Created by \(name) \(email)\n\nlet foo = bar" - let fileInfo = FileInfo(replacements: [.authorName: .constant(name), .authorEmail: .constant(email)]) - let options = FormatOptions(fileHeader: "// Created by {author.name} {author.email}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderAuthorReplacement2() { - let author = "Test User " - let input = "let foo = bar" - let output = "// Created by \(author)\n\nlet foo = bar" - let fileInfo = FileInfo(replacements: [.author: .constant(author)]) - let options = FormatOptions(fileHeader: "// Created by {author}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderMultipleReplacement() { - let name = "Test User" - let input = "let foo = bar" - let output = "// Copyright © \(name)\n// Created by \(name)\n\nlet foo = bar" - let fileInfo = FileInfo(replacements: [.authorName: .constant(name)]) - let options = FormatOptions(fileHeader: "// Copyright © {author.name}\n// Created by {author.name}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderCreationDateReplacement() { - let input = "let foo = bar" - let date = Date(timeIntervalSince1970: 0) - let output: String = { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .none - return "// Created by Nick Lockwood on \(formatter.string(from: date)).\n\nlet foo = bar" - }() - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderDateFormattingIso() { - let date = createTestDate("2023-08-09") - - let input = "let foo = bar" - let output = "// 2023-08-09\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", dateFormat: .iso, fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderDateFormattingDayMonthYear() { - let date = createTestDate("2023-08-09") - - let input = "let foo = bar" - let output = "// 09/08/2023\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", dateFormat: .dayMonthYear, fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderDateFormattingMonthDayYear() { - let date = createTestDate("2023-08-09") - - let input = "let foo = bar" - let output = "// 08/09/2023\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", - dateFormat: .monthDayYear, - fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderDateFormattingCustom() { - let date = createTestDate("2023-08-09T12:59:30.345Z", .timestamp) - - let input = "let foo = bar" - let output = "// 23.08.09-12.59.30.345\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", - dateFormat: .custom("yy.MM.dd-HH.mm.ss.SSS"), - timeZone: .identifier("UTC"), - fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - private func testTimeZone( - timeZone: FormatTimeZone, - tests: [String: String] - ) { - for (input, expected) in tests { - let date = createTestDate(input, .time) - let input = "let foo = bar" - let output = "// \(expected)\n\nlet foo = bar" - - let fileInfo = FileInfo(creationDate: date) - - let options = FormatOptions( - fileHeader: "// {created}", - dateFormat: .custom("HH:mm"), - timeZone: timeZone, - fileInfo: fileInfo - ) - - testFormatting(for: input, output, - rule: FormatRules.fileHeader, - options: options) - } - } - - func testFileHeaderDateTimeZoneSystem() { - let baseDate = createTestDate("15:00Z", .time) - let offset = TimeZone.current.secondsFromGMT(for: baseDate) - - let date = baseDate.addingTimeInterval(Double(offset)) - - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm" - formatter.timeZone = TimeZone(secondsFromGMT: 0) - - let expected = formatter.string(from: date) - - testTimeZone(timeZone: .system, tests: [ - "15:00Z": expected, - "16:00+1": expected, - "01:00+10": expected, - "16:30+0130": expected, - ]) - } - - func testFileHeaderDateTimeZoneAbbreviations() { - // GMT+0530 - testTimeZone(timeZone: FormatTimeZone(rawValue: "IST")!, tests: [ - "15:00Z": "20:30", - "16:00+1": "20:30", - "01:00+10": "20:30", - "16:30+0130": "20:30", - ]) - } - - func testFileHeaderDateTimeZoneIdentifiers() { - // GMT+0845 - testTimeZone(timeZone: FormatTimeZone(rawValue: "Australia/Eucla")!, tests: [ - "15:00Z": "23:45", - "16:00+1": "23:45", - "01:00+10": "23:45", - "16:30+0130": "23:45", - ]) - } - - func testGitHelpersReturnsInfo() { - let info = GitFileInfo(url: URL(fileURLWithPath: #file)) - XCTAssertNotNil(info?.authorName) - XCTAssertNotNil(info?.authorEmail) - XCTAssertNotNil(info?.creationDate) - } - - func testFileHeaderRuleThrowsIfCreationDateUnavailable() { - let input = "let foo = bar" - let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: FileInfo()) - XCTAssertThrowsError(try format(input, rules: [FormatRules.fileHeader], options: options)) - } - - func testFileHeaderFileReplacement() { - let input = "let foo = bar" - let output = "// MyFile.swift\n\nlet foo = bar" - let fileInfo = FileInfo(filePath: "~/MyFile.swift") - let options = FormatOptions(fileHeader: "// {file}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderRuleThrowsIfFileNameUnavailable() { - let input = "let foo = bar" - let options = FormatOptions(fileHeader: "// {file}.", fileInfo: FileInfo()) - XCTAssertThrowsError(try format(input, rules: [FormatRules.fileHeader], options: options)) - } - - func testEdgeCaseHeaderEndIndexPlusNewHeaderTokensCountEqualsFileTokensEndIndex() { - let input = "// Header comment\n\nclass Foo {}" - let output = "// Header line1\n// Header line2\n\nclass Foo {}" - let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderBlankLineNotRemovedBeforeFollowingComment() { - let input = """ - // - // Header - // - - // Something else... - """ - let options = FormatOptions(fileHeader: "//\n// Header\n//") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderBlankLineNotRemovedBeforeFollowingComment2() { - let input = """ - // - // Header - // - - // - // Something else... - // - """ - let options = FormatOptions(fileHeader: "//\n// Header\n//") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderRemovedAfterHashbang() { - let input = """ - #!/usr/bin/swift - - // Header line1 - // Header line2 - - let foo = 5 - """ - let output = """ - #!/usr/bin/swift - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testFileHeaderPlacedAfterHashbang() { - let input = """ - #!/usr/bin/swift - - let foo = 5 - """ - let output = """ - #!/usr/bin/swift - - // Header line1 - // Header line2 - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) - } - - func testBlankLineAfterHashbangNotRemovedByFileHeader() { - let input = """ - #!/usr/bin/swift - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testLineAfterHashbangNotAffectedByFileHeaderRemoval() { - let input = """ - #!/usr/bin/swift - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testDisableFileHeaderCommentRespectedAfterHashbang() { - let input = """ - #!/usr/bin/swift - // swiftformat:disable fileHeader - - // Header line1 - // Header line2 - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - func testDisableFileHeaderCommentRespectedAfterHashbang2() { - let input = """ - #!/usr/bin/swift - - // swiftformat:disable fileHeader - // Header line1 - // Header line2 - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) - } - - // MARK: - headerFileName - - func testHeaderFileNameReplaced() { - let input = """ - // MyFile.swift - - let foo = bar - """ - let output = """ - // YourFile.swift - - let foo = bar - """ - let options = FormatOptions(fileInfo: FileInfo(filePath: "~/YourFile.swift")) - testFormatting(for: input, output, rule: FormatRules.headerFileName, options: options) - } - - // MARK: - strongOutlets - - func testRemoveWeakFromOutlet() { - let input = "@IBOutlet weak var label: UILabel!" - let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) - } - - func testRemoveWeakFromPrivateOutlet() { - let input = "@IBOutlet private weak var label: UILabel!" - let output = "@IBOutlet private var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) - } - - func testRemoveWeakFromOutletOnSplitLine() { - let input = "@IBOutlet\nweak var label: UILabel!" - let output = "@IBOutlet\nvar label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) - } - - func testNoRemoveWeakFromNonOutlet() { - let input = "weak var label: UILabel!" - testFormatting(for: input, rule: FormatRules.strongOutlets) - } - - func testNoRemoveWeakFromNonOutletAfterOutlet() { - let input = "@IBOutlet weak var label1: UILabel!\nweak var label2: UILabel!" - let output = "@IBOutlet var label1: UILabel!\nweak var label2: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) - } - - func testNoRemoveWeakFromDelegateOutlet() { - let input = "@IBOutlet weak var delegate: UITableViewDelegate?" - testFormatting(for: input, rule: FormatRules.strongOutlets) - } - - func testNoRemoveWeakFromDataSourceOutlet() { - let input = "@IBOutlet weak var dataSource: UITableViewDataSource?" - testFormatting(for: input, rule: FormatRules.strongOutlets) - } - - func testRemoveWeakFromOutletAfterDelegateOutlet() { - let input = "@IBOutlet weak var delegate: UITableViewDelegate?\n@IBOutlet weak var label1: UILabel!" - let output = "@IBOutlet weak var delegate: UITableViewDelegate?\n@IBOutlet var label1: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) - } - - func testRemoveWeakFromOutletAfterDataSourceOutlet() { - let input = "@IBOutlet weak var dataSource: UITableViewDataSource?\n@IBOutlet weak var label1: UILabel!" - let output = "@IBOutlet weak var dataSource: UITableViewDataSource?\n@IBOutlet var label1: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) - } - - // MARK: - strongifiedSelf - - func testBacktickedSelfConvertedToSelfInGuard() { - let input = """ - { [weak self] in - guard let `self` = self else { return } - } - """ - let output = """ - { [weak self] in - guard let self = self else { return } - } - """ - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, output, rule: FormatRules.strongifiedSelf, options: options, - exclude: ["wrapConditionalBodies"]) - } - - func testBacktickedSelfConvertedToSelfInIf() { - let input = """ - { [weak self] in - if let `self` = self else { print(self) } - } - """ - let output = """ - { [weak self] in - if let self = self else { print(self) } - } - """ - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, output, rule: FormatRules.strongifiedSelf, options: options, - exclude: ["wrapConditionalBodies"]) - } - - func testBacktickedSelfNotConvertedIfVersionLessThan4_2() { - let input = """ - { [weak self] in - guard let `self` = self else { return } - } - """ - let options = FormatOptions(swiftVersion: "4.1.5") - testFormatting(for: input, rule: FormatRules.strongifiedSelf, options: options, - exclude: ["wrapConditionalBodies"]) - } - - func testBacktickedSelfNotConvertedIfVersionUnspecified() { - let input = """ - { [weak self] in - guard let `self` = self else { return } - } - """ - testFormatting(for: input, rule: FormatRules.strongifiedSelf, - exclude: ["wrapConditionalBodies"]) - } - - func testBacktickedSelfNotConvertedIfNotConditional() { - let input = "nonisolated(unsafe) let `self` = self" - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, rule: FormatRules.strongifiedSelf, options: options) - } - - // MARK: - yodaConditions - - func testNumericLiteralEqualYodaCondition() { - let input = "5 == foo" - let output = "foo == 5" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNumericLiteralGreaterYodaCondition() { - let input = "5.1 > foo" - let output = "foo < 5.1" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testStringLiteralNotEqualYodaCondition() { - let input = "\"foo\" != foo" - let output = "foo != \"foo\"" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNilNotEqualYodaCondition() { - let input = "nil != foo" - let output = "foo != nil" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testTrueNotEqualYodaCondition() { - let input = "true != foo" - let output = "foo != true" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testEnumCaseNotEqualYodaCondition() { - let input = ".foo != foo" - let output = "foo != .foo" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testArrayLiteralNotEqualYodaCondition() { - let input = "[5, 6] != foo" - let output = "foo != [5, 6]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNestedArrayLiteralNotEqualYodaCondition() { - let input = "[5, [6, 7]] != foo" - let output = "foo != [5, [6, 7]]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testDictionaryLiteralNotEqualYodaCondition() { - let input = "[foo: 5, bar: 6] != foo" - let output = "foo != [foo: 5, bar: 6]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testSubscriptNotTreatedAsYodaCondition() { - let input = "foo[5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testSubscriptOfParenthesizedExpressionNotTreatedAsYodaCondition() { - let input = "(foo + bar)[5] != baz" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testSubscriptOfUnwrappedValueNotTreatedAsYodaCondition() { - let input = "foo![5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testSubscriptOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { - let input = "foo /* foo */ [5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testSubscriptOfCollectionNotTreatedAsYodaCondition() { - let input = "[foo][5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testSubscriptOfTrailingClosureNotTreatedAsYodaCondition() { - let input = "foo { [5] }[0] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testSubscriptOfRhsNotMangledInYodaCondition() { - let input = "[1] == foo[0]" - let output = "foo[0] == [1]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testTupleYodaCondition() { - let input = "(5, 6) != bar" - let output = "bar != (5, 6)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testLabeledTupleYodaCondition() { - let input = "(foo: 5, bar: 6) != baz" - let output = "baz != (foo: 5, bar: 6)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNestedTupleYodaCondition() { - let input = "(5, (6, 7)) != baz" - let output = "baz != (5, (6, 7))" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testFunctionCallNotTreatedAsYodaCondition() { - let input = "foo(5) != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testCallOfParenthesizedExpressionNotTreatedAsYodaCondition() { - let input = "(foo + bar)(5) != baz" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testCallOfUnwrappedValueNotTreatedAsYodaCondition() { - let input = "foo!(5) != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testCallOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { - let input = "foo /* foo */ (5) != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testCallOfRhsNotMangledInYodaCondition() { - let input = "(1, 2) == foo(0)" - let output = "foo(0) == (1, 2)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testTrailingClosureOnRhsNotMangledInYodaCondition() { - let input = "(1, 2) == foo { $0 }" - let output = "foo { $0 } == (1, 2)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testYodaConditionInIfStatement() { - let input = "if 5 != foo {}" - let output = "if foo != 5 {}" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testSubscriptYodaConditionInIfStatementWithBraceOnNextLine() { - let input = "if [0] == foo.bar[0]\n{ baz() }" - let output = "if foo.bar[0] == [0]\n{ baz() }" - testFormatting(for: input, output, rule: FormatRules.yodaConditions, - exclude: ["wrapConditionalBodies"]) - } - - func testYodaConditionInSecondClauseOfIfStatement() { - let input = "if foo, 5 != bar {}" - let output = "if foo, bar != 5 {}" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testYodaConditionInExpression() { - let input = "let foo = 5 < bar\nbaz()" - let output = "let foo = bar > 5\nbaz()" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testYodaConditionInExpressionWithTrailingClosure() { - let input = "let foo = 5 < bar { baz() }" - let output = "let foo = bar { baz() } > 5" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testYodaConditionInFunctionCall() { - let input = "foo(5 < bar)" - let output = "foo(bar > 5)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testYodaConditionFollowedByExpression() { - let input = "5 == foo + 6" - let output = "foo + 6 == 5" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testPrefixExpressionYodaCondition() { - let input = "!false == foo" - let output = "foo == !false" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testPrefixExpressionYodaCondition2() { - let input = "true == !foo" - let output = "!foo == true" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testPostfixExpressionYodaCondition() { - let input = "5<*> == foo" - let output = "foo == 5<*>" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testDoublePostfixExpressionYodaCondition() { - let input = "5!! == foo" - let output = "foo == 5!!" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testPostfixExpressionNonYodaCondition() { - let input = "5 == 5<*>" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testPostfixExpressionNonYodaCondition2() { - let input = "5<*> == 5" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testStringEqualsStringNonYodaCondition() { - let input = "\"foo\" == \"bar\"" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testConstantAfterNullCoalescingNonYodaCondition() { - let input = "foo.last ?? -1 < bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testNoMangleYodaConditionFollowedByAndOperator() { - let input = "5 <= foo && foo <= 7" - let output = "foo >= 5 && foo <= 7" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNoMangleYodaConditionFollowedByOrOperator() { - let input = "5 <= foo || foo <= 7" - let output = "foo >= 5 || foo <= 7" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNoMangleYodaConditionFollowedByParentheses() { - let input = "0 <= (foo + bar)" - let output = "(foo + bar) >= 0" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNoMangleYodaConditionInTernary() { - let input = "let z = 0 < y ? 3 : 4" - let output = "let z = y > 0 ? 3 : 4" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNoMangleYodaConditionInTernary2() { - let input = "let z = y > 0 ? 0 < x : 4" - let output = "let z = y > 0 ? x > 0 : 4" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testNoMangleYodaConditionInTernary3() { - let input = "let z = y > 0 ? 3 : 0 < x" - let output = "let z = y > 0 ? 3 : x > 0" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) - } - - func testKeyPathNotMangledAndNotTreatedAsYodaCondition() { - let input = "\\.foo == bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - func testEnumCaseLessThanEnumCase() { - let input = "XCTAssertFalse(.never < .never)" - testFormatting(for: input, rule: FormatRules.yodaConditions) - } - - // yodaSwap = literalsOnly - - func testNoSwapYodaDotMember() { - let input = "foo(where: .bar == baz)" - let options = FormatOptions(yodaSwap: .literalsOnly) - testFormatting(for: input, rule: FormatRules.yodaConditions, options: options) - } - - // MARK: - leadingDelimiters - - func testLeadingCommaMovedToPreviousLine() { - let input = """ - let foo = 5 - , bar = 6 - """ - let output = """ - let foo = 5, - bar = 6 - """ - testFormatting(for: input, output, rule: FormatRules.leadingDelimiters) - } - - func testLeadingColonFollowedByCommentMovedToPreviousLine() { - let input = """ - let foo - : /* string */ String - """ - let output = """ - let foo: - /* string */ String - """ - testFormatting(for: input, output, rule: FormatRules.leadingDelimiters) - } - - func testCommaMovedBeforeCommentIfLineEndsInComment() { - let input = """ - let foo = 5 // first - , bar = 6 - """ - let output = """ - let foo = 5, // first - bar = 6 - """ - testFormatting(for: input, output, rule: FormatRules.leadingDelimiters) - } -} diff --git a/Tests/RulesTests+Hoisting.swift b/Tests/RulesTests+Hoisting.swift deleted file mode 100644 index 18ba6827..00000000 --- a/Tests/RulesTests+Hoisting.swift +++ /dev/null @@ -1,1079 +0,0 @@ -// -// RulesTests+Hoisting.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 25/03/2023. -// Copyright © 2023 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class HoistingTests: RulesTests { - // MARK: - hoistTry - - func testHoistTry() { - let input = "greet(try name(), try surname())" - let output = "try greet(name(), surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryWithOptionalTry() { - let input = "greet(try name(), try? surname())" - let output = "try greet(name(), try? surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideStringInterpolation() { - let input = "\"\\(replace(regex: try something()))\"" - let output = "try \"\\(replace(regex: something()))\"" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideStringInterpolation2() { - let input = """ - "Hello \\(try await someValue())" - """ - let output = """ - try "Hello \\(await someValue())" - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry, - options: FormatOptions(swiftVersion: "5.5"), - exclude: ["hoistAwait"]) - } - - func testHoistTryInsideStringInterpolation3() { - let input = """ - let text = "\"" - abc - \\(try bar()) - xyz - "\"" - """ - let output = """ - let text = try "\"" - abc - \\(bar()) - xyz - "\"" - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideStringInterpolation4() { - let input = """ - let str = "&enrolments[\\(index)][userid]=\\(try Foo.tryMe())" - """ - let output = """ - let str = try "&enrolments[\\(index)][userid]=\\(Foo.tryMe())" - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideStringInterpolation5() { - let input = """ - return str + - "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + - "&enrolments[\\(index)][userid]=\\(try user.requireMoodleID())" - """ - let output = """ - return try str + - "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + - "&enrolments[\\(index)][userid]=\\(user.requireMoodleID())" - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideStringInterpolation6() { - let input = #""" - """ - let \(object.varName) = - \(tripleQuote) - \(try encode(object.object)) - \(tripleQuote) - """ - """# - let output = #""" - try """ - let \(object.varName) = - \(tripleQuote) - \(encode(object.object)) - \(tripleQuote) - """ - """# - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideArgument() { - let input = """ - array.append(contentsOf: try await asyncFunction(param1: param1)) - """ - let output = """ - try array.append(contentsOf: await asyncFunction(param1: param1)) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry, exclude: ["hoistAwait"]) - } - - func testNoHoistTryInsideXCTAssert() { - let input = "XCTAssertFalse(try foo())" - testFormatting(for: input, rule: FormatRules.hoistTry) - } - - func testNoMergeTrysInsideXCTAssert() { - let input = "XCTAssertEqual(try foo(), try bar())" - testFormatting(for: input, rule: FormatRules.hoistTry) - } - - func testNoHoistTryInsideDo() { - let input = "do { rg.box.seal(.fulfilled(try body(error))) }" - let output = "do { try rg.box.seal(.fulfilled(body(error))) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testNoHoistTryInsideDoThrows() { - let input = "do throws(Foo) { rg.box.seal(.fulfilled(try body(error))) }" - let output = "do throws(Foo) { try rg.box.seal(.fulfilled(body(error))) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testNoHoistTryInsideMultilineDo() { - let input = """ - do { - rg.box.seal(.fulfilled(try body(error))) - } - """ - let output = """ - do { - try rg.box.seal(.fulfilled(body(error))) - } - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistedTryPlacedBeforeAwait() { - let input = "let foo = await bar(contentsOf: try baz())" - let output = "let foo = try await bar(contentsOf: baz())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInExpressionWithNoSpaces() { - let input = "let foo=bar(contentsOf:try baz())" - let output = "let foo=try bar(contentsOf:baz())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, - exclude: ["spaceAroundOperators"]) - } - - func testHoistTryInExpressionWithExcessSpaces() { - let input = "let foo = bar ( contentsOf: try baz() )" - let output = "let foo = try bar ( contentsOf: baz() )" - testFormatting(for: input, output, rule: FormatRules.hoistTry, - exclude: ["spaceAroundParens", "spaceInsideParens"]) - } - - func testHoistTryWithReturn() { - let input = "return .enumCase(try await service.greet())" - let output = "return try .enumCase(await service.greet())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, - exclude: ["hoistAwait"]) - } - - func testHoistDeeplyNestedTrys() { - let input = "let foo = (bar: (5, (try quux(), 6)), baz: (7, quux: try quux()))" - let output = "let foo = try (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testTryNotHoistedOutOfClosure() { - let input = "let foo = { (try bar(), 5) }" - let output = "let foo = { try (bar(), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testTryNotHoistedOutOfClosureWithArguments() { - let input = "let foo = { bar in (try baz(bar), 5) }" - let output = "let foo = { bar in try (baz(bar), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testTryNotHoistedOutOfForCondition() { - let input = "for foo in bar(try baz()) {}" - let output = "for foo in try bar(baz()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryWithInitAssignment() { - let input = "let variable = String(try await asyncFunction())" - let output = "let variable = try String(await asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, - exclude: ["hoistAwait"]) - } - - func testHoistTryWithAssignment() { - let input = "let variable = (try await asyncFunction())" - let output = "let variable = try (await asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, - exclude: ["hoistAwait"]) - } - - func testHoistTryOnlyOne() { - let input = "greet(name, try surname())" - let output = "try greet(name, surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryRedundantTry() { - let input = "try greet(try name(), try surname())" - let output = "try greet(name(), surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryWithAwaitOnDifferentStatement() { - let input = """ - let asyncVariable = try await performSomething() - return Foo(param1: try param1()) - """ - let output = """ - let asyncVariable = try await performSomething() - return try Foo(param1: param1()) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryDoubleParens() { - let input = """ - array.append((value: try compute())) - """ - let output = """ - try array.append((value: compute())) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryDoesNothing() { - let input = "try greet(name, surname)" - testFormatting(for: input, rule: FormatRules.hoistTry) - } - - func testHoistOptionalTryDoesNothing() { - let input = "try? greet(name, surname)" - testFormatting(for: input, rule: FormatRules.hoistTry) - } - - func testHoistedTryOnLineBeginningWithInfixDot() { - let input = """ - let foo = bar() - .baz(try quux()) - """ - let output = """ - let foo = try bar() - .baz(quux()) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistedTryOnLineBeginningWithInfixPlus() { - let input = """ - let foo = bar() - + baz(try quux()) - """ - let output = """ - let foo = try bar() - + baz(quux()) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistedTryOnLineBeginningWithPrefixOperator() { - let input = """ - foo() - !bar(try quux()) - """ - let output = """ - foo() - try !bar(quux()) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testNoHoistTryIntoPreviousLineEndingWithPostfixOperator() { - let input = """ - let foo = bar! - (try baz(), quux()).foo() - """ - let output = """ - let foo = bar! - try (baz(), quux()).foo() - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testNoHoistTryInCapturingFunction() { - let input = "foo(try bar)" - testFormatting(for: input, rule: FormatRules.hoistTry, - options: FormatOptions(throwCapturing: ["foo"])) - } - - func testNoHoistSecondArgumentTryInCapturingFunction() { - let input = "foo(bar, try baz)" - testFormatting(for: input, rule: FormatRules.hoistTry, - options: FormatOptions(throwCapturing: ["foo"])) - } - - func testNoHoistFailToTerminate() { - let input = """ - return ManyInitExample( - a: try Example(string: try throwingExample()), - b: try throwingExample(), - c: try throwingExample(), - d: try throwingExample(), - e: try throwingExample(), - f: try throwingExample(), - g: try throwingExample(), - h: try throwingExample(), - i: try throwingExample() - ) - """ - let output = """ - return try ManyInitExample( - a: Example(string: throwingExample()), - b: throwingExample(), - c: throwingExample(), - d: throwingExample(), - e: throwingExample(), - f: throwingExample(), - g: throwingExample(), - h: throwingExample(), - i: throwingExample() - ) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideOptionalFunction() { - let input = "foo?(try bar())" - let output = "try foo?(bar())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testNoHoistTryAfterOptionalTry() { - let input = "let foo = try? bar(try baz())" - testFormatting(for: input, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideOptionalSubscript() { - let input = "foo?[try bar()]" - let output = "try foo?[bar()]" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryAfterGenericType() { - let input = "let foo = Tree.Foo(bar: try baz())" - let output = "let foo = try Tree.Foo(bar: baz())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryAfterArrayLiteral() { - let input = "if [.first, .second].contains(try foo()) {}" - let output = "if try [.first, .second].contains(foo()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryAfterSubscript() { - let input = "if foo[5].bar(try baz()) {}" - let output = "if try foo[5].bar(baz()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideGenericInit() { - let input = """ - return Target( - file: try parseFile(path: $0) - ) - """ - let output = """ - return try Target( - file: parseFile(path: $0) - ) - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryInsideArrayClosure() { - let input = "foo[bar](try parseFile(path: $0))" - let output = "try foo[bar](parseFile(path: $0))" - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryAfterString() { - let input = """ - let json = "{}" - - someFunction(try parse(json), "someKey") - """ - let output = """ - let json = "{}" - - try someFunction(parse(json), "someKey") - """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - func testHoistTryAfterMultilineString() { - let input = #""" - let json = """ - { - "foo": "bar" - } - """ - - someFunction(try parse(json), "someKey") - """# - let output = #""" - let json = """ - { - "foo": "bar" - } - """ - - try someFunction(parse(json), "someKey") - """# - testFormatting(for: input, output, rule: FormatRules.hoistTry) - } - - // MARK: - hoistAwait - - func testHoistAwait() { - let input = "greet(await name, await surname)" - let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitInsideIf() { - let input = "if !(await isSomething()) {}" - let output = "if await !(isSomething()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), - exclude: ["redundantParens"]) - } - - func testHoistAwaitInsideArgument() { - let input = """ - array.append(contentsOf: try await asyncFunction(param1: param1)) - """ - let output = """ - await array.append(contentsOf: try asyncFunction(param1: param1)) - """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) - } - - func testHoistAwaitInsideStringInterpolation() { - let input = "\"\\(replace(regex: await something()))\"" - let output = "await \"\\(replace(regex: something()))\"" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitInsideStringInterpolation2() { - let input = """ - "Hello \\(try await someValue())" - """ - let output = """ - await "Hello \\(try someValue())" - """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) - } - - func testNoHoistAwaitInsideDo() { - let input = """ - do { - rg.box.seal(.fulfilled(await body(error))) - } - """ - let output = """ - do { - await rg.box.seal(.fulfilled(body(error))) - } - """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoHoistAwaitInsideDoThrows() { - let input = """ - do throws(Foo) { - rg.box.seal(.fulfilled(await body(error))) - } - """ - let output = """ - do throws(Foo) { - await rg.box.seal(.fulfilled(body(error))) - } - """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitInExpressionWithNoSpaces() { - let input = "let foo=bar(contentsOf:await baz())" - let output = "let foo=await bar(contentsOf:baz())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundOperators"]) - } - - func testHoistAwaitInExpressionWithExcessSpaces() { - let input = "let foo = bar ( contentsOf: await baz() )" - let output = "let foo = await bar ( contentsOf: baz() )" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), - exclude: ["spaceAroundParens", "spaceInsideParens"]) - } - - func testHoistAwaitWithReturn() { - let input = "return .enumCase(try await service.greet())" - let output = "return await .enumCase(try service.greet())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) - } - - func testHoistDeeplyNestedAwaits() { - let input = "let foo = (bar: (5, (await quux(), 6)), baz: (7, quux: await quux()))" - let output = "let foo = await (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfClosure() { - let input = "let foo = { (await bar(), 5) }" - let output = "let foo = { await (bar(), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfClosureWithArguments() { - let input = "let foo = { bar in (await baz(bar), 5) }" - let output = "let foo = { bar in await (baz(bar), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfForCondition() { - let input = "for foo in bar(await baz()) {}" - let output = "for foo in await bar(baz()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfForIndex() { - let input = "for await foo in asyncSequence() {}" - testFormatting(for: input, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitWithInitAssignment() { - let input = "let variable = String(try await asyncFunction())" - let output = "let variable = await String(try asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) - } - - func testHoistAwaitWithAssignment() { - let input = "let variable = (try await asyncFunction())" - let output = "let variable = await (try asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) - } - - func testHoistAwaitInRedundantScopePriorToNumber() { - let input = """ - let identifiersTypes = 1 - (try? await asyncFunction(param1: param1)) - """ - let output = """ - let identifiersTypes = 1 - await (try? asyncFunction(param1: param1)) - """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitOnlyOne() { - let input = "greet(name, await surname)" - let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitRedundantAwait() { - let input = "await greet(await name, await surname)" - let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitDoesNothing() { - let input = "await greet(name, surname)" - testFormatting(for: input, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoHoistAwaitBeforeTry() { - let input = "try foo(await bar())" - let output = "try await foo(bar())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoHoistAwaitInCapturingFunction() { - let input = "foo(await bar)" - testFormatting(for: input, rule: FormatRules.hoistAwait, - options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) - } - - func testNoHoistSecondArgumentAwaitInCapturingFunction() { - let input = "foo(bar, await baz)" - testFormatting(for: input, rule: FormatRules.hoistAwait, - options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) - } - - func testHoistAwaitAfterOrdinaryOperator() { - let input = "let foo = bar + (await baz)" - let output = "let foo = await bar + (baz)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) - } - - func testHoistAwaitAfterUnknownOperator() { - let input = "let foo = bar ??? (await baz)" - let output = "let foo = await bar ??? (baz)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) - } - - func testNoHoistAwaitAfterCapturingOperator() { - let input = "let foo = await bar ??? (await baz)" - testFormatting(for: input, rule: FormatRules.hoistAwait, - options: FormatOptions(asyncCapturing: ["???"], swiftVersion: "5.5")) - } - - func testNoHoistAwaitInMacroArgument() { - let input = "#expect (await monitor.isAvailable == false)" - testFormatting(for: input, rule: FormatRules.hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundParens"]) - } - - // MARK: - hoistPatternLet - - // hoist = true - - func testHoistCaseLet() { - let input = "if case .foo(let bar, let baz) = quux {}" - let output = "if case let .foo(bar, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistLabelledCaseLet() { - let input = "if case .foo(bar: let bar, baz: let baz) = quux {}" - let output = "if case let .foo(bar: bar, baz: baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistCaseVar() { - let input = "if case .foo(var bar, var baz) = quux {}" - let output = "if case var .foo(bar, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testNoHoistMixedCaseLetVar() { - let input = "if case .foo(let bar, var baz) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) - } - - func testNoHoistIfFirstArgSpecified() { - let input = "if case .foo(bar, let baz) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) - } - - func testNoHoistIfLastArgSpecified() { - let input = "if case .foo(let bar, baz) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) - } - - func testHoistIfArgIsNumericLiteral() { - let input = "if case .foo(5, let baz) = quux {}" - let output = "if case let .foo(5, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistIfArgIsEnumCaseLiteral() { - let input = "if case .foo(.bar, let baz) = quux {}" - let output = "if case let .foo(.bar, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistIfArgIsNamespacedEnumCaseLiteralInParens() { - let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" - let output = "switch foo {\ncase let (Foo.bar(baz)):\n}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, exclude: ["redundantParens"]) - } - - func testHoistIfFirstArgIsUnderscore() { - let input = "if case .foo(_, let baz) = quux {}" - let output = "if case let .foo(_, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistIfSecondArgIsUnderscore() { - let input = "if case .foo(let baz, _) = quux {}" - let output = "if case let .foo(baz, _) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testNestedHoistLet() { - let input = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" - let output = "if case let (.foo(a, b), .bar(c, d)) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistCommaSeparatedSwitchCaseLets() { - let input = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" - let output = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) - } - - func testHoistNewlineSeparatedSwitchCaseLets() { - let input = """ - switch foo { - case .foo(let bar), - .bar(let bar): - } - """ - - let output = """ - switch foo { - case let .foo(bar), - let .bar(bar): - } - """ - - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) - } - - func testHoistCatchLet() { - let input = "do {} catch Foo.foo(bar: let bar) {}" - let output = "do {} catch let Foo.foo(bar: bar) {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testNoNestedHoistLetWithSpecifiedArgs() { - let input = "if case (.foo(let a, b), .bar(let c, d)) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) - } - - func testNoHoistClosureVariables() { - let input = "foo({ let bar = 5 })" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, exclude: ["trailingClosures"]) - } - - // TODO: this should actually hoist the let, but that's tricky to implement without - // breaking the `testNoOverHoistSwitchCaseWithNestedParens` case - func testHoistSwitchCaseWithNestedParens() { - let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), Foo.bar): break\n}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, - exclude: ["blankLineAfterImports"]) - } - - // TODO: this could actually hoist the let by one level, but that's tricky to implement - func testNoOverHoistSwitchCaseWithNestedParens() { - let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), bar): break\n}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, - exclude: ["blankLineAfterImports"]) - } - - func testNoHoistLetWithEmptArg() { - let input = "if .foo(let _) = bar {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, - exclude: ["redundantLet", "redundantPattern"]) - } - - func testHoistLetWithNoSpaceAfterCase() { - let input = "switch x { case.some(let y): return y }" - let output = "switch x { case let .some(y): return y }" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testHoistWrappedGuardCaseLet() { - let input = """ - guard case Foo - .bar(let baz) - else { - return - } - """ - let output = """ - guard case let Foo - .bar(baz) - else { - return - } - """ - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) - } - - func testNoHoistCaseLetContainingGenerics() { - // Hoisting in this case causes a compilation error as-of Swift 5.3 - // See: https://github.com/nicklockwood/SwiftFormat/issues/768 - let input = "if case .some(Optional.some(let foo)) = bar else {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, exclude: ["typeSugar"]) - } - - // hoist = false - - func testUnhoistCaseLet() { - let input = "if case let .foo(bar, baz) = quux {}" - let output = "if case .foo(let bar, let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistCaseLetDictionaryTuple() { - let input = """ - switch (a, b) { - case let (c as [String: Any], d as [String: Any]): - break - } - """ - let output = """ - switch (a, b) { - case (let c as [String: Any], let d as [String: Any]): - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistLabelledCaseLet() { - let input = "if case let .foo(bar: bar, baz: baz) = quux {}" - let output = "if case .foo(bar: let bar, baz: let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistCaseVar() { - let input = "if case var .foo(bar, baz) = quux {}" - let output = "if case .foo(var bar, var baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNoUnhoistGuardCaseLetFollowedByFunction() { - let input = """ - guard case let foo as Foo = bar else { return } - foo.bar(foo: bar) - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options, - exclude: ["wrapConditionalBodies"]) - } - - func testNoUnhoistSwitchCaseLetFollowedByWhere() { - let input = """ - switch foo { - case let bar? where bar >= baz(quux): - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNoUnhoistSwitchCaseLetFollowedByAs() { - let input = """ - switch foo { - case let bar as (String, String): - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistSingleCaseLet() { - let input = "if case let .foo(bar) = quux {}" - let output = "if case .foo(let bar) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistIfArgIsEnumCaseLiteral() { - let input = "if case let .foo(.bar, baz) = quux {}" - let output = "if case .foo(.bar, let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistIfArgIsEnumCaseLiteralInParens() { - let input = "switch foo {\ncase let (.bar(baz)):\n}" - let output = "switch foo {\ncase (.bar(let baz)):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, - exclude: ["redundantParens"]) - } - - func testUnhoistIfArgIsNamespacedEnumCaseLiteral() { - let input = "switch foo {\ncase let Foo.bar(baz):\n}" - let output = "switch foo {\ncase Foo.bar(let baz):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { - let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" - let output = "switch foo {\ncase (Foo.bar(let baz)):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, - exclude: ["redundantParens"]) - } - - func testUnhoistIfArgIsUnderscore() { - let input = "if case let .foo(_, baz) = quux {}" - let output = "if case .foo(_, let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNestedUnhoistLet() { - let input = "if case let (.foo(a, b), .bar(c, d)) = quux {}" - let output = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistCommaSeparatedSwitchCaseLets() { - let input = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" - let output = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) - } - - func testUnhoistCommaSeparatedSwitchCaseLets2() { - let input = "switch foo {\ncase let Foo.foo(bar), let Foo.bar(bar):\n}" - let output = "switch foo {\ncase Foo.foo(let bar), Foo.bar(let bar):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) - } - - func testUnhoistCatchLet() { - let input = "do {} catch let Foo.foo(bar: bar) {}" - let output = "do {} catch Foo.foo(bar: let bar) {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNoUnhoistTupleLet() { - let input = "let (bar, baz) = quux()" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNoUnhoistIfLetTuple() { - let input = "if let x = y, let (_, a) = z {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNoUnhoistIfCaseFollowedByLetTuple() { - let input = "if case .foo = bar, let (foo, bar) = baz {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) - } - - func testNoUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { - let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options, - exclude: ["redundantParens"]) - } - - func testNoDeleteCommentWhenUnhoistingWrappedLet() { - let input = """ - switch foo { - case /* next */ let .bar(bar): - } - """ - - let output = """ - switch foo { - case /* next */ .bar(let bar): - } - """ - - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, - options: options, exclude: ["wrapSwitchCases", "sortSwitchCases"]) - } - - func testMultilineGuardLet() { - let input = """ - guard - let first = response?.first, - let last = response?.last, - case .foo(token: let foo, provider: let bar) = first, - case .foo(token: let baz, provider: let quux) = last - else { - return - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistCaseWithNilValue() { - let input = """ - switch (foo, bar) { - case let (.some(unwrappedFoo), nil): - print(unwrappedFoo) - default: - break - } - """ - let output = """ - switch (foo, bar) { - case (.some(let unwrappedFoo), nil): - print(unwrappedFoo) - default: - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } - - func testUnhoistCaseWithBoolValue() { - let input = """ - switch (foo, bar) { - case let (.some(unwrappedFoo), false): - print(unwrappedFoo) - default: - break - } - """ - let output = """ - switch (foo, bar) { - case (.some(let unwrappedFoo), false): - print(unwrappedFoo) - default: - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) - } -} diff --git a/Tests/RulesTests+Linebreaks.swift b/Tests/RulesTests+Linebreaks.swift deleted file mode 100644 index 1ad3e6b6..00000000 --- a/Tests/RulesTests+Linebreaks.swift +++ /dev/null @@ -1,861 +0,0 @@ -// -// RulesTests+Linebreaks.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class LinebreakTests: RulesTests { - // MARK: - linebreaks - - func testCarriageReturn() { - let input = "foo\rbar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) - } - - func testCarriageReturnLinefeed() { - let input = "foo\r\nbar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) - } - - func testVerticalTab() { - let input = "foo\u{000B}bar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) - } - - func testFormfeed() { - let input = "foo\u{000C}bar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) - } - - // MARK: - consecutiveBlankLines - - func testConsecutiveBlankLines() { - let input = "foo\n\n \nbar" - let output = "foo\n\nbar" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAtEndOfFile() { - let input = "foo\n\n" - let output = "foo\n" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAtStartOfFile() { - let input = "\n\n\nfoo" - let output = "\n\nfoo" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) - } - - func testConsecutiveBlankLinesInsideStringLiteral() { - let input = "\"\"\"\nhello\n\n\nworld\n\"\"\"" - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAtStartOfStringLiteral() { - let input = "\"\"\"\n\n\nhello world\n\"\"\"" - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAfterStringLiteral() { - let input = "\"\"\"\nhello world\n\"\"\"\n\n\nfoo()" - let output = "\"\"\"\nhello world\n\"\"\"\n\nfoo()" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) - } - - func testFragmentWithTrailingLinebreaks() { - let input = "func foo() {}\n\n\n" - let output = "func foo() {}\n\n" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines, options: options) - } - - func testConsecutiveBlankLinesNoInterpolation() { - let input = """ - \"\"\" - AAA - ZZZ - - - - \"\"\" - """ - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAfterInterpolation() { - let input = """ - \"\"\" - AAA - \\(interpolated) - - - - \"\"\" - """ - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) - } - - func testLintingConsecutiveBlankLinesReportsCorrectLine() { - let input = "foo\n \n\nbar" - XCTAssertEqual(try lint(input, rules: [FormatRules.consecutiveBlankLines]), [ - .init(line: 3, rule: FormatRules.consecutiveBlankLines, filePath: nil), - ]) - } - - // MARK: - blankLinesAtStartOfScope - - func testBlankLinesRemovedAtStartOfFunction() { - let input = "func foo() {\n\n // code\n}" - let output = "func foo() {\n // code\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) - } - - func testBlankLinesRemovedAtStartOfParens() { - let input = "(\n\n foo: Int\n)" - let output = "(\n foo: Int\n)" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) - } - - func testBlankLinesRemovedAtStartOfBrackets() { - let input = "[\n\n foo,\n bar,\n]" - let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) - } - - func testBlankLinesNotRemovedBetweenElementsInsideBrackets() { - let input = "[foo,\n\n bar]" - testFormatting(for: input, rule: FormatRules.blankLinesAtStartOfScope, exclude: ["wrapArguments"]) - } - - func testBlankLineRemovedFromStartOfTypeByDefault() { - let input = """ - class FooTests { - - func testFoo() {} - } - """ - - let output = """ - class FooTests { - func testFoo() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) - } - - func testBlankLinesNotRemovedFromStartOfTypeWithOptionEnabled() { - let input = """ - class FooClass { - - func fooMethod() {} - } - - struct FooStruct { - - func fooMethod() {} - } - - enum FooEnum { - - func fooMethod() {} - } - - actor FooActor { - - func fooMethod() {} - } - - protocol FooProtocol { - - func fooMethod() - } - - extension Array where Element == Foo { - - func fooMethod() {} - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - func testBlankLineAtStartOfScopeRemovedFromMethodInType() { - let input = """ - class Foo { - func bar() { - - print("hello world") - } - } - """ - - let output = """ - class Foo { - func bar() { - print("hello world") - } - } - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - // MARK: - blankLinesAtEndOfScope - - func testBlankLinesRemovedAtEndOfFunction() { - let input = "func foo() {\n // code\n\n}" - let output = "func foo() {\n // code\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) - } - - func testBlankLinesRemovedAtEndOfParens() { - let input = "(\n foo: Int\n\n)" - let output = "(\n foo: Int\n)" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) - } - - func testBlankLinesRemovedAtEndOfBrackets() { - let input = "[\n foo,\n bar,\n\n]" - let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) - } - - func testBlankLineNotRemovedBeforeElse() { - let input = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n\n}" - let output = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope, - exclude: ["blankLinesAtStartOfScope"]) - } - - func testBlankLineRemovedFromEndOfTypeByDefault() { - let input = """ - class FooTests { - func testFoo() {} - - } - """ - - let output = """ - class FooTests { - func testFoo() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) - } - - func testBlankLinesNotRemovedFromEndOfTypeWithOptionEnabled() { - let input = """ - class FooClass { - func fooMethod() {} - - } - - struct FooStruct { - func fooMethod() {} - - } - - enum FooEnum { - func fooMethod() {} - - } - - actor FooActor { - func fooMethod() {} - - } - - protocol FooProtocol { - func fooMethod() - } - - extension Array where Element == Foo { - func fooMethod() {} - - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - func testBlankLineAtEndOfScopeRemovedFromMethodInType() { - let input = """ - class Foo { - func bar() { - print("hello world") - - } - } - """ - - let output = """ - class Foo { - func bar() { - print("hello world") - } - } - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - // MARK: - blankLinesBetweenImports - - func testBlankLinesBetweenImportsShort() { - let input = """ - import ModuleA - - import ModuleB - """ - let output = """ - import ModuleA - import ModuleB - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenImports) - } - - func testBlankLinesBetweenImportsLong() { - let input = """ - import ModuleA - import ModuleB - - import ModuleC - import ModuleD - import ModuleE - - import ModuleF - - import ModuleG - import ModuleH - """ - let output = """ - import ModuleA - import ModuleB - import ModuleC - import ModuleD - import ModuleE - import ModuleF - import ModuleG - import ModuleH - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenImports) - } - - func testBlankLinesBetweenImportsWithTestable() { - let input = """ - import ModuleA - - @testable import ModuleB - import ModuleC - - @testable import ModuleD - @testable import ModuleE - - @testable import ModuleF - """ - let output = """ - import ModuleA - @testable import ModuleB - import ModuleC - @testable import ModuleD - @testable import ModuleE - @testable import ModuleF - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenImports) - } - - // MARK: - blankLinesBetweenChainedFunctions - - func testBlankLinesBetweenChainedFunctions() { - let input = """ - [0, 1, 2] - .map { $0 * 2 } - - - - .map { $0 * 3 } - """ - let output1 = """ - [0, 1, 2] - .map { $0 * 2 } - .map { $0 * 3 } - """ - let output2 = """ - [0, 1, 2] - .map { $0 * 2 } - .map { $0 * 3 } - """ - testFormatting(for: input, [output1, output2], rules: [FormatRules.blankLinesBetweenChainedFunctions]) - } - - func testBlankLinesWithCommentsBetweenChainedFunctions() { - let input = """ - [0, 1, 2] - .map { $0 * 2 } - - // Multiplies by 3 - - .map { $0 * 3 } - """ - let output = """ - [0, 1, 2] - .map { $0 * 2 } - // Multiplies by 3 - .map { $0 * 3 } - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenChainedFunctions) - } - - func testBlankLinesWithMarkCommentBetweenChainedFunctions() { - let input = """ - [0, 1, 2] - .map { $0 * 2 } - - // MARK: hello - - .map { $0 * 3 } - """ - testFormatting(for: input, rules: [FormatRules.blankLinesBetweenChainedFunctions, FormatRules.blankLinesAroundMark]) - } - - // MARK: - blankLineAfterImports - - func testBlankLineAfterImport() { - let input = """ - import ModuleA - @testable import ModuleB - import ModuleC - @testable import ModuleD - @_exported import ModuleE - @_implementationOnly import ModuleF - @_spi(SPI) import ModuleG - @_spiOnly import ModuleH - @preconcurrency import ModuleI - class foo {} - """ - let output = """ - import ModuleA - @testable import ModuleB - import ModuleC - @testable import ModuleD - @_exported import ModuleE - @_implementationOnly import ModuleF - @_spi(SPI) import ModuleG - @_spiOnly import ModuleH - @preconcurrency import ModuleI - - class foo {} - """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) - } - - func testBlankLinesBetweenConditionalImports() { - let input = """ - #if foo - import ModuleA - #else - import ModuleB - #endif - import ModuleC - func foo() {} - """ - let output = """ - #if foo - import ModuleA - #else - import ModuleB - #endif - import ModuleC - - func foo() {} - """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) - } - - func testBlankLinesBetweenNestedConditionalImports() { - let input = """ - #if foo - import ModuleA - #if bar - import ModuleB - #endif - #else - import ModuleC - #endif - import ModuleD - func foo() {} - """ - let output = """ - #if foo - import ModuleA - #if bar - import ModuleB - #endif - #else - import ModuleC - #endif - import ModuleD - - func foo() {} - """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) - } - - func testBlankLineAfterScopedImports() { - let input = """ - internal import UIKit - internal import Foundation - private import Time - public class Foo {} - """ - let output = """ - internal import UIKit - internal import Foundation - private import Time - - public class Foo {} - """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) - } - - // MARK: - blankLinesBetweenScopes - - func testBlankLineBetweenFunctions() { - let input = "func foo() {\n}\nfunc bar() {\n}" - let output = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testNoBlankLineBetweenPropertyAndFunction() { - let input = "var foo: Int\nfunc bar() {\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) - } - - func testBlankLineBetweenFunctionsIsBeforeComment() { - let input = "func foo() {\n}\n/// headerdoc\nfunc bar() {\n}" - let output = "func foo() {\n}\n\n/// headerdoc\nfunc bar() {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testBlankLineBeforeAtObjcOnLineBeforeProtocol() { - let input = "@objc\nprotocol Foo {\n}\n@objc\nprotocol Bar {\n}" - let output = "@objc\nprotocol Foo {\n}\n\n@objc\nprotocol Bar {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testBlankLineBeforeAtAvailabilityOnLineBeforeClass() { - let input = "protocol Foo {\n}\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" - let output = "protocol Foo {\n}\n\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testNoExtraBlankLineBetweenFunctions() { - let input = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) - } - - func testNoBlankLineBetweenFunctionsInProtocol() { - let input = "protocol Foo {\n func bar()\n func baz() -> Int\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoBlankLineInsideInitFunction() { - let input = "init() {\n super.init()\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testBlankLineAfterProtocolBeforeProperty() { - let input = "protocol Foo {\n}\nvar bar: String" - let output = "protocol Foo {\n}\n\nvar bar: String" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testNoExtraBlankLineAfterSingleLineComment() { - let input = "var foo: Bar? // comment\n\nfunc bar() {}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoExtraBlankLineAfterMultilineComment() { - let input = "var foo: Bar? /* comment */\n\nfunc bar() {}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoBlankLineBeforeFuncAsIdentifier() { - let input = "var foo: Bar?\nfoo.func(x) {}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenFunctionsWithInlineBody() { - let input = "class Foo {\n func foo() { print(\"foo\") }\n func bar() { print(\"bar\") }\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenIfStatements() { - let input = "func foo() {\n if x {\n }\n if y {\n }\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) - } - - func testNoBlanksInsideClassFunc() { - let input = "class func foo {\n if x {\n }\n if y {\n }\n}" - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, options: options, - exclude: ["emptyBraces"]) - } - - func testNoBlanksInsideClassVar() { - let input = "class var foo: Int {\n if x {\n }\n if y {\n }\n}" - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, options: options, - exclude: ["emptyBraces"]) - } - - func testBlankLineBetweenCalledClosures() { - let input = "class Foo {\n var foo = {\n }()\n func bar {\n }\n}" - let output = "class Foo {\n var foo = {\n }()\n\n func bar {\n }\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testNoBlankLineAfterCalledClosureAtEndOfScope() { - let input = "class Foo {\n var foo = {\n }()\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) - } - - func testNoBlankLineBeforeWhileInRepeatWhile() { - let input = """ - repeat - { print("foo") } - while false - { print("bar") }() - """ - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, options: options, exclude: ["redundantClosure", "wrapLoopBodies"]) - } - - func testBlankLineBeforeWhileIfNotRepeatWhile() { - let input = "func foo(x)\n{\n}\nwhile true\n{\n}" - let output = "func foo(x)\n{\n}\n\nwhile true\n{\n}" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, options: options, - exclude: ["emptyBraces"]) - } - - func testNoInsertBlankLinesInConditionalCompilation() { - let input = """ - struct Foo { - #if BAR - func something() { - } - #else - func something() { - } - #endif - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, - exclude: ["emptyBraces"]) - } - - func testNoInsertBlankLineAfterBraceBeforeSourceryComment() { - let input = """ - struct Foo { - var bar: String - - // sourcery:inline:Foo.init - public init(bar: String) { - self.bar = bar - } - // sourcery:end - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenChainedClosures() { - let input = """ - foo { - doFoo() - } - // bar - .bar { - doBar() - } - // baz - .baz { - doBaz($0) - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenTrailingClosures() { - let input = """ - UIView.animate(withDuration: 0) { - fromView.transform = .identity - } - completion: { finished in - context.completeTransition(finished) - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) - } - - func testBlankLineBetweenTrailingClosureAndLabelledLoop() { - let input = """ - UIView.animate(withDuration: 0) { - fromView.transform = .identity - } - completion: for foo in bar { - print(foo) - } - """ - let output = """ - UIView.animate(withDuration: 0) { - fromView.transform = .identity - } - - completion: for foo in bar { - print(foo) - } - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes) - } - - // MARK: - blankLinesAroundMark - - func testInsertBlankLinesAroundMark() { - let input = """ - let foo = "foo" - // MARK: bar - let bar = "bar" - """ - let output = """ - let foo = "foo" - - // MARK: bar - - let bar = "bar" - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark) - } - - func testNoInsertExtraBlankLinesAroundMark() { - let input = """ - let foo = "foo" - - // MARK: bar - - let bar = "bar" - """ - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark) - } - - func testInsertBlankLineAfterMarkAtStartOfFile() { - let input = """ - // MARK: bar - let bar = "bar" - """ - let output = """ - // MARK: bar - - let bar = "bar" - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark) - } - - func testInsertBlankLineBeforeMarkAtEndOfFile() { - let input = """ - let foo = "foo" - // MARK: bar - """ - let output = """ - let foo = "foo" - - // MARK: bar - """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark) - } - - func testNoInsertBlankLineBeforeMarkAtStartOfScope() { - let input = """ - do { - // MARK: foo - - let foo = "foo" - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark) - } - - func testNoInsertBlankLineAfterMarkAtEndOfScope() { - let input = """ - do { - let foo = "foo" - - // MARK: foo - } - """ - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark) - } - - func testInsertBlankLinesJustBeforeMarkNotAfter() { - let input = """ - let foo = "foo" - // MARK: bar - let bar = "bar" - """ - let output = """ - let foo = "foo" - - // MARK: bar - let bar = "bar" - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark, options: options) - } - - func testNoInsertExtraBlankLinesAroundMarkWithNoBlankLineAfterMark() { - let input = """ - let foo = "foo" - - // MARK: bar - let bar = "bar" - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark, options: options) - } - - func testNoInsertBlankLineAfterMarkAtStartOfFile() { - let input = """ - // MARK: bar - let bar = "bar" - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark, options: options) - } - - // MARK: - linebreakAtEndOfFile - - func testLinebreakAtEndOfFile() { - let input = "foo\nbar" - let output = "foo\nbar\n" - testFormatting(for: input, output, rule: FormatRules.linebreakAtEndOfFile) - } - - func testNoLinebreakAtEndOfFragment() { - let input = "foo\nbar" - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.linebreakAtEndOfFile, options: options) - } -} diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift deleted file mode 100644 index 8feb8668..00000000 --- a/Tests/RulesTests+Organization.swift +++ /dev/null @@ -1,5332 +0,0 @@ -// -// RulesTests+Organization.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class OrganizationTests: RulesTests { - // MARK: organizeDeclarations - - func testOrganizeClassDeclarationsIntoCategories() { - let input = """ - class Foo { - private func privateMethod() {} - - private let bar = 1 - public let baz = 1 - open var quack = 2 - package func packageMethod() {} - var quux = 2 - - /// `open` is the only visibility keyword that - /// can also be used as an identifier. - var open = 10 - - /* - * Block comment - */ - - init() {} - - /// Doc comment - public func publicMethod() {} - - #if DEBUG - private var foo: Foo? { nil } - #endif - } - - enum Bar { - private var bar: Bar { Bar() } - case enumCase - } - """ - - let output = """ - class Foo { - - // MARK: Lifecycle - - /* - * Block comment - */ - - init() {} - - // MARK: Open - - open var quack = 2 - - // MARK: Public - - public let baz = 1 - - /// Doc comment - public func publicMethod() {} - - // MARK: Package - - package func packageMethod() {} - - // MARK: Internal - - var quux = 2 - - /// `open` is the only visibility keyword that - /// can also be used as an identifier. - var open = 10 - - // MARK: Private - - private let bar = 1 - - #if DEBUG - private var foo: Foo? { nil } - #endif - - private func privateMethod() {} - - } - - enum Bar { - case enumCase - - // MARK: Private - - private var bar: Bar { Bar() } - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testOrganizeClassDeclarationsIntoCategoriesWithCustomTypeOrder() { - let input = """ - class Foo { - private func privateMethod() {} - - private let bar = 1 - public let baz = 1 - open var quack = 2 - package func packageMethod() {} - var quux = 2 - - /// `open` is the only visibility keyword that - /// can also be used as an identifier. - var open = 10 - - /* - * Block comment - */ - - init() {} - - /// Doc comment - public func publicMethod() {} - - #if DEBUG - private var foo: Foo? { nil } - #endif - } - - enum Bar { - private var bar: Bar { Bar() } - case enumCase - } - """ - - let output = """ - class Foo { - - // MARK: Lifecycle - - /* - * Block comment - */ - - init() {} - - // MARK: Open - - open var quack = 2 - - // MARK: Public - - public let baz = 1 - - /// Doc comment - public func publicMethod() {} - - // MARK: Package - - package func packageMethod() {} - - // MARK: Internal - - var quux = 2 - - /// `open` is the only visibility keyword that - /// can also be used as an identifier. - var open = 10 - - // MARK: Private - - private let bar = 1 - - #if DEBUG - private var foo: Foo? { nil } - #endif - - private func privateMethod() {} - - } - - enum Bar { - case enumCase - - // MARK: Private - - private var bar: Bar { Bar() } - } - """ - - // The configuration used in Airbnb's Swift Style Guide, - // as defined here: https://github.com/airbnb/swift#subsection-organization - let airbnbVisibilityOrder = "beforeMarks,instanceLifecycle,open,public,package,internal,private,fileprivate" - let airbnbTypeOrder = "nestedType,staticProperty,staticPropertyWithBody,classPropertyWithBody,instanceProperty,instancePropertyWithBody,staticMethod,classMethod,instanceMethod" - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - visibilityOrder: airbnbVisibilityOrder.components(separatedBy: ","), - typeOrder: airbnbTypeOrder.components(separatedBy: ",") - ), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testOrganizeClassDeclarationsIntoCategoriesInTypeOrder() { - let input = """ - class Foo { - private func privateMethod() {} - - private let bar = 1 - public let baz = 1 - open var quack = 2 - package func packageMethod() {} - var quux = 2 - - /// `open` is the only visibility keyword that - /// can also be used as an identifier. - var open = 10 - - /* - * Block comment - */ - - init() {} - - /// Doc comment - public func publicMethod() {} - } - """ - - let output = """ - class Foo { - - // MARK: Properties - - open var quack = 2 - - public let baz = 1 - - var quux = 2 - - /// `open` is the only visibility keyword that - /// can also be used as an identifier. - var open = 10 - - private let bar = 1 - - // MARK: Lifecycle - - /* - * Block comment - */ - - init() {} - - // MARK: Functions - - /// Doc comment - public func publicMethod() {} - - package func packageMethod() {} - - private func privateMethod() {} - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testOrganizeTypeWithOverridenFieldsInVisibilityOrder() { - let input = """ - class Test { - - override var b: Any? { nil } - - var a = "" - - override func bar() -> Bar { - Bar() - } - - func foo() -> Foo { - Foo() - } - - func baaz() -> Baaz { - Baaz() - } - - } - """ - - testFormatting( - for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] - ) - } - - func testOrganizeTypeWithOverridenFieldsInTypeOrder() { - let input = """ - class Test { - - var a = "" - - override var b: Any? { nil } - - func foo() -> Foo { - Foo() - } - - override func bar() -> Bar { - Bar() - } - - func baaz() -> Baaz { - Baaz() - } - - } - """ - - let output = """ - class Test { - - // MARK: Overridden Properties - - override var b: Any? { nil } - - // MARK: Properties - - var a = "" - - // MARK: Overridden Functions - - override func bar() -> Bar { - Bar() - } - - // MARK: Functions - - func foo() -> Foo { - Foo() - } - - func baaz() -> Baaz { - Baaz() - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] - ) - } - - func testOrganizeTypeWithSwiftUIMethodInVisibilityOrder() { - let input = """ - class Test { - - func bar() -> some View { - EmptyView() - } - - func foo() -> Foo { - Foo() - } - - func baaz() -> Baaz { - Baaz() - } - - } - """ - - testFormatting( - for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] - ) - } - - func testOrganizeSwiftUIViewInTypeOrder() { - let input = """ - struct ContentView: View { - - private var label: String - - @State - var isOn: Bool = false - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - .fixedSize() - } - - init(label: String) { - self.label = label - } - - @ViewBuilder - var body: some View { - toggle - } - } - """ - - let output = """ - struct ContentView: View { - - // MARK: SwiftUI Properties - - @State - var isOn: Bool = false - - // MARK: Properties - - private var label: String - - // MARK: Lifecycle - - init(label: String) { - self.label = label - } - - // MARK: Content Properties - - @ViewBuilder - var body: some View { - toggle - } - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - .fixedSize() - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testOrganizeSwiftUIViewModifierInTypeOrder() { - let input = """ - struct Modifier: ViewModifier { - - private var label: String - - @State - var isOn: Bool = false - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - .fixedSize() - } - - func body(content: Content) -> some View { - content - .overlay { - toggle - } - } - - init(label: String) { - self.label = label - } - } - """ - - let output = """ - struct Modifier: ViewModifier { - - // MARK: SwiftUI Properties - - @State - var isOn: Bool = false - - // MARK: Properties - - private var label: String - - // MARK: Lifecycle - - init(label: String) { - self.label = label - } - - // MARK: Content Properties - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - .fixedSize() - } - - // MARK: Content Methods - - func body(content: Content) -> some View { - content - .overlay { - toggle - } - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testCustomOrganizationInVisibilityOrder() { - let input = """ - class Foo { - public func bar() {} - func baz() {} - private func quux() {} - } - """ - - let output = """ - class Foo { - - // MARK: Private - - private func quux() {} - - // MARK: Internal - - func baz() {} - - // MARK: Public - - public func bar() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - visibilityOrder: ["private", "internal", "public"], - typeOrder: DeclarationType.allCases.map(\.rawValue) - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomOrganizationInVisibilityOrderWithParametrizedTypeOrder() { - let input = """ - class Foo { - - // MARK: Private - - private func quux() {} - - // MARK: Internal - - var baaz: Baaz - - func baz() {} - - // MARK: Public - - public func bar() {} - } - """ - - let output = """ - class Foo { - - // MARK: Private - - private func quux() {} - - // MARK: Internal - - func baz() {} - - var baaz: Baaz - - // MARK: Public - - public func bar() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - visibilityOrder: ["private", "internal", "public"], - typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomOrganizationInTypeOrder() { - let input = """ - class Foo { - private func quux() {} - var baaz: Baaz - func baz() {} - init() - override public func baar() - public func bar() {} - } - """ - - let output = """ - class Foo { - - // MARK: Lifecycle - - init() - - // MARK: Functions - - public func bar() {} - - func baz() {} - - private func quux() {} - - // MARK: Properties - - var baaz: Baaz - - // MARK: Overridden Functions - - override public func baar() - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .type, - typeOrder: ["beforeMarks", "instanceLifecycle", "instanceMethod", "nestedType", "instanceProperty", "overriddenMethod"] - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testOrganizeDeclarationsIgnoresNotDefinedCategories() { - let input = """ - class Foo { - private func quux() {} - var baaz: Baaz - func baz() {} - init() - override public func baar() - public func bar() {} - } - """ - - let output = """ - class Foo { - - // MARK: Lifecycle - - init() - - // MARK: Functions - - override public func baar() - public func bar() {} - - func baz() {} - - private func quux() {} - - // MARK: Properties - - var baaz: Baaz - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .type, - typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomOrganizationInTypeOrderWithParametrizedVisibilityOrder() { - let input = """ - class Foo { - private func quux() {} - var baaz: Baaz - private var fooo: Fooo - func baz() {} - init() - override public func baar() - public func bar() {} - } - """ - - let output = """ - class Foo { - - // MARK: Lifecycle - - init() - - // MARK: Functions - - private func quux() {} - - func baz() {} - - public func bar() {} - - // MARK: Properties - - private var fooo: Fooo - - var baaz: Baaz - - // MARK: Overridden Functions - - override public func baar() - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .type, - visibilityOrder: ["private", "internal", "public"], - typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomDeclarationTypeUsedAsTopLevelCategory() { - let input = """ - class Test { - private let foo = "foo" - func bar() {} - } - """ - - let output = """ - class Test { - - // MARK: Functions - - func bar() {} - - // MARK: Private - - private let foo = "foo" - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .visibility, - visibilityOrder: ["instanceMethod"] + Visibility.allCases.map(\.rawValue), - typeOrder: DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" } - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testVisibilityModeWithoutInstanceLifecycle() { - let input = """ - class Test { - init() {} - private func bar() {} - } - """ - - let output = """ - class Test { - - // MARK: Internal - - init() {} - - // MARK: Private - - private func bar() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .visibility, - visibilityOrder: Visibility.allCases.map(\.rawValue), - typeOrder: DeclarationType.allCases.map(\.rawValue) - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomCategoryNamesInVisibilityOrder() { - let input = """ - class Foo { - public var bar: Bar - init(bar: Bar) { - self.bar = bar - } - func baaz() {} - } - """ - - let output = """ - class Foo { - - // MARK: Init - - init(bar: Bar) { - self.bar = bar - } - - // MARK: Public_Group - - public var bar: Bar - - // MARK: Internal - - func baaz() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .visibility, - customVisibilityMarks: ["instanceLifecycle:Init", "public:Public_Group"] - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomCategoryNamesInTypeOrder() { - let input = """ - class Foo { - public var bar: Bar - init(bar: Bar) { - self.bar = bar - } - func baaz() {} - } - """ - - let output = """ - class Foo { - - // MARK: Bar_Bar - - public var bar: Bar - - // MARK: Init - - init(bar: Bar) { - self.bar = bar - } - - // MARK: Buuuz Lightyeeeaaar - - func baaz() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizationMode: .type, - customTypeMarks: ["instanceLifecycle:Init", "instanceProperty:Bar_Bar", "instanceMethod:Buuuz Lightyeeeaaar"] - ), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testClassNestedInClassIsOrganized() { - let input = """ - public class Foo { - public class Bar { - fileprivate func baz() {} - public var quux: Int - init() {} - deinit {} - } - } - """ - - let output = """ - public class Foo { - public class Bar { - - // MARK: Lifecycle - - init() {} - deinit {} - - // MARK: Public - - public var quux: Int - - // MARK: Fileprivate - - fileprivate func baz() {} - } - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "enumNamespaces"] - ) - } - - func testStructNestedInExtensionIsOrganized() { - let input = """ - public extension Foo { - struct Bar { - private var foo: Int - private let bar: Int - - public var foobar: (Int, Int) { - (foo, bar) - } - - public init(foo: Int, bar: Int) { - self.foo = foo - self.bar = bar - } - } - } - """ - - let output = """ - public extension Foo { - struct Bar { - - // MARK: Lifecycle - - public init(foo: Int, bar: Int) { - self.foo = foo - self.bar = bar - } - - // MARK: Public - - public var foobar: (Int, Int) { - (foo, bar) - } - - // MARK: Private - - private var foo: Int - private let bar: Int - - } - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testOrganizePrivateSet() { - let input = """ - class Foo { - public private(set) var bar: Int - private(set) var baz: Int - internal private(set) var baz: Int - } - """ - - let output = """ - class Foo { - - // MARK: Public - - public private(set) var bar: Int - - // MARK: Internal - - private(set) var baz: Int - internal private(set) var baz: Int - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "redundantInternal"] - ) - } - - func testSortDeclarationTypes() { - let input = """ - class Foo { - static var a1: Int = 1 - static var a2: Int = 2 - var d1: CGFloat { - 3.141592653589 - } - - class var b2: String { - "class computed property" - } - - func g() -> Int { - 10 - } - - let c: String = String { - "closure body" - }() - - static func e() {} - - typealias Bar = Int - - static var b1: String { - "static computed property" - } - - class func f() -> Foo { - Foo() - } - - enum NestedEnum {} - - var d2: CGFloat = 3.141592653589 { - didSet {} - } - } - """ - - let output = """ - class Foo { - typealias Bar = Int - - enum NestedEnum {} - - static var a1: Int = 1 - static var a2: Int = 2 - - static var b1: String { - "static computed property" - } - - class var b2: String { - "class computed property" - } - - let c: String = String { - "closure body" - }() - - var d1: CGFloat { - 3.141592653589 - } - - var d2: CGFloat = 3.141592653589 { - didSet {} - } - - static func e() {} - - class func f() -> Foo { - Foo() - } - - func g() -> Int { - 10 - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtEndOfScope", "redundantType", "redundantClosure"] - ) - } - - func testSortDeclarationTypesByType() { - let input = """ - class Foo { - var a: Int - init(a: Int) { - self.a = a - } - private convenience init() { - self.init(a: 0) - } - - static var a1: Int = 1 - static var a2: Int = 2 - var d1: CGFloat { - 3.141592653589 - } - - class var b2: String { - "class computed property" - } - - func g() -> Int { - 10 - } - - let c: String = String { - "closure body" - }() - - static func e() {} - - typealias Bar = Int - - static var b1: String { - "static computed property" - } - - class func f() -> Foo { - Foo() - } - - enum NestedEnum {} - - var d2: CGFloat = 3.141592653589 { - didSet {} - } - } - """ - - let output = """ - class Foo { - - // MARK: Nested Types - - typealias Bar = Int - - enum NestedEnum {} - - // MARK: Static Properties - - static var a1: Int = 1 - static var a2: Int = 2 - - // MARK: Static Computed Properties - - static var b1: String { - "static computed property" - } - - // MARK: Class Properties - - class var b2: String { - "class computed property" - } - - // MARK: Properties - - var a: Int - let c: String = String { - "closure body" - }() - - // MARK: Computed Properties - - var d1: CGFloat { - 3.141592653589 - } - - var d2: CGFloat = 3.141592653589 { - didSet {} - } - - // MARK: Lifecycle - - init(a: Int) { - self.a = a - } - - private convenience init() { - self.init(a: 0) - } - - // MARK: Static Functions - - static func e() {} - - // MARK: Class Functions - - class func f() -> Foo { - Foo() - } - - // MARK: Functions - - func g() -> Int { - 10 - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtEndOfScope", "blankLinesAtStartOfScope", "redundantType", "redundantClosure"] - ) - } - - func testOrganizeEnumCasesFirst() { - let input = """ - enum Foo { - init?(rawValue: String) { - return nil - } - - case bar - case baz - case quux - } - """ - - let output = """ - enum Foo { - case bar - case baz - case quux - - // MARK: Lifecycle - - init?(rawValue: String) { - return nil - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtEndOfScope", "unusedArguments"] - ) - } - - func testPlacingCustomDeclarationsBeforeMarks() { - let input = """ - struct Foo { - - public init() {} - - public typealias Bar = Int - - public struct Baz {} - - } - """ - - let output = """ - struct Foo { - - public typealias Bar = Int - - public struct Baz {} - - // MARK: Lifecycle - - public init() {} - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(beforeMarks: ["typealias", "struct"]), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testCustomLifecycleMethods() { - let input = """ - class ViewController: UIViewController { - - public init() { - super.init(nibName: nil, bundle: nil) - } - - func viewDidLoad() { - super.viewDidLoad() - } - - func internalInstanceMethod() {} - - func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - } - """ - - let output = """ - class ViewController: UIViewController { - - // MARK: Lifecycle - - public init() { - super.init(nibName: nil, bundle: nil) - } - - func viewDidLoad() { - super.viewDidLoad() - } - - func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - // MARK: Internal - - func internalInstanceMethod() {} - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(lifecycleMethods: ["viewDidLoad", "viewWillAppear", "viewDidAppear"]), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testCustomCategoryMarkTemplate() { - let input = """ - struct Foo { - public init() {} - public func publicInstanceMethod() {} - } - """ - - let output = """ - struct Foo { - - // - Lifecycle - - public init() {} - - // - Public - - public func publicInstanceMethod() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(categoryMarkComment: "- %c"), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testBelowCustomStructOrganizationThreshold() { - let input = """ - struct StructBelowThreshold { - init() {} - } - """ - - testFormatting( - for: input, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeStructThreshold: 2) - ) - } - - func testAboveCustomStructOrganizationThreshold() { - let input = """ - struct StructAboveThreshold { - init() {} - public func instanceMethod() {} - } - """ - - let output = """ - struct StructAboveThreshold { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func instanceMethod() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeStructThreshold: 2), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testCustomClassOrganizationThreshold() { - let input = """ - class ClassBelowThreshold { - init() {} - } - """ - - testFormatting( - for: input, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeClassThreshold: 2) - ) - } - - func testCustomEnumOrganizationThreshold() { - let input = """ - enum EnumBelowThreshold { - case enumCase - } - """ - - testFormatting( - for: input, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeEnumThreshold: 2) - ) - } - - func testBelowCustomExtensionOrganizationThreshold() { - let input = """ - extension FooBelowThreshold { - func bar() {} - } - """ - - testFormatting( - for: input, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizeTypes: ["class", "struct", "enum", "extension"], - organizeExtensionThreshold: 2 - ) - ) - } - - func testAboveCustomExtensionOrganizationThreshold() { - let input = """ - extension FooBelowThreshold { - public func bar() {} - func baz() {} - private func quux() {} - } - """ - - let output = """ - extension FooBelowThreshold { - - // MARK: Public - - public func bar() {} - - // MARK: Internal - - func baz() {} - - // MARK: Private - - private func quux() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions( - organizeTypes: ["class", "struct", "enum", "extension"], - organizeExtensionThreshold: 2 - ), exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testPreservesExistingMarks() { - let input = """ - actor Foo { - - // MARK: Lifecycle - - init(json: JSONObject) throws { - bar = try json.value(for: "bar") - baz = try json.value(for: "baz") - } - - // MARK: Internal - - let bar: String - let baz: Int? - } - """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) - } - - func testUpdatesMalformedMarks() { - let input = """ - actor Foo { - - // MARK: lifecycle - - // MARK: Lifeycle - - init() {} - - // Public - - // - Public - - public func bar() {} - - // MARK: - Internal - - func baz() {} - - // mrak: privat - - // Pulse - - private func quux() {} - } - """ - - let output = """ - actor Foo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func bar() {} - - // MARK: Internal - - func baz() {} - - // MARK: Private - - // Pulse - - private func quux() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) - } - - func testDoesntAttemptToUpdateMarksNotAtTopLevel() { - let input = """ - class Foo { - - // MARK: Lifecycle - - public init() { - foo = ["foo"] - } - - // Comment at bottom of lifecycle category - - // MARK: Private - - @annotation // Private - /// Private - private var foo: [String] = [] - - private func bar() { - // Private - guard let baz = bar else { - return - } - } - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "docCommentsBeforeAttributes"]) - } - - func testHandlesTrailingCommentCorrectly() { - let input = """ - class Foo { - var bar = "bar" - /// Leading comment - public var baz = "baz" // Trailing comment - var quux = "quux" - } - """ - - let output = """ - class Foo { - - // MARK: Public - - /// Leading comment - public var baz = "baz" // Trailing comment - - // MARK: Internal - - var bar = "bar" - var quux = "quux" - } - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) - } - - func testDoesntInsertMarkWhenOnlyOneCategory() { - let input = """ - class Foo { - var bar: Int - var baz: Int - func instanceMethod() {} - } - """ - - let output = """ - class Foo { - var bar: Int - var baz: Int - - func instanceMethod() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations) - } - - func testOrganizesTypesWithinConditionalCompilationBlock() { - let input = """ - #if DEBUG - struct DebugFoo { - init() {} - public func instanceMethod() {} - } - #else - struct ProductionFoo { - init() {} - public func instanceMethod() {} - } - #endif - """ - - let output = """ - #if DEBUG - struct DebugFoo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func instanceMethod() {} - } - #else - struct ProductionFoo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func instanceMethod() {} - } - #endif - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope"]) - } - - func testOrganizesTypesBelowConditionalCompilationBlock() { - let input = """ - #if canImport(UIKit) - import UIKit - #endif - - struct Foo { - init() {} - public func instanceMethod() {} - } - """ - - let output = """ - #if canImport(UIKit) - import UIKit - #endif - - struct Foo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func instanceMethod() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope"]) - } - - func testOrganizesNestedTypesWithinConditionalCompilationBlock() { - let input = """ - public struct Foo { - - public var bar = "bar" - var baz = "baz" - - #if DEBUG - public struct DebugFoo { - init() {} - var debugBar = "debug" - } - - static let debugFoo = DebugFoo() - - private let other = "other" - #endif - - init() {} - - var quuz = "quux" - - #if DEBUG - struct Test { - let foo: Bar - } - #endif - } - """ - - let output = """ - public struct Foo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - #if DEBUG - public struct DebugFoo { - - // MARK: Lifecycle - - init() {} - - // MARK: Internal - - var debugBar = "debug" - } - - static let debugFoo = DebugFoo() - - private let other = "other" - #endif - - public var bar = "bar" - - // MARK: Internal - - #if DEBUG - struct Test { - let foo: Bar - } - #endif - - var baz = "baz" - - var quuz = "quux" - - } - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "propertyType"]) - } - - func testOrganizesTypeBelowSymbolImport() { - let input = """ - import protocol SomeModule.SomeProtocol - import class SomeModule.SomeClass - import enum SomeModule.SomeEnum - import struct SomeModule.SomeStruct - import typealias SomeModule.SomeTypealias - import let SomeModule.SomeGlobalConstant - import var SomeModule.SomeGlobalVariable - import func SomeModule.SomeFunc - - struct Foo { - init() {} - public func instanceMethod() {} - } - """ - - let output = """ - import protocol SomeModule.SomeProtocol - import class SomeModule.SomeClass - import enum SomeModule.SomeEnum - import struct SomeModule.SomeStruct - import typealias SomeModule.SomeTypealias - import let SomeModule.SomeGlobalConstant - import var SomeModule.SomeGlobalVariable - import func SomeModule.SomeFunc - - struct Foo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func instanceMethod() {} - } - """ - - testFormatting( - for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "sortImports"] - ) - } - - func testDoesntBreakStructSynthesizedMemberwiseInitializer() { - let input = """ - struct Foo { - var bar: Int { - didSet {} - } - - var baz: Int - public let quux: Int - } - - Foo(bar: 1, baz: 2, quux: 3) - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations) - } - - func testOrganizesStructPropertiesThatDontBreakMemberwiseInitializer() { - let input = """ - struct Foo { - var computed: String { - let didSet = "didSet" - let willSet = "willSet" - return didSet + willSet - } - - private func instanceMethod() {} - public let bar: Int - var baz: Int - var quux: Int { - didSet {} - } - } - - Foo(bar: 1, baz: 2, quux: 3) - """ - - let output = """ - struct Foo { - - // MARK: Public - - public let bar: Int - - // MARK: Internal - - var baz: Int - - var computed: String { - let didSet = "didSet" - let willSet = "willSet" - return didSet + willSet - } - - var quux: Int { - didSet {} - } - - // MARK: Private - - private func instanceMethod() {} - } - - Foo(bar: 1, baz: 2, quux: 3) - """ - - testFormatting( - for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testPreservesCategoryMarksInStructWithIncorrectSubcategoryOrdering() { - let input = """ - struct Foo { - - // MARK: Public - - public let quux: Int - - // MARK: Internal - - var bar: Int { - didSet {} - } - - var baz: Int - } - - Foo(bar: 1, baz: 2, quux: 3) - """ - - testFormatting( - for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testPreservesCommentsAtBottomOfCategory() { - let input = """ - struct Foo { - - // MARK: Lifecycle - - init() {} - - // Important comment at end of section! - - // MARK: Public - - public let bar = 1 - } - """ - - testFormatting( - for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testPreservesCommentsAtBottomOfCategoryWhenReorganizing() { - let input = """ - struct Foo { - - // MARK: Lifecycle - - init() {} - - // Important comment at end of section! - - // MARK: Internal - - // Important comment at start of section! - - var baz = 1 - - public let bar = 1 - } - """ - - let output = """ - struct Foo { - - // MARK: Lifecycle - - init() {} - - // Important comment at end of section! - - // MARK: Public - - public let bar = 1 - - // MARK: Internal - - // Important comment at start of section! - - var baz = 1 - - } - """ - - testFormatting( - for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testDoesntRemoveCategorySeparatorsFromBodyNotBeingOrganized() { - let input = """ - struct Foo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public var bar = 10 - } - - extension Foo { - - // MARK: Public - - public var baz: Int { 20 } - - // MARK: Internal - - var quux: Int { 30 } - } - """ - - testFormatting( - for: input, rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeStructThreshold: 20), - exclude: ["blankLinesAtStartOfScope"] - ) - } - - func testParsesPropertiesWithBodies() { - let input = """ - class Foo { - // Instance properties without bodies: - - let propertyWithoutBody1 = 10 - - let propertyWithoutBody2: String = { - "bar" - }() - - let propertyWithoutBody3: () -> String = { - "bar" - } - - // Instance properties with bodies: - - var withBody1: String { - "bar" - } - - var withBody2: String { - didSet { print("didSet") } - } - - var withBody3: String = "bar" { - didSet { print("didSet") } - } - - var withBody4: String = "bar" { - didSet { print("didSet") } - } - - var withBody5: () -> String = { "bar" } { - didSet { print("didSet") } - } - - var withBody6: String = { "bar" }() { - didSet { print("didSet") } - } - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations, exclude: ["redundantClosure"]) - } - - func testFuncWithNestedInitNotTreatedAsLifecycle() { - let input = """ - struct Foo { - - // MARK: Public - - public func baz() {} - - // MARK: Internal - - func bar() { - class NestedClass { - init() {} - } - - // ... - } - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) - } - - func testOrganizeRuleNotConfusedByClassProtocol() { - let input = """ - protocol Foo: class { - func foo() - } - - class Bar { - // MARK: Fileprivate - - private var baz: Int - - // MARK: Private - - private let quux: String - } - """ - - let output = """ - protocol Foo: class { - func foo() - } - - class Bar { - private var baz: Int - - private let quux: String - } - """ - - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) - } - - func testOrganizeClassDeclarationsIntoCategoriesWithNoBlankLineAfterMark() { - let input = """ - class Foo { - private func privateMethod() {} - - private let bar = 1 - public let baz = 1 - open var quack = 2 - var quux = 2 - - init() {} - - /// Doc comment - public func publicMethod() {} - } - """ - - let output = """ - class Foo { - - // MARK: Lifecycle - init() {} - - // MARK: Open - open var quack = 2 - - // MARK: Public - public let baz = 1 - - /// Doc comment - public func publicMethod() {} - - // MARK: Internal - var quux = 2 - - // MARK: Private - private let bar = 1 - - private func privateMethod() {} - - } - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: options, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testOrganizeWithNoCategoryMarks_noSpacesBetweenDeclarations() { - let input = """ - class Foo { - private func privateMethod() {} - private let bar = 1 - public let baz = 1 - } - """ - - let output = """ - class Foo { - public let baz = 1 - - private let bar = 1 - - private func privateMethod() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(markCategories: false) - ) - } - - func testOrganizeWithNoCategoryMarks_withSpacesBetweenDeclarations() { - let input = """ - class Foo { - private func privateMethod() {} - - private let bar = 1 - - public let baz = 1 - - private func anotherPrivateMethod() {} - } - """ - - let output = """ - class Foo { - public let baz = 1 - - private let bar = 1 - - private func privateMethod() {} - - private func anotherPrivateMethod() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(markCategories: false) - ) - } - - func testOrganizeConditionalInitDeclaration() { - let input = """ - class Foo { - - // MARK: Lifecycle - - init() {} - - #if DEBUG - init() { - print("Debug") - } - #endif - - // MARK: Internal - - func test() {} - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - func testOrganizeConditionalPublicFunction() { - let input = """ - class Foo { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - #if DEBUG - public func publicTest() {} - #endif - - // MARK: Internal - - func internalTest() {} - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - // MARK: extensionAccessControl .onDeclarations - - func testUpdatesVisibilityOfExtensionMembers() { - let input = """ - private extension Foo { - var publicProperty: Int { 10 } - public func publicFunction1() {} - func publicFunction2() {} - internal func internalFunction() {} - private func privateFunction() {} - fileprivate var privateProperty: Int { 10 } - } - """ - - let output = """ - extension Foo { - fileprivate var publicProperty: Int { 10 } - public func publicFunction1() {} - fileprivate func publicFunction2() {} - internal func internalFunction() {} - private func privateFunction() {} - fileprivate var privateProperty: Int { 10 } - } - """ - - testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations), - exclude: ["redundantInternal"] - ) - } - - func testUpdatesVisibilityOfExtensionInConditionalCompilationBlock() { - let input = """ - #if DEBUG - public extension Foo { - var publicProperty: Int { 10 } - } - #endif - """ - - let output = """ - #if DEBUG - extension Foo { - public var publicProperty: Int { 10 } - } - #endif - """ - - testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } - - func testUpdatesVisibilityOfExtensionMembersInConditionalCompilationBlock() { - let input = """ - public extension Foo { - #if DEBUG - var publicProperty: Int { 10 } - #endif - } - """ - - let output = """ - extension Foo { - #if DEBUG - public var publicProperty: Int { 10 } - #endif - } - """ - - testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } - - func testDoesntUpdateDeclarationsInsideTypeInsideExtension() { - let input = """ - public extension Foo { - struct Bar { - var baz: Int - var quux: Int - } - } - """ - - let output = """ - extension Foo { - public struct Bar { - var baz: Int - var quux: Int - } - } - """ - - testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } - - func testDoesNothingForInternalExtension() { - let input = """ - extension Foo { - func bar() {} - func baz() {} - public func quux() {} - } - """ - - testFormatting( - for: input, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } - - func testPlacesVisibilityKeywordAfterAnnotations() { - let input = """ - public extension Foo { - @discardableResult - func bar() -> Int { 10 } - - /// Doc comment - @discardableResult - @available(iOS 10.0, *) - func baz() -> Int { 10 } - - @objc func quux() {} - @available(iOS 10.0, *) func quixotic() {} - } - """ - - let output = """ - extension Foo { - @discardableResult - public func bar() -> Int { 10 } - - /// Doc comment - @discardableResult - @available(iOS 10.0, *) - public func baz() -> Int { 10 } - - @objc public func quux() {} - @available(iOS 10.0, *) public func quixotic() {} - } - """ - - testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } - - func testConvertsExtensionPrivateToMemberFileprivate() { - let input = """ - private extension Foo { - var bar: Int - } - - let bar = Foo().bar - """ - - let output = """ - extension Foo { - fileprivate var bar: Int - } - - let bar = Foo().bar - """ - - testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), - exclude: ["propertyType"] - ) - } - - // MARK: extensionAccessControl .onExtension - - func testUpdatedVisibilityOfExtension() { - let input = """ - extension Foo { - public func bar() {} - public var baz: Int { 10 } - - public struct Foo2 { - var quux: Int - } - } - """ - - let output = """ - public extension Foo { - func bar() {} - var baz: Int { 10 } - - struct Foo2 { - var quux: Int - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) - } - - func testUpdatedVisibilityOfExtensionWithDeclarationsInConditionalCompilation() { - let input = """ - extension Foo { - #if DEBUG - public func bar() {} - public var baz: Int { 10 } - #endif - } - """ - - let output = """ - public extension Foo { - #if DEBUG - func bar() {} - var baz: Int { 10 } - #endif - } - """ - - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) - } - - func testDoesntUpdateExtensionVisibilityWithoutMajorityBodyVisibility() { - let input = """ - extension Foo { - public func foo() {} - public func bar() {} - var baz: Int { 10 } - var quux: Int { 5 } - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testUpdateExtensionVisibilityWithMajorityBodyVisibility() { - let input = """ - extension Foo { - public func foo() {} - public func bar() {} - public var baz: Int { 10 } - var quux: Int { 5 } - } - """ - - let output = """ - public extension Foo { - func foo() {} - func bar() {} - var baz: Int { 10 } - internal var quux: Int { 5 } - } - """ - - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) - } - - func testDoesntUpdateExtensionVisibilityWhenMajorityBodyVisibilityIsntMostVisible() { - let input = """ - extension Foo { - func foo() {} - func bar() {} - public var baz: Int { 10 } - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testDoesntUpdateExtensionVisibilityWithInternalDeclarations() { - let input = """ - extension Foo { - func bar() {} - var baz: Int { 10 } - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testDoesntUpdateExtensionThatAlreadyHasCorrectVisibilityKeyword() { - let input = """ - public extension Foo { - func bar() {} - func baz() {} - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testUpdatesExtensionThatHasHigherACLThanBodyDeclarations() { - let input = """ - public extension Foo { - fileprivate func bar() {} - fileprivate func baz() {} - } - """ - - let output = """ - fileprivate extension Foo { - func bar() {} - func baz() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl, - exclude: ["redundantFileprivate"]) - } - - func testDoesntHoistPrivateVisibilityFromExtensionBodyDeclarations() { - let input = """ - extension Foo { - private var bar() {} - private func baz() {} - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testDoesntUpdatesExtensionThatHasLowerACLThanBodyDeclarations() { - let input = """ - private extension Foo { - public var bar() {} - public func baz() {} - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testDoesntReduceVisibilityOfImplicitInternalDeclaration() { - let input = """ - extension Foo { - fileprivate var bar() {} - func baz() {} - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testUpdatesExtensionThatHasRedundantACLOnBodyDeclarations() { - let input = """ - public extension Foo { - func bar() {} - public func baz() {} - } - """ - - let output = """ - public extension Foo { - func bar() {} - func baz() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) - } - - func testNoHoistAccessModifierForOpenMethod() { - let input = """ - extension Foo { - open func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testDontChangePrivateExtensionToFileprivate() { - let input = """ - private extension Foo { - func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testDontRemoveInternalKeywordFromExtension() { - let input = """ - internal extension Foo { - func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl, exclude: ["redundantInternal"]) - } - - func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() { - let input = """ - extension Foo: Bar { - public func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testProtocolConformanceCheckNotFooledByWhereClause() { - let input = """ - extension Foo where Self: Bar { - public func bar() {} - } - """ - let output = """ - public extension Foo where Self: Bar { - func bar() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) - } - - func testAccessNotHoistedIfTypeVisibilityIsLower() { - let input = """ - class Foo {} - - extension Foo { - public func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() { - let input = """ - #if os(Linux) - #error("Linux is currently not supported") - #endif - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() { - let input = """ - struct Foo { - // This type is empty - } - - extension Foo { - // This extension is empty - } - """ - - testFormatting(for: input, rule: FormatRules.extensionAccessControl) - } - - // MARK: markTypes - - func testAddsMarkBeforeTypes() { - let input = """ - struct Foo {} - class Bar {} - enum Baz {} - protocol Quux {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - - // MARK: - Bar - - class Bar {} - - // MARK: - Baz - - enum Baz {} - - // MARK: - Quux - - protocol Quux {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testDoesntAddMarkBeforeStructWithExistingMark() { - let input = """ - // MARK: - Foo - - struct Foo {} - extension Foo {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testCorrectsTypoInTypeMark() { - let input = """ - // mark: foo - - struct Foo {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testUpdatesMarkAfterTypeIsRenamed() { - let input = """ - // MARK: - FooBarControllerFactory - - struct FooBarControllerBuilder {} - extension FooBarControllerBuilder {} - """ - - let output = """ - // MARK: - FooBarControllerBuilder - - struct FooBarControllerBuilder {} - extension FooBarControllerBuilder {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testAddsMarkBeforeTypeWithDocComment() { - let input = """ - /// This is a doc comment with several - /// lines of prose at the start - /// - And then, after the prose, - /// - a few bullet points just for fun - actor Foo {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo - - /// This is a doc comment with several - /// lines of prose at the start - /// - And then, after the prose, - /// - a few bullet points just for fun - actor Foo {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testCustomTypeMark() { - let input = """ - struct Foo {} - extension Foo {} - """ - - let output = """ - // TYPE DEFINITION: Foo - - struct Foo {} - extension Foo {} - """ - - testFormatting( - for: input, output, rule: FormatRules.markTypes, - options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t") - ) - } - - func testDoesNothingForExtensionWithoutProtocolConformance() { - let input = """ - extension Foo {} - extension Foo {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func preservesExistingCommentForExtensionWithNoConformances() { - let input = """ - // MARK: Description of extension - - extension Foo {} - extension Foo {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testAddsMarkCommentForExtensionWithConformance() { - let input = """ - extension Foo: BarProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testUpdatesExtensionMarkToCorrectMark() { - let input = """ - // MARK: - BarProtocol - - extension Foo: BarProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testAddsMarkCommentForExtensionWithMultipleConformances() { - let input = """ - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol, BazProtocol - - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testUpdatesMarkCommentWithCorrectConformances() { - let input = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol, BazProtocol - - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testCustomExtensionMarkComment() { - let input = """ - struct Foo {} - extension Foo: BarProtocol {} - extension String: BarProtocol {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - - // EXTENSION: - BarProtocol - - extension Foo: BarProtocol {} - - // EXTENSION: - String: BarProtocol - - extension String: BarProtocol {} - """ - - testFormatting( - for: input, output, rule: FormatRules.markTypes, - options: FormatOptions( - extensionMarkComment: "EXTENSION: - %t: %c", - groupedExtensionMarkComment: "EXTENSION: - %c" - ) - ) - } - - func testTypeAndExtensionMarksTogether() { - let input = """ - struct Foo {} - extension Foo: Bar {} - extension String: Bar {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - - // MARK: Bar - - extension Foo: Bar {} - - // MARK: - String + Bar - - extension String: Bar {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testFullyQualifiedTypeNames() { - let input = """ - extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} - extension MyModule.Foo {} - """ - - let output = """ - // MARK: - MyModule.Foo + MyModule.MyNamespace.BarProtocol, QuuxProtocol - - extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} - extension MyModule.Foo {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testWhereClauseConformanceWithExactConstraint() { - let input = """ - extension Array: BarProtocol where Element == String {} - extension Array {} - """ - - let output = """ - // MARK: - Array + BarProtocol - - extension Array: BarProtocol where Element == String {} - extension Array {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testWhereClauseConformanceWithConformanceConstraint() { - let input = """ - extension Array: BarProtocol where Element: BarProtocol {} - extension Array {} - """ - - let output = """ - // MARK: - Array + BarProtocol - - extension Array: BarProtocol where Element: BarProtocol {} - extension Array {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testWhereClauseWithExactConstraint() { - let input = """ - extension Array where Element == String {} - extension Array {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testWhereClauseWithConformanceConstraint() { - let input = """ - // MARK: [BarProtocol] helpers - - extension Array where Element: BarProtocol {} - extension Rules {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testPlacesMarkAfterImports() { - let input = """ - import Foundation - import os - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - let output = """ - import Foundation - import os - - // MARK: - Rules - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testPlacesMarkAfterFileHeader() { - let input = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - let output = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - // MARK: - Rules - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testPlacesMarkAfterFileHeaderAndImports() { - let input = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - import Foundation - import os - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - let output = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - import Foundation - import os - - // MARK: - Rules - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testDoesNothingIfOnlyOneDeclaration() { - let input = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - import Foundation - import os - - /// All of SwiftFormat's Rule implementation - class Rules {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testMultipleExtensionsOfSameType() { - let input = """ - extension Foo: BarProtocol {} - extension Foo: QuuxProtocol {} - """ - - let output = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol {} - - // MARK: - Foo + QuuxProtocol - - extension Foo: QuuxProtocol {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testNeverMarkTypes() { - let input = """ - struct EmptyFoo {} - struct EmptyBar { } - struct EmptyBaz { - - } - struct Quux { - let foo = 1 - } - """ - - let options = FormatOptions(markTypes: .never) - testFormatting( - for: input, rule: FormatRules.markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] - ) - } - - func testMarkTypesIfNotEmpty() { - let input = """ - struct EmptyFoo {} - struct EmptyBar { } - struct EmptyBaz { - - } - struct Quux { - let foo = 1 - } - """ - - let output = """ - struct EmptyFoo {} - struct EmptyBar { } - struct EmptyBaz { - - } - - // MARK: - Quux - - struct Quux { - let foo = 1 - } - """ - - let options = FormatOptions(markTypes: .ifNotEmpty) - testFormatting( - for: input, output, rule: FormatRules.markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] - ) - } - - func testNeverMarkExtensions() { - let input = """ - extension EmptyFoo: FooProtocol {} - extension EmptyBar: BarProtocol { } - extension EmptyBaz: BazProtocol { - - } - extension Quux: QuuxProtocol { - let foo = 1 - } - """ - - let options = FormatOptions(markExtensions: .never) - testFormatting( - for: input, rule: FormatRules.markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] - ) - } - - func testMarkExtensionsIfNotEmpty() { - let input = """ - extension EmptyFoo: FooProtocol {} - extension EmptyBar: BarProtocol { } - extension EmptyBaz: BazProtocol { - - } - extension Quux: QuuxProtocol { - let foo = 1 - } - """ - - let output = """ - extension EmptyFoo: FooProtocol {} - extension EmptyBar: BarProtocol { } - extension EmptyBaz: BazProtocol { - - } - - // MARK: - Quux + QuuxProtocol - - extension Quux: QuuxProtocol { - let foo = 1 - } - """ - - let options = FormatOptions(markExtensions: .ifNotEmpty) - testFormatting( - for: input, output, rule: FormatRules.markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] - ) - } - - func testMarkExtensionsDisabled() { - let input = """ - extension Foo: FooProtocol {} - - // swiftformat:disable markTypes - - extension Bar: BarProtocol {} - - // swiftformat:enable markTypes - - extension Baz: BazProtocol {} - - extension Quux: QuuxProtocol {} - """ - - let output = """ - // MARK: - Foo + FooProtocol - - extension Foo: FooProtocol {} - - // swiftformat:disable markTypes - - extension Bar: BarProtocol {} - - // MARK: - Baz + BazProtocol - - // swiftformat:enable markTypes - - extension Baz: BazProtocol {} - - // MARK: - Quux + QuuxProtocol - - extension Quux: QuuxProtocol {} - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testExtensionMarkWithImportOfSameName() { - let input = """ - import MagazineLayout - - // MARK: - MagazineLayout + FooProtocol - - extension MagazineLayout: FooProtocol {} - - // MARK: - MagazineLayout + BarProtocol - - extension MagazineLayout: BarProtocol {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testDoesntUseGroupedMarkTemplateWhenSeparatedByOtherType() { - let input = """ - // MARK: - MyComponent - - class MyComponent {} - - // MARK: - MyComponentContent - - struct MyComponentContent {} - - // MARK: - MyComponent + ContentConfigurableView - - extension MyComponent: ContentConfigurableView {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testUsesGroupedMarkTemplateWhenSeparatedByExtensionOfSameType() { - let input = """ - // MARK: - MyComponent - - class MyComponent {} - - // MARK: Equatable - - extension MyComponent: Equatable {} - - // MARK: ContentConfigurableView - - extension MyComponent: ContentConfigurableView {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testDoesntUseGroupedMarkTemplateWhenSeparatedByExtensionOfOtherType() { - let input = """ - // MARK: - MyComponent - - class MyComponent {} - - // MARK: - OtherComponent + Equatable - - extension OtherComponent: Equatable {} - - // MARK: - MyComponent + ContentConfigurableView - - extension MyComponent: ContentConfigurableView {} - """ - - testFormatting(for: input, rule: FormatRules.markTypes) - } - - func testAddsMarkBeforeTypesWithNoBlankLineAfterMark() { - let input = """ - struct Foo {} - class Bar {} - enum Baz {} - protocol Quux {} - """ - - let output = """ - // MARK: - Foo - struct Foo {} - - // MARK: - Bar - class Bar {} - - // MARK: - Baz - enum Baz {} - - // MARK: - Quux - protocol Quux {} - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, output, rule: FormatRules.markTypes, options: options) - } - - func testAddsMarkForTypeInExtension() { - let input = """ - enum Foo {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - let output = """ - // MARK: - Foo - - enum Foo {} - - // MARK: Foo.Bar - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testDoesntAddsMarkForMultipleTypesInExtension() { - let input = """ - enum Foo {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - - struct Quux { - let baaz: Baaz - } - } - """ - - let output = """ - // MARK: - Foo - - enum Foo {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - - struct Quux { - let baaz: Baaz - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testAddsMarkForTypeInExtensionNotFollowingTypeBeingExtended() { - let input = """ - struct Baaz {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - let output = """ - // MARK: - Baaz - - struct Baaz {} - - // MARK: - Foo.Bar - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testHandlesMultipleLayersOfExtensionNesting() { - let input = """ - enum Foo {} - - extension Foo { - enum Bar {} - } - - extension Foo { - extension Bar { - struct Baaz { - let quux: Quux - } - } - } - """ - - let output = """ - // MARK: - Foo - - enum Foo {} - - // MARK: Foo.Bar - - extension Foo { - enum Bar {} - } - - // MARK: Foo.Bar.Baaz - - extension Foo { - extension Bar { - struct Baaz { - let quux: Quux - } - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.markTypes) - } - - func testMarkTypeLintReturnsErrorAsExpected() throws { - let input = """ - struct MyStruct {} - - extension MyStruct {} - """ - - // Initialize rule names - let _ = FormatRules.byName - let changes = try lint(input, rules: [FormatRules.markTypes]) - XCTAssertEqual(changes, [ - .init(line: 1, rule: FormatRules.markTypes, filePath: nil), - .init(line: 2, rule: FormatRules.markTypes, filePath: nil), - ]) - } - - // MARK: - sortImports - - func testSortImportsSimpleCase() { - let input = "import Foo\nimport Bar" - let output = "import Bar\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testSortImportsKeepsPreviousCommentWithImport() { - let input = "import Foo\n// important comment\n// (very important)\nimport Bar" - let output = "// important comment\n// (very important)\nimport Bar\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports, - exclude: ["blankLineAfterImports"]) - } - - func testSortImportsKeepsPreviousCommentWithImport2() { - let input = "// important comment\n// (very important)\nimport Foo\nimport Bar" - let output = "import Bar\n// important comment\n// (very important)\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports, - exclude: ["blankLineAfterImports"]) - } - - func testSortImportsDoesntMoveHeaderComment() { - let input = "// header comment\n\nimport Foo\nimport Bar" - let output = "// header comment\n\nimport Bar\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testSortImportsDoesntMoveHeaderCommentFollowedByImportComment() { - let input = "// header comment\n\n// important comment\nimport Foo\nimport Bar" - let output = "// header comment\n\nimport Bar\n// important comment\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports, - exclude: ["blankLineAfterImports"]) - } - - func testSortImportsOnSameLine() { - let input = "import Foo; import Bar\nimport Baz" - let output = "import Baz\nimport Foo; import Bar" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testSortImportsWithSemicolonAndCommentOnSameLine() { - let input = "import Foo; // foobar\nimport Bar\nimport Baz" - let output = "import Bar\nimport Baz\nimport Foo; // foobar" - testFormatting(for: input, output, rule: FormatRules.sortImports, exclude: ["semicolons"]) - } - - func testSortImportEnum() { - let input = "import enum Foo.baz\nimport Foo.bar" - let output = "import Foo.bar\nimport enum Foo.baz" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testSortImportFunc() { - let input = "import func Foo.baz\nimport Foo.bar" - let output = "import Foo.bar\nimport func Foo.baz" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testAlreadySortImportsDoesNothing() { - let input = "import Bar\nimport Foo" - testFormatting(for: input, rule: FormatRules.sortImports) - } - - func testPreprocessorSortImports() { - let input = "#if os(iOS)\n import Foo2\n import Bar2\n#else\n import Foo1\n import Bar1\n#endif\nimport Foo3\nimport Bar3" - let output = "#if os(iOS)\n import Bar2\n import Foo2\n#else\n import Bar1\n import Foo1\n#endif\nimport Bar3\nimport Foo3" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testTestableSortImports() { - let input = "@testable import Foo3\nimport Bar3" - let output = "import Bar3\n@testable import Foo3" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testLengthSortImports() { - let input = "import Foo\nimport Module\nimport Bar3" - let output = "import Foo\nimport Bar3\nimport Module" - let options = FormatOptions(importGrouping: .length) - testFormatting(for: input, output, rule: FormatRules.sortImports, options: options) - } - - func testTestableImportsWithTestableOnPreviousLine() { - let input = "@testable\nimport Foo3\nimport Bar3" - let output = "import Bar3\n@testable\nimport Foo3" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testTestableImportsWithGroupingTestableBottom() { - let input = "@testable import Bar\nimport Foo\n@testable import UIKit" - let output = "import Foo\n@testable import Bar\n@testable import UIKit" - let options = FormatOptions(importGrouping: .testableLast) - testFormatting(for: input, output, rule: FormatRules.sortImports, options: options) - } - - func testTestableImportsWithGroupingTestableTop() { - let input = "@testable import Bar\nimport Foo\n@testable import UIKit" - let output = "@testable import Bar\n@testable import UIKit\nimport Foo" - let options = FormatOptions(importGrouping: .testableFirst) - testFormatting(for: input, output, rule: FormatRules.sortImports, options: options) - } - - func testCaseInsensitiveSortImports() { - let input = "import Zlib\nimport lib" - let output = "import lib\nimport Zlib" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testCaseInsensitiveCaseDifferingSortImports() { - let input = "import c\nimport B\nimport A.a\nimport A.A" - let output = "import A.A\nimport A.a\nimport B\nimport c" - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testNoDeleteCodeBetweenImports() { - let input = "import Foo\nfunc bar() {}\nimport Bar" - testFormatting(for: input, rule: FormatRules.sortImports, - exclude: ["blankLineAfterImports"]) - } - - func testNoDeleteCodeBetweenImports2() { - let input = "import Foo\nimport Bar\nfoo = bar\nimport Bar" - let output = "import Bar\nimport Foo\nfoo = bar\nimport Bar" - testFormatting(for: input, output, rule: FormatRules.sortImports, - exclude: ["blankLineAfterImports"]) - } - - func testNoDeleteCodeBetweenImports3() { - let input = """ - import Z - - // one - - #if FLAG - print("hi") - #endif - - import A - """ - testFormatting(for: input, rule: FormatRules.sortImports) - } - - func testSortContiguousImports() { - let input = "import Foo\nimport Bar\nfunc bar() {}\nimport Quux\nimport Baz" - let output = "import Bar\nimport Foo\nfunc bar() {}\nimport Baz\nimport Quux" - testFormatting(for: input, output, rule: FormatRules.sortImports, - exclude: ["blankLineAfterImports"]) - } - - func testNoMangleImportsPrecededByComment() { - let input = """ - // evil comment - - #if canImport(Foundation) - import Foundation - #if canImport(UIKit) && canImport(AVFoundation) - import UIKit - import AVFoundation - #endif - #endif - """ - let output = """ - // evil comment - - #if canImport(Foundation) - import Foundation - #if canImport(UIKit) && canImport(AVFoundation) - import AVFoundation - import UIKit - #endif - #endif - """ - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - func testNoMangleFileHeaderNotFollowedByLinebreak() { - let input = """ - // - // Code.swift - // Module - // - // Created by Someone on 4/30/20. - // - import AModuleUI - import AModule - import AModuleHelper - import SomeOtherModule - """ - let output = """ - // - // Code.swift - // Module - // - // Created by Someone on 4/30/20. - // - import AModule - import AModuleHelper - import AModuleUI - import SomeOtherModule - """ - testFormatting(for: input, output, rule: FormatRules.sortImports) - } - - // MARK: - sortSwitchCases - - func testSortedSwitchCaseNestedSwitchOneCaseDoesNothing() { - let input = """ - switch result { - case let .success(value): - switch result { - case .success: - print("success") - case .value: - print("value") - } - - case .failure: - guard self.bar else { - print(self.bar) - return - } - print(self.bar) - } - """ - - testFormatting(for: input, rule: FormatRules.sortSwitchCases, exclude: ["redundantSelf"]) - } - - func testSortedSwitchCaseMultilineWithOneComment() { - let input = """ - switch self { - case let .type, // something - let .conditionalCompilation: - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, - let .type: // something - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) - } - - func testSortedSwitchCaseMultilineWithComments() { - let input = """ - switch self { - case let .type, // typeComment - let .conditionalCompilation: // conditionalCompilationComment - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, // conditionalCompilationComment - let .type: // typeComment - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, exclude: ["indent"]) - } - - func testSortedSwitchCaseMultilineWithCommentsAndMoreThanOneCasePerLine() { - let input = """ - switch self { - case let .type, // typeComment - let .type1, .type2, - let .conditionalCompilation: // conditionalCompilationComment - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, // conditionalCompilationComment - let .type, // typeComment - let .type1, - .type2: - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) - } - - func testSortedSwitchCaseMultiline() { - let input = """ - switch self { - case let .type, - let .conditionalCompilation: - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, - let .type: - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) - } - - func testSortedSwitchCaseMultipleAssociatedValues() { - let input = """ - switch self { - case let .b(whatever, whatever2), .a(whatever): - break - } - """ - let output = """ - switch self { - case .a(whatever), let .b(whatever, whatever2): - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - func testSortedSwitchCaseOneLineWithoutSpaces() { - let input = """ - switch self { - case .b,.a: - break - } - """ - let output = """ - switch self { - case .a,.b: - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases", "spaceAroundOperators"]) - } - - func testSortedSwitchCaseLet() { - let input = """ - switch self { - case let .b(whatever), .a(whatever): - break - } - """ - let output = """ - switch self { - case .a(whatever), let .b(whatever): - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - func testSortedSwitchCaseOneCaseDoesNothing() { - let input = """ - switch self { - case "a": - break - } - """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases) - } - - func testSortedSwitchStrings() { - let input = """ - switch self { - case "GET", "POST", "PUT", "DELETE": - break - } - """ - let output = """ - switch self { - case "DELETE", "GET", "POST", "PUT": - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - func testSortedSwitchWhereConditionNotLastCase() { - let input = """ - switch self { - case .b, .c, .a where isTrue: - break - } - """ - testFormatting(for: input, - rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - func testSortedSwitchWhereConditionLastCase() { - let input = """ - switch self { - case .b, .c where isTrue, .a: - break - } - """ - let output = """ - switch self { - case .a, .b, .c where isTrue: - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - func testSortNumericSwitchCases() { - let input = """ - switch foo { - case 12, 3, 5, 7, 8, 10, 1: - break - } - """ - let output = """ - switch foo { - case 1, 3, 5, 7, 8, 10, 12: - break - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - func testSortedSwitchTuples() { - let input = """ - switch foo { - case (.foo, _), - (.bar, _), - (.baz, _), - (_, .foo): - } - """ - let output = """ - switch foo { - case (_, .foo), - (.bar, _), - (.baz, _), - (.foo, _): - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) - } - - func testSortedSwitchTuples2() { - let input = """ - switch self { - case (.quux, .bar), - (_, .foo), - (_, .bar), - (_, .baz), - (.foo, .bar): - } - """ - let output = """ - switch self { - case (_, .bar), - (_, .baz), - (_, .foo), - (.foo, .bar), - (.quux, .bar): - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) - } - - func testSortSwitchCasesShortestFirst() { - let input = """ - switch foo { - case let .fooAndBar(baz, quux), - let .foo(baz): - } - """ - let output = """ - switch foo { - case let .foo(baz), - let .fooAndBar(baz, quux): - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) - } - - func testSortHexLiteralCasesInAscendingOrder() { - let input = """ - switch value { - case 0x30 ... 0x39, // 0-9 - 0x0300 ... 0x036F, - 0x1DC0 ... 0x1DFF, - 0x20D0 ... 0x20FF, - 0xFE20 ... 0xFE2F: - return true - default: - return false - } - """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases) - } - - func testMixedOctalHexIntAndBinaryLiteralCasesInAscendingOrder() { - let input = """ - switch value { - case 0o3, - 0x20, - 110, - 0b1111110: - return true - default: - return false - } - """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases) - } - - func testSortSwitchCasesNoUnwrapReturn() { - let input = """ - switch self { - case .b, .a, .c, .e, .d: - return nil - } - """ - let output = """ - switch self { - case .a, .b, .c, .d, .e: - return nil - } - """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, - exclude: ["wrapSwitchCases"]) - } - - // MARK: - modifierOrder - - func testVarModifiersCorrected() { - let input = "unowned private static var foo" - let output = "private unowned static var foo" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) - } - - func testPrivateSetModifierNotMangled() { - let input = "private(set) public weak lazy var foo" - let output = "public private(set) lazy weak var foo" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) - } - - func testUnownedUnsafeModifierNotMangled() { - let input = "unowned(unsafe) lazy var foo" - let output = "lazy unowned(unsafe) var foo" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) - } - - func testPrivateRequiredStaticFuncModifiers() { - let input = "required static private func foo()" - let output = "private required static func foo()" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) - } - - func testPrivateConvenienceInit() { - let input = "convenience private init()" - let output = "private convenience init()" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) - } - - func testSpaceInModifiersLeftIntact() { - let input = "weak private(set) /* read-only */\npublic var" - let output = "public private(set) /* read-only */\nweak var" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) - } - - func testSpaceInModifiersLeftIntact2() { - let input = "nonisolated(unsafe) public var foo: String" - let output = "public nonisolated(unsafe) var foo: String" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) - } - - func testPrefixModifier() { - let input = "prefix public static func - (rhs: Foo) -> Foo" - let output = "public static prefix func - (rhs: Foo) -> Foo" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) - } - - func testModifierOrder() { - let input = "override public var foo: Int { 5 }" - let output = "public override var foo: Int { 5 }" - let options = FormatOptions(modifierOrder: ["public", "override"]) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) - } - - func testConsumingModifierOrder() { - let input = "consuming public func close()" - let output = "public consuming func close()" - let options = FormatOptions(modifierOrder: ["public", "consuming"]) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options, exclude: ["noExplicitOwnership"]) - } - - func testNoConfusePostfixIdentifierWithKeyword() { - let input = "var foo = .postfix\noverride init() {}" - testFormatting(for: input, rule: FormatRules.modifierOrder) - } - - func testNoConfusePostfixIdentifierWithKeyword2() { - let input = "var foo = postfix\noverride init() {}" - testFormatting(for: input, rule: FormatRules.modifierOrder) - } - - func testNoConfuseCaseWithModifier() { - let input = """ - enum Foo { - case strong - case weak - public init() {} - } - """ - testFormatting(for: input, rule: FormatRules.modifierOrder) - } - - // MARK: - sortDeclarations - - func testSortEnumBody() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsellB - case fooFeature( - fooConfiguration: Foo, - barConfiguration: Bar - ) - case barFeature // Trailing comment -- bar feature - /// Leading comment -- upsell A - case upsellA( - fooConfiguration: Foo, - barConfiguration: Bar - ) - } - - enum NextType { - case foo - case bar - } - """ - - let output = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature // Trailing comment -- bar feature - case fooFeature( - fooConfiguration: Foo, - barConfiguration: Bar - ) - /// Leading comment -- upsell A - case upsellA( - fooConfiguration: Foo, - barConfiguration: Bar - ) - case upsellB - } - - enum NextType { - case foo - case bar - } - """ - - testFormatting(for: input, output, rule: FormatRules.sortDeclarations) - } - - func testSortEnumBodyWithOnlyOneCase() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsellB - } - """ - - testFormatting(for: input, rule: FormatRules.sortDeclarations) - } - - func testSortEnumBodyWithoutCase() { - let input = """ - // swiftformat:sort - enum FeatureFlags {} - """ - - testFormatting(for: input, rule: FormatRules.sortDeclarations) - } - - func testNoSortUnannotatedType() { - let input = """ - enum FeatureFlags { - case upsellB - case fooFeature - case barFeature - case upsellA - } - """ - - testFormatting(for: input, rule: FormatRules.sortDeclarations) - } - - func testPreservesSortedBody() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - case upsellB - } - """ - - testFormatting(for: input, rule: FormatRules.sortDeclarations) - } - - func testSortsTypeBody() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsellB - case fooFeature - case barFeature - case upsellA - } - """ - - let output = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - case upsellB - } - """ - - testFormatting(for: input, output, rule: FormatRules.sortDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - func testSortClassWithMixedDeclarationTypes() { - let input = """ - // swiftformat:sort - class Foo { - let quuxProperty = Quux() - let barProperty = Bar() - - var fooComputedProperty: Foo { - Foo() - } - - func baazFunction() -> Baaz { - Baaz() - } - } - """ - - let output = """ - // swiftformat:sort - class Foo { - func baazFunction() -> Baaz { - Baaz() - } - let barProperty = Bar() - - var fooComputedProperty: Foo { - Foo() - } - - let quuxProperty = Quux() - } - """ - - testFormatting(for: input, [output], - rules: [FormatRules.sortDeclarations, FormatRules.consecutiveBlankLines], - exclude: ["blankLinesBetweenScopes", "propertyType"]) - } - - func testSortBetweenDirectiveCommentsInType() { - let input = """ - enum FeatureFlags { - // swiftformat:sort:begin - case upsellB - case fooFeature - case barFeature - case upsellA - // swiftformat:sort:end - - var anUnsortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - enum FeatureFlags { - // swiftformat:sort:begin - case barFeature - case fooFeature - case upsellA - case upsellB - // swiftformat:sort:end - - var anUnsortedProperty: Foo { - Foo() - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.sortDeclarations) - } - - func testSortTopLevelDeclarations() { - let input = """ - let anUnsortedGlobal = 0 - - // swiftformat:sort:begin - let sortThisGlobal = 1 - public let thisGlobalIsSorted = 2 - private let anotherSortedGlobal = 5 - let sortAllOfThem = 8 - // swiftformat:sort:end - - let anotherUnsortedGlobal = 9 - """ - - let output = """ - let anUnsortedGlobal = 0 - - // swiftformat:sort:begin - private let anotherSortedGlobal = 5 - let sortAllOfThem = 8 - let sortThisGlobal = 1 - public let thisGlobalIsSorted = 2 - // swiftformat:sort:end - - let anotherUnsortedGlobal = 9 - """ - - testFormatting(for: input, output, rule: FormatRules.sortDeclarations) - } - - func testDoesntConflictWithOrganizeDeclarations() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - case upsellB - - // MARK: Internal - - var anUnsortedProperty: Foo { - Foo() - } - - var unsortedProperty: Foo { - Foo() - } - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations) - } - - func testSortsWithinOrganizeDeclarations() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - - case upsellB - - // MARK: Internal - - var aSortedProperty: Foo { - Foo() - } - - var sortedProperty: Foo { - Foo() - } - - } - """ - - testFormatting(for: input, [output], - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], - exclude: ["blankLinesAtEndOfScope"]) - } - - func testSortsWithinOrganizeDeclarationsByClassName() { - let input = """ - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - - case upsellB - - // MARK: Internal - - var aSortedProperty: Foo { - Foo() - } - - var sortedProperty: Foo { - Foo() - } - - } - """ - - testFormatting(for: input, [output], - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], - options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), - exclude: ["blankLinesAtEndOfScope"]) - } - - func testSortsWithinOrganizeDeclarationsByPartialClassName() { - let input = """ - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - - case upsellB - - // MARK: Internal - - var aSortedProperty: Foo { - Foo() - } - - var sortedProperty: Foo { - Foo() - } - - } - """ - - testFormatting(for: input, [output], - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], - options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), - exclude: ["blankLinesAtEndOfScope"]) - } - - func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() { - let input = """ - /// Comment - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - testFormatting(for: input, - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], - options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), - exclude: ["blankLinesAtEndOfScope"]) - } - - func testSortDeclarationsSortsByNamePattern() { - let input = """ - enum Namespace {} - - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let output = """ - enum Namespace {} - - extension Namespace { - static let baaz = "baaz" - public static let bar = "bar" - static let foo = "foo" - } - """ - - let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Namespace"]) - testFormatting(for: input, [output], rules: [FormatRules.sortDeclarations, FormatRules.blankLinesBetweenScopes], options: options) - } - - func testSortDeclarationsWontSortByNamePatternInComment() { - let input = """ - enum Namespace {} - - /// Constants - /// enum Constants - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Constants"]) - testFormatting(for: input, rules: [FormatRules.sortDeclarations, FormatRules.blankLinesBetweenScopes], options: options) - } - - func testSortDeclarationsUsesLocalizedCompare() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsella - case upsellA - case upsellb - case upsellB - } - """ - - testFormatting(for: input, rule: FormatRules.sortDeclarations) - } - - func testOrganizeDeclarationsSortUsesLocalizedCompare() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsella - case upsellA - case upsellb - case upsellB - } - """ - - testFormatting(for: input, rule: FormatRules.organizeDeclarations) - } - - func testSortDeclarationsSortsExtensionBody() { - let input = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let output = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - static let baaz = "baaz" - public static let bar = "bar" - static let foo = "foo" - } - """ - - // organizeTypes doesn't include "extension". So even though the - // organizeDeclarations rule is enabled, the extension should be - // sorted by the sortDeclarations rule. - let options = FormatOptions(organizeTypes: ["class"]) - testFormatting(for: input, [output], rules: [FormatRules.sortDeclarations, FormatRules.organizeDeclarations], options: options) - } - - func testOrganizeDeclarationsSortsExtensionBody() { - let input = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let output = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - - // MARK: Public - - public static let bar = "bar" - - // MARK: Internal - - static let baaz = "baaz" - static let foo = "foo" - } - """ - - let options = FormatOptions(organizeTypes: ["extension"]) - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, options: options, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - func testOrganizeDeclarationsContainingNonisolated() { - let input = """ - class Test { - public static func test1() {} - - private nonisolated(unsafe) static var test3: (( - _ arg1: Bool, - _ arg2: Int - ) -> Bool)? - - static func test2() {} - } - """ - let output = """ - class Test { - - // MARK: Public - - public static func test1() {} - - // MARK: Internal - - static func test2() {} - - // MARK: Private - - private nonisolated(unsafe) static var test3: (( - _ arg1: Bool, - _ arg2: Int - ) -> Bool)? - - } - """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - func testSortStructPropertiesWithAttributes() { - let input = """ - // swiftformat:sort - struct BookReaderView { - @Namespace private var animation - @State private var animationContent: Bool = false - @State private var offsetY: CGFloat = 0 - @Bindable var model: Book - @Query( - filter: #Predicate { $0.progress_ < 1 }, - sort: \\.updatedAt_, - order: .reverse - ) private var incompleteTextContents: [TextContent] - } - """ - let output = """ - // swiftformat:sort - struct BookReaderView { - - // MARK: Internal - - @Bindable var model: Book - - // MARK: Private - - @Namespace private var animation - @State private var animationContent: Bool = false - @Query( - filter: #Predicate { $0.progress_ < 1 }, - sort: \\.updatedAt_, - order: .reverse - ) private var incompleteTextContents: [TextContent] - @State private var offsetY: CGFloat = 0 - } - """ - let options = FormatOptions(indent: " ", organizeTypes: ["struct"]) - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, - options: options, exclude: ["blankLinesAtStartOfScope"]) - } - - func testDoesntAddUnexpectedBlankLinesDueToBlankLinesWithSpaces() { - // The blank lines in this input code are indented with four spaces. - // Done using string interpolation in the input code to make this - // more clear, and to prevent the spaces from being removed automatically. - let input = """ - public class TestClass { - var variable01 = 1 - var variable02 = 2 - var variable03 = 3 - var variable04 = 4 - var variable05 = 5 - \(" ") - public func foo() {} - \(" ") - func bar() {} - \(" ") - private func baz() {} - } - """ - - let output = """ - public class TestClass { - - // MARK: Public - - public func foo() {} - \(" ") - // MARK: Internal - - var variable01 = 1 - var variable02 = 2 - var variable03 = 3 - var variable04 = 4 - var variable05 = 5 - \(" ") - func bar() {} - \(" ") - // MARK: Private - - private func baz() {} - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "consecutiveBlankLines", "trailingSpace", "consecutiveSpaces", "indent"] - ) - } - - // MARK: - sortTypealiases - - func testSortSingleLineTypealias() { - let input = """ - typealias Placeholders = Foo & Bar & Quux & Baaz - """ - - let output = """ - typealias Placeholders = Baaz & Bar & Foo & Quux - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortMultilineTypealias() { - let input = """ - typealias Placeholders = Foo & Bar - & Quux & Baaz - """ - - let output = """ - typealias Placeholders = Baaz & Bar - & Foo & Quux - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortMultilineTypealiasWithComments() { - let input = """ - typealias Placeholders = Foo & Bar // Comment about Bar - // Comment about Quux - & Quux & Baaz // Comment about Baaz - """ - - let output = """ - typealias Placeholders = Baaz // Comment about Baaz - & Bar // Comment about Bar - & Foo - // Comment about Quux - & Quux - """ - - testFormatting(for: input, [output], rules: [FormatRules.sortTypealiases, FormatRules.indent, FormatRules.trailingSpace]) - } - - func testSortWrappedMultilineTypealias1() { - let input = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let output = """ - typealias Dependencies = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortWrappedMultilineTypealias2() { - let input = """ - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let output = """ - typealias Dependencies - = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortWrappedMultilineTypealiasWithComments() { - let input = """ - typealias Dependencies - // Comment about FooProviding - = FooProviding - // Comment about BarProviding - & BarProviding - & QuuxProviding // Comment about QuuxProviding - // Comment about BaazProviding - & BaazProviding // Comment about BaazProviding - """ - - let output = """ - typealias Dependencies - // Comment about BaazProviding - = BaazProviding // Comment about BaazProviding - // Comment about BarProviding - & BarProviding - // Comment about FooProviding - & FooProviding - & QuuxProviding // Comment about QuuxProviding - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortTypealiasesWithAssociatedTypes() { - let input = """ - typealias Collections - = Collection - & Collection - & Collection - & Collection - """ - - let output = """ - typealias Collections - = Collection - & Collection - & Collection - & Collection - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortTypeAliasesAndRemoveDuplicates() { - let input = """ - typealias Placeholders = Foo & Bar & Quux & Baaz & Bar - - typealias Dependencies1 - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - & FooProviding - - typealias Dependencies2 - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - & BaazProviding - """ - - let output = """ - typealias Placeholders = Baaz & Bar & Foo & Quux - - typealias Dependencies1 - = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - - typealias Dependencies2 - = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - """ - - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) - } - - func testSortSingleSwiftUIPropertyWrapper() { - let input = """ - struct ContentView: View { - - private var label: String - - @State - private var isOn: Bool = false - - private var foo = true - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - @ViewBuilder - var body: some View { - toggle - } - } - """ - - let output = """ - struct ContentView: View { - - // MARK: Internal - - @ViewBuilder - var body: some View { - toggle - } - - // MARK: Private - - @State - private var isOn: Bool = false - - private var label: String - - private var foo = true - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testSortMultipleSwiftUIPropertyWrappers() { - let input = """ - struct ContentView: View { - - let foo: Foo - @State var bar: Bar - let baaz: Baaz - @State var quux: Quux - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - @ViewBuilder - var body: some View { - toggle - } - } - """ - - let output = """ - struct ContentView: View { - - // MARK: Internal - - @State var bar: Bar - @State var quux: Quux - - let foo: Foo - let baaz: Baaz - - @ViewBuilder - var body: some View { - toggle - } - - // MARK: Private - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testSortSwiftUIPropertyWrappersWithDifferentVisibility() { - let input = """ - struct ContentView: View { - - let foo: Foo - @State private var bar: Bar - private let baaz: Baaz - @Binding var isOn: Bool - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - @ViewBuilder - var body: some View { - toggle - } - } - """ - - let output = """ - struct ContentView: View { - - // MARK: Internal - - @Binding var isOn: Bool - - let foo: Foo - - @ViewBuilder - var body: some View { - toggle - } - - // MARK: Private - - @State private var bar: Bar - - private let baaz: Baaz - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } - - func testSortSwiftUIPropertyWrappersWithArguments() { - let input = """ - struct ContentView: View { - - let foo: Foo - @Environment(\\.colorScheme) var colorScheme - let baaz: Baaz - @Environment(\\.quux) let quux: Quux - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - @ViewBuilder - var body: some View { - toggle - } - } - """ - - let output = """ - struct ContentView: View { - - // MARK: Internal - - @Environment(\\.colorScheme) var colorScheme - @Environment(\\.quux) let quux: Quux - - let foo: Foo - let baaz: Baaz - - @ViewBuilder - var body: some View { - toggle - } - - // MARK: Private - - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - } - - } - """ - - testFormatting( - for: input, output, - rule: FormatRules.organizeDeclarations, - options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] - ) - } -} diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift deleted file mode 100644 index 5cc51557..00000000 --- a/Tests/RulesTests+Redundancy.swift +++ /dev/null @@ -1,10939 +0,0 @@ -// -// RulesTests+Redundancy.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class RedundancyTests: RulesTests { - // MARK: - redundantBreak - - func testRedundantBreaksRemoved() { - let input = """ - switch x { - case foo: - print("hello") - break - case bar: - print("world") - break - default: - print("goodbye") - break - } - """ - let output = """ - switch x { - case foo: - print("hello") - case bar: - print("world") - default: - print("goodbye") - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantBreak) - } - - func testBreakInEmptyCaseNotRemoved() { - let input = """ - switch x { - case foo: - break - case bar: - break - default: - break - } - """ - testFormatting(for: input, rule: FormatRules.redundantBreak) - } - - func testConditionalBreakNotRemoved() { - let input = """ - switch x { - case foo: - if bar { - break - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantBreak) - } - - func testBreakAfterSemicolonNotMangled() { - let input = """ - switch foo { - case 1: print(1); break - } - """ - let output = """ - switch foo { - case 1: print(1); - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantBreak, exclude: ["semicolons"]) - } - - // MARK: - redundantExtensionACL - - func testPublicExtensionMemberACLStripped() { - let input = """ - public extension Foo { - public var bar: Int { 5 } - private static let baz = "baz" - public func quux() {} - } - """ - let output = """ - public extension Foo { - var bar: Int { 5 } - private static let baz = "baz" - func quux() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantExtensionACL) - } - - func testPrivateExtensionMemberACLNotStrippedUnlessFileprivate() { - let input = """ - private extension Foo { - fileprivate var bar: Int { 5 } - private static let baz = "baz" - fileprivate func quux() {} - } - """ - let output = """ - private extension Foo { - var bar: Int { 5 } - private static let baz = "baz" - func quux() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantExtensionACL) - } - - // MARK: - redundantFileprivate - - func testFileScopeFileprivateVarChangedToPrivate() { - let input = """ - fileprivate var foo = "foo" - """ - let output = """ - private var foo = "foo" - """ - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate) - } - - func testFileScopeFileprivateVarNotChangedToPrivateIfFragment() { - let input = """ - fileprivate var foo = "foo" - """ - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherType() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - } - """ - let output = """ - struct Foo { - private var foo = "foo" - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherTypeAndFileIncludesImports() { - let input = """ - import Foundation - - struct Foo { - fileprivate var foo = "foo" - } - """ - let output = """ - import Foundation - - struct Foo { - private var foo = "foo" - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAnotherType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - struct Bar { - func bar() { - print(Foo().foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromSubclass() { - let input = """ - class Foo { - fileprivate func foo() {} - } - - class Bar: Foo { - func bar() { - return foo() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAFunction() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - func getFoo() -> String { - return Foo().foo - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAConstant() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - let kFoo = Foo().foo - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - var kFoo: String { return Foo().foo } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromCode() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - print(Foo().foo) - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAClosure() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - print({ Foo().foo }()) - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["redundantClosure"]) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAnExtensionOnAnotherType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - extension Bar { - func bar() { - print(Foo().foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfAccessedFromAnExtensionOnSameType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - extension Foo { - func bar() { - print(foo) - } - } - """ - let output = """ - struct Foo { - private let foo = "foo" - } - - extension Foo { - func bar() { - print(foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfAccessedViaSelfFromAnExtensionOnSameType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - extension Foo { - func bar() { - print(self.foo) - } - } - """ - let output = """ - struct Foo { - private let foo = "foo" - } - - extension Foo { - func bar() { - print(self.foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options, - exclude: ["redundantSelf"]) - } - - func testFileprivateMultiLetNotChangedToPrivateIfAccessedOutsideType() { - let input = """ - struct Foo { - fileprivate let foo = "foo", bar = "bar" - } - - extension Foo { - func bar() { - print(foo) - } - } - - extension Bar { - func bar() { - print(Foo().bar) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInitChangedToPrivateIfConstructorNotCalledOutsideType() { - let input = """ - struct Foo { - fileprivate init() {} - } - """ - let output = """ - struct Foo { - private init() {} - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType() { - let input = """ - struct Foo { - fileprivate init() {} - } - - let foo = Foo() - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) - } - - func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { - let input = """ - class Foo { - fileprivate init() {} - } - - struct Bar { - let foo = Foo() - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) - } - - func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { - let input = """ - struct Foo { - fileprivate let bar: String - } - - let foo = Foo(bar: "test") - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) - } - - func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { - let input = """ - class Foo { - fileprivate let bar: String - } - - let foo = Foo() - """ - let output = """ - class Foo { - private let bar: String - } - - let foo = Foo() - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) - } - - func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { - let input = """ - private class Foo: Equatable { - fileprivate static func == (_: Foo, _: Foo) -> Bool { - return true - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInnerTypeNotChangedToPrivate() { - let input = """ - struct Foo { - fileprivate enum Bar { - case a, b - } - - fileprivate let bar: Bar - } - - func foo(foo: Foo) { - print(foo.bar) - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, - rule: FormatRules.redundantFileprivate, - options: options, - exclude: ["wrapEnumCases"]) - } - - func testFileprivateClassTypeMemberNotChangedToPrivate() { - let input = """ - class Foo { - fileprivate class var bar = "bar" - } - - func foo() { - print(Foo.bar) - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testOverriddenFileprivateInitNotChangedToPrivate() { - let input = """ - class Foo { - fileprivate init() {} - } - - class Bar: Foo, Equatable { - override public init() { - super.init() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testNonOverriddenFileprivateInitChangedToPrivate() { - let input = """ - class Foo { - fileprivate init() {} - } - - class Bar: Baz { - override public init() { - super.init() - } - } - """ - let output = """ - class Foo { - private init() {} - } - - class Bar: Baz { - override public init() { - super.init() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInitNotChangedToPrivateWhenUsingTypeInferredInits() { - let input = """ - struct Example { - fileprivate init() {} - } - - enum Namespace { - static let example: Example = .init() - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) - } - - func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { - let input = """ - private struct Foo {} - - public struct Bar { - fileprivate let consumeFoo: (Foo) -> Void - } - - public func makeBar() -> Bar { - Bar { _ in } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnContainingType() { - let input = """ - extension Foo.Bar { - fileprivate init() {} - } - - extension Foo { - func baz() -> Foo.Bar { - return Bar() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnNestedType() { - let input = """ - extension Foo { - fileprivate init() {} - } - - extension Foo.Bar { - func baz() -> Foo { - return Foo() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromSubclass() { - let input = """ - class Foo: Bar { - func quux() { - baz() - } - } - - extension Bar { - fileprivate func baz() {} - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInitNotChangedToPrivateWhenAccessedFromSubclass() { - let input = """ - public class Foo { - fileprivate init() {} - } - - private class Bar: Foo { - init(something: String) { - print(something) - super.init() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromExtensionOnSubclass() { - let input = """ - class Foo: Bar {} - - extension Foo { - func quux() { - baz() - } - } - - extension Bar { - fileprivate func baz() {} - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateVarWithPropertWrapperNotChangedToPrivateIfAccessedFromSubclass() { - let input = """ - class Foo { - @Foo fileprivate var foo = 5 - } - - class Bar: Foo { - func bar() { - return $foo - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile() { - let input = """ - extension [String] { - fileprivate func fileprivateMember() {} - } - - extension Namespace { - func testCanAccessFileprivateMember() { - ["string", "array"].fileprivateMember() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) - } - - func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile2() { - let input = """ - extension Array { - fileprivate func fileprivateMember() {} - } - - extension Namespace { - func testCanAccessFileprivateMember() { - ["string", "array"].fileprivateMember() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, - options: options, exclude: ["typeSugar"]) - } - - // MARK: - redundantGet - - func testRemoveSingleLineIsolatedGet() { - let input = "var foo: Int { get { return 5 } }" - let output = "var foo: Int { return 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantGet) - } - - func testRemoveMultilineIsolatedGet() { - let input = "var foo: Int {\n get {\n return 5\n }\n}" - let output = "var foo: Int {\n return 5\n}" - testFormatting(for: input, [output], rules: [FormatRules.redundantGet, FormatRules.indent]) - } - - func testNoRemoveMultilineGetSet() { - let input = "var foo: Int {\n get { return 5 }\n set { foo = newValue }\n}" - testFormatting(for: input, rule: FormatRules.redundantGet) - } - - func testNoRemoveAttributedGet() { - let input = "var enabled: Bool { @objc(isEnabled) get { true } }" - testFormatting(for: input, rule: FormatRules.redundantGet) - } - - func testRemoveSubscriptGet() { - let input = "subscript(_ index: Int) {\n get {\n return lookup(index)\n }\n}" - let output = "subscript(_ index: Int) {\n return lookup(index)\n}" - testFormatting(for: input, [output], rules: [FormatRules.redundantGet, FormatRules.indent]) - } - - func testGetNotRemovedInFunction() { - let input = "func foo() {\n get {\n self.lookup(index)\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantGet) - } - - func testEffectfulGetNotRemoved() { - let input = """ - var foo: Int { - get async throws { - try await getFoo() - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantGet) - } - - // MARK: - redundantInit - - func testRemoveRedundantInit() { - let input = "[1].flatMap { String.init($0) }" - let output = "[1].flatMap { String($0) }" - testFormatting(for: input, output, rule: FormatRules.redundantInit) - } - - func testRemoveRedundantInit2() { - let input = "[String.self].map { Type in Type.init(foo: 1) }" - let output = "[String.self].map { Type in Type(foo: 1) }" - testFormatting(for: input, output, rule: FormatRules.redundantInit) - } - - func testRemoveRedundantInit3() { - let input = "String.init(\"text\")" - let output = "String(\"text\")" - testFormatting(for: input, output, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitInSuperCall() { - let input = "class C: NSObject { override init() { super.init() } }" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitInSelfCall() { - let input = "struct S { let n: Int }; extension S { init() { self.init(n: 1) } }" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitWhenPassedAsFunction() { - let input = "[1].flatMap(String.init)" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitWhenUsedOnMetatype() { - let input = "[String.self].map { type in type.init(1) }" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitWhenUsedOnImplicitClosureMetatype() { - let input = "[String.self].map { $0.init(1) }" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitWhenUsedOnPossibleMetatype() { - let input = "let something = Foo.bar.init()" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testDontRemoveInitWithExplicitSignature() { - let input = "[String.self].map(Foo.init(bar:))" - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testRemoveInitWithOpenParenOnFollowingLine() { - let input = """ - var foo: Foo { - Foo.init - ( - bar: bar, - baaz: baaz - ) - } - """ - let output = """ - var foo: Foo { - Foo( - bar: bar, - baaz: baaz - ) - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantInit) - } - - func testNoRemoveInitWithOpenParenOnFollowingLineAfterComment() { - let input = """ - var foo: Foo { - Foo.init // foo - ( - bar: bar, - baaz: baaz - ) - } - """ - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testNoRemoveInitForLowercaseType() { - let input = """ - let foo = bar.init() - """ - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testNoRemoveInitForLocalLetType() { - let input = """ - let Foo = Foo.self - let foo = Foo.init() - """ - testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["propertyType"]) - } - - func testNoRemoveInitForLocalLetType2() { - let input = """ - let Foo = Foo.self - if x { - return Foo.init(x) - } else { - return Foo.init(y) - } - """ - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testNoRemoveInitInsideIfdef() { - let input = """ - func myFunc() async throws -> String { - #if DEBUG - .init("foo") - #else - "" - #endif - } - """ - testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["indent"]) - } - - func testNoRemoveInitInsideIfdef2() { - let input = """ - func myFunc() async throws(Foo) -> String { - #if DEBUG - .init("foo") - #else - "" - #endif - } - """ - testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["indent"]) - } - - func testRemoveInitAfterCollectionLiterals() { - let input = """ - let array = [String].init() - let arrayElement = [String].Element.init() - let nestedArray = [[String]].init() - let tupleArray = [(key: String, value: Int)].init() - let dictionary = [String: Int].init() - """ - let output = """ - let array = [String]() - let arrayElement = [String].Element() - let nestedArray = [[String]]() - let tupleArray = [(key: String, value: Int)]() - let dictionary = [String: Int]() - """ - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["propertyType"]) - } - - func testPreservesInitAfterTypeOfCall() { - let input = """ - type(of: oldViewController).init() - """ - - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testRemoveInitAfterOptionalType() { - let input = """ - let someOptional = String?.init("Foo") - // (String!.init("Foo") isn't valid Swift code, so we don't test for it) - """ - let output = """ - let someOptional = String?("Foo") - // (String!.init("Foo") isn't valid Swift code, so we don't test for it) - """ - - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["propertyType"]) - } - - func testPreservesTryBeforeInit() { - let input = """ - let throwing: Foo = try .init() - let throwingOptional1: Foo = try? .init() - let throwingOptional2: Foo = try! .init() - """ - - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - func testRemoveInitAfterGenericType() { - let input = """ - let array = Array.init() - let dictionary = Dictionary.init() - let atomicDictionary = Atomic<[String: Int]>.init() - """ - let output = """ - let array = Array() - let dictionary = Dictionary() - let atomicDictionary = Atomic<[String: Int]>() - """ - - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["typeSugar", "propertyType"]) - } - - func testPreserveNonRedundantInitInTernaryOperator() { - let input = """ - let bar: Bar = (foo.isBar && bar.isBaaz) ? .init() : nil - """ - testFormatting(for: input, rule: FormatRules.redundantInit) - } - - // MARK: - redundantLetError - - func testCatchLetError() { - let input = "do {} catch let error {}" - let output = "do {} catch {}" - testFormatting(for: input, output, rule: FormatRules.redundantLetError) - } - - func testCatchLetErrorWithTypedThrows() { - let input = "do throws(Foo) {} catch let error {}" - let output = "do throws(Foo) {} catch {}" - testFormatting(for: input, output, rule: FormatRules.redundantLetError) - } - - // MARK: - redundantObjc - - func testRedundantObjcRemovedFromBeforeOutlet() { - let input = "@objc @IBOutlet var label: UILabel!" - let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testRedundantObjcRemovedFromAfterOutlet() { - let input = "@IBOutlet @objc var label: UILabel!" - let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testRedundantObjcRemovedFromLineBeforeOutlet() { - let input = "@objc\n@IBOutlet var label: UILabel!" - let output = "\n@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testRedundantObjcCommentNotRemoved() { - let input = "@objc /// an outlet\n@IBOutlet var label: UILabel!" - let output = "/// an outlet\n@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testObjcNotRemovedFromNSCopying() { - let input = "@objc @NSCopying var foo: String!" - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testRenamedObjcNotRemoved() { - let input = "@IBOutlet @objc(uiLabel) var label: UILabel!" - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testObjcRemovedOnObjcMembersClass() { - let input = """ - @objcMembers class Foo: NSObject { - @objc var foo: String - } - """ - let output = """ - @objcMembers class Foo: NSObject { - var foo: String - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testObjcRemovedOnRenamedObjcMembersClass() { - let input = """ - @objcMembers @objc(OCFoo) class Foo: NSObject { - @objc var foo: String - } - """ - let output = """ - @objcMembers @objc(OCFoo) class Foo: NSObject { - var foo: String - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testObjcNotRemovedOnNestedClass() { - let input = """ - @objcMembers class Foo: NSObject { - @objc class Bar: NSObject {} - } - """ - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testObjcNotRemovedOnRenamedPrivateNestedClass() { - let input = """ - @objcMembers class Foo: NSObject { - @objc private class Bar: NSObject {} - } - """ - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testObjcNotRemovedOnNestedEnum() { - let input = """ - @objcMembers class Foo: NSObject { - @objc enum Bar: Int {} - } - """ - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testObjcRemovedOnObjcExtensionVar() { - let input = """ - @objc extension Foo { - @objc var foo: String {} - } - """ - let output = """ - @objc extension Foo { - var foo: String {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testObjcRemovedOnObjcExtensionFunc() { - let input = """ - @objc extension Foo { - @objc func foo() -> String {} - } - """ - let output = """ - @objc extension Foo { - func foo() -> String {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - func testObjcNotRemovedOnPrivateFunc() { - let input = """ - @objcMembers class Foo: NSObject { - @objc private func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testObjcNotRemovedOnFileprivateFunc() { - let input = """ - @objcMembers class Foo: NSObject { - @objc fileprivate func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.redundantObjc) - } - - func testObjcRemovedOnPrivateSetFunc() { - let input = """ - @objcMembers class Foo: NSObject { - @objc private(set) func bar() {} - } - """ - let output = """ - @objcMembers class Foo: NSObject { - private(set) func bar() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) - } - - // MARK: - redundantType - - func testVarRedundantTypeRemoval() { - let input = "var view: UIView = UIView()" - let output = "var view = UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testVarRedundantArrayTypeRemoval() { - let input = "var foo: [String] = [String]()" - let output = "var foo = [String]()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testVarRedundantDictionaryTypeRemoval() { - let input = "var foo: [String: Int] = [String: Int]()" - let output = "var foo = [String: Int]()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testLetRedundantGenericTypeRemoval() { - let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" - let output = "let relay = BehaviourRelay(value: nil)" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testVarNonRedundantTypeDoesNothing() { - let input = "var view: UIView = UINavigationBar()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testLetRedundantTypeRemoval() { - let input = "let view: UIView = UIView()" - let output = "let view = UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testLetNonRedundantTypeDoesNothing() { - let input = "let view: UIView = UINavigationBar()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testTypeNoRedundancyDoesNothing() { - let input = "let foo: Bar = 5" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testClassTwoVariablesNoRedundantTypeDoesNothing() { - let input = """ - final class LGWebSocketClient: WebSocketClient, WebSocketLibraryDelegate { - var webSocket: WebSocketLibraryProtocol - var timeoutIntervalForRequest: TimeInterval = LGCoreKitConstants.websocketTimeOutTimeInterval - } - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testRedundantTypeRemovedIfValueOnNextLine() { - let input = """ - let view: UIView - = UIView() - """ - let output = """ - let view - = UIView() - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeRemovedIfValueOnNextLine2() { - let input = """ - let view: UIView = - UIView() - """ - let output = """ - let view = - UIView() - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testAllRedundantTypesRemovedInCommaDelimitedDeclaration() { - let input = "var foo: Int = 0, bar: Int = 0" - let output = "var foo = 0, bar = 0" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeRemovalWithComment() { - let input = "var view: UIView /* view */ = UIView()" - let output = "var view /* view */ = UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeRemovalWithComment2() { - let input = "var view: UIView = /* view */ UIView()" - let output = "var view = /* view */ UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testNonRedundantTernaryConditionTypeNotRemoved() { - let input = "let foo: Bar = Bar.baz() ? .bar1 : .bar2" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testTernaryConditionAfterLetNotTreatedAsPartOfExpression() { - let input = """ - let foo: Bar = Bar.baz() - baz ? bar2() : bar2() - """ - let output = """ - let foo = Bar.baz() - baz ? bar2() : bar2() - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testNoRemoveRedundantTypeIfVoid() { - let input = "let foo: Void = Void()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options, exclude: ["void"]) - } - - func testNoRemoveRedundantTypeIfVoid2() { - let input = "let foo: () = ()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options, exclude: ["void"]) - } - - func testNoRemoveRedundantTypeIfVoid3() { - let input = "let foo: [Void] = [Void]()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testNoRemoveRedundantTypeIfVoid4() { - let input = "let foo: Array = Array()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options, exclude: ["typeSugar"]) - } - - func testNoRemoveRedundantTypeIfVoid5() { - let input = "let foo: Void? = Void?.none" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testNoRemoveRedundantTypeIfVoid6() { - let input = "let foo: Optional = Optional.none" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options, exclude: ["typeSugar"]) - } - - func testRedundantTypeWithLiterals() { - let input = """ - let a1: Bool = true - let a2: Bool = false - - let b1: String = "foo" - let b2: String = "\\(b1)" - - let c1: Int = 1 - let c2: Int = 1.0 - - let d1: Double = 3.14 - let d2: Double = 3 - - let e1: [Double] = [3.14] - let e2: [Double] = [3] - - let f1: [String: Int] = ["foo": 5] - let f2: [String: Int?] = ["foo": nil] - """ - let output = """ - let a1 = true - let a2 = false - - let b1 = "foo" - let b2 = "\\(b1)" - - let c1 = 1 - let c2: Int = 1.0 - - let d1 = 3.14 - let d2: Double = 3 - - let e1 = [3.14] - let e2: [Double] = [3] - - let f1 = ["foo": 5] - let f2: [String: Int?] = ["foo": nil] - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypePreservesLiteralRepresentableTypes() { - let input = """ - let a: MyBoolRepresentable = true - let b: MyStringRepresentable = "foo" - let c: MyIntRepresentable = 1 - let d: MyDoubleRepresentable = 3.14 - let e: MyArrayRepresentable = ["bar"] - let f: MyDictionaryRepresentable = ["baz": 1] - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testPreservesTypeWithIfExpressionInSwift5_8() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testPreservesNonRedundantTypeWithIfExpression() { - let input = """ - let foo: Foo = if condition { - Foo("foo") - } else { - FooSubclass("bar") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - func testRedundantTypeWithIfExpression_inferred() { - let input = """ - let foo: Foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let output = """ - let foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - func testRedundantTypeWithIfExpression_explicit() { - let input = """ - let foo: Foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let output = """ - let foo: Foo = if condition { - .init("foo") - } else { - .init("bar") - } - """ - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) - } - - func testRedundantTypeWithNestedIfExpression_inferred() { - let input = """ - let foo: Foo = if condition { - switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - Foo("baaz") - } - } else { - Foo("quux") - } - """ - let output = """ - let foo = if condition { - switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - Foo("baaz") - } - } else { - Foo("quux") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - func testRedundantTypeWithNestedIfExpression_explicit() { - let input = """ - let foo: Foo = if condition { - switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - Foo("baaz") - } - } else { - Foo("quux") - } - """ - let output = """ - let foo: Foo = if condition { - switch condition { - case true: - if condition { - .init("foo") - } else { - .init("bar") - } - - case false: - .init("baaz") - } - } else { - .init("quux") - } - """ - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) - } - - func testRedundantTypeWithLiteralsInIfExpression() { - let input = """ - let foo: String = if condition { - "foo" - } else { - "bar" - } - """ - let output = """ - let foo = if condition { - "foo" - } else { - "bar" - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - // --redundanttype explicit - - func testVarRedundantTypeRemovalExplicitType() { - let input = "var view: UIView = UIView()" - let output = "var view: UIView = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testVarRedundantTypeRemovalExplicitType2() { - let input = "var view: UIView = UIView /* foo */()" - let output = "var view: UIView = .init /* foo */()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["spaceAroundComments", "propertyType"]) - } - - func testLetRedundantGenericTypeRemovalExplicitType() { - let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" - let output = "let relay: BehaviourRelay = .init(value: nil)" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { - let input = "let relay: Foo = Foo\n .default" - let output = "let relay: Foo = \n .default" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["trailingSpace", "propertyType"]) - } - - func testVarNonRedundantTypeDoesNothingExplicitType() { - let input = "var view: UIView = UINavigationBar()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - func testLetRedundantTypeRemovalExplicitType() { - let input = "let view: UIView = UIView()" - let output = "let view: UIView = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { - let input = """ - let view: UIView - = UIView() - """ - let output = """ - let view: UIView - = .init() - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { - let input = """ - let view: UIView = - UIView() - """ - let output = """ - let view: UIView = - .init() - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovalWithCommentExplicitType() { - let input = "var view: UIView /* view */ = UIView()" - let output = "var view: UIView /* view */ = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovalWithComment2ExplicitType() { - let input = "var view: UIView = /* view */ UIView()" - let output = "var view: UIView = /* view */ .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovalWithStaticMember() { - let input = """ - let session: URLSession = URLSession.default - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let output = """ - let session: URLSession = .default - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovalWithStaticFunc() { - let input = """ - let session: URLSession = URLSession.default() - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let output = """ - let session: URLSession = .default() - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeDoesNothingWithChainedMember() { - let input = "let session: URLSession = URLSession.default.makeCopy()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { - let input = "let session: URLSession = URLSession.default.makeCopy()" - let output = "let session: URLSession = .default.makeCopy()" - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeDoesNothingWithChainedMember2() { - let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeDoesNothingWithChainedMember3() { - let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { - let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" - let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeDoesNothingIfLet() { - let input = "if let foo: Foo = Foo() {}" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeDoesNothingGuardLet() { - let input = "guard let foo: Foo = Foo() else {}" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeDoesNothingIfLetAfterComma() { - let input = "if check == true, let foo: Foo = Foo() {}" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeWorksAfterIf() { - let input = """ - if foo {} - let foo: Foo = Foo() - """ - let output = """ - if foo {} - let foo: Foo = .init() - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeIfVoid() { - let input = "let foo: [Void] = [Void]()" - let output = "let foo: [Void] = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testRedundantTypeWithIntegerLiteralNotMangled() { - let input = "let foo: Int = 1.toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeWithFloatLiteralNotMangled() { - let input = "let foo: Double = 1.0.toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeWithArrayLiteralNotMangled() { - let input = "let foo: [Int] = [1].toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeWithBoolLiteralNotMangled() { - let input = "let foo: Bool = false.toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options) - } - - func testRedundantTypeInModelClassNotStripped() { - // See: https://github.com/nicklockwood/SwiftFormat/issues/1649 - let input = """ - @Model - class FooBar { - var created: Date = Date.now - } - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) - } - - // --redundanttype infer-locals-only - - func testRedundantTypeinferLocalsOnly() { - let input = """ - let globalFoo: Foo = Foo() - - struct SomeType { - let instanceFoo: Foo = Foo() - - func method() { - let localFoo: Foo = Foo() - let localString: String = "foo" - } - - let instanceString: String = "foo" - } - - let globalString: String = "foo" - """ - - let output = """ - let globalFoo: Foo = .init() - - struct SomeType { - let instanceFoo: Foo = .init() - - func method() { - let localFoo = Foo() - let localString = "foo" - } - - let instanceString: String = "foo" - } - - let globalString: String = "foo" - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["propertyType"]) - } - - func testClassWithWhereNotMistakenForLocalScope() { - let input = """ - final class Foo where Bar: Equatable { - var isFoo: Bool = false - var fooName: String = "name" - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: FormatRules.redundantType, - options: options) - } - - // MARK: - redundantNilInit - - func testRemoveRedundantNilInit() { - let input = "var foo: Int? = nil\nlet bar: Int? = nil" - let output = "var foo: Int?\nlet bar: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveLetNilInitAfterVar() { - let input = "var foo: Int; let bar: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNonNilInit() { - let input = "var foo: Int? = 0" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testRemoveRedundantImplicitUnwrapInit() { - let input = "var foo: Int! = nil" - let output = "var foo: Int!" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testRemoveMultipleRedundantNilInitsInSameLine() { - let input = "var foo: Int? = nil, bar: Int? = nil" - let output = "var foo: Int?, bar: Int?" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveLazyVarNilInit() { - let input = "lazy var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveLazyPublicPrivateSetVarNilInit() { - let input = "lazy private(set) public var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, options: options, - exclude: ["modifierOrder"]) - } - - func testNoRemoveCodableNilInit() { - let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithPropertyWrapper() { - let input = "@Foo var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithLowercasePropertyWrapper() { - let input = "@foo var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithPropertyWrapperWithArgument() { - let input = "@Foo(bar: baz) var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithLowercasePropertyWrapperWithArgument() { - let input = "@foo(bar: baz) var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testRemoveNilInitWithObjcAttributes() { - let input = "@objc var foo: Int? = nil" - let output = "@objc var foo: Int?" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInStructWithDefaultInit() { - let input = """ - struct Foo { - var bar: String? = nil - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testRemoveNilInitInStructWithDefaultInitInSwiftVersion5_2() { - let input = """ - struct Foo { - var bar: String? = nil - } - """ - let output = """ - struct Foo { - var bar: String? - } - """ - let options = FormatOptions(nilInit: .remove, swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testRemoveNilInitInStructWithCustomInit() { - let input = """ - struct Foo { - var bar: String? = nil - init() { - bar = "bar" - } - } - """ - let output = """ - struct Foo { - var bar: String? - init() { - bar = "bar" - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - var foo: String? = nil - Text(foo ?? "") - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInIfStatementInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - if true { - var foo: String? = nil - Text(foo ?? "") - } else { - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInSwitchStatementInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - switch foo { - case .bar: - var foo: String? = nil - Text(foo ?? "") - - default: - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - // --nilInit insert - - func testInsertNilInit() { - let input = "var foo: Int?\nlet bar: Int? = nil" - let output = "var foo: Int? = nil\nlet bar: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertNilInitBeforeLet() { - let input = "var foo: Int?; let bar: Int? = nil" - let output = "var foo: Int? = nil; let bar: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertNilInitAfterLet() { - let input = "let bar: Int? = nil; var foo: Int?" - let output = "let bar: Int? = nil; var foo: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNonNilInit() { - let input = "var foo: Int? = 0" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertRedundantImplicitUnwrapInit() { - let input = "var foo: Int!" - let output = "var foo: Int! = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertMultipleRedundantNilInitsInSameLine() { - let input = "var foo: Int?, bar: Int?" - let output = "var foo: Int? = nil, bar: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertLazyVarNilInit() { - let input = "lazy var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertLazyPublicPrivateSetVarNilInit() { - let input = "lazy private(set) public var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, options: options, - exclude: ["modifierOrder"]) - } - - func testNoInsertCodableNilInit() { - let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithPropertyWrapper() { - let input = "@Foo var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithLowercasePropertyWrapper() { - let input = "@foo var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithPropertyWrapperWithArgument() { - let input = "@Foo(bar: baz) var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithLowercasePropertyWrapperWithArgument() { - let input = "@foo(bar: baz) var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertNilInitWithObjcAttributes() { - let input = "@objc var foo: Int?" - let output = "@objc var foo: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInStructWithDefaultInit() { - let input = """ - struct Foo { - var bar: String? - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertNilInitInStructWithDefaultInitInSwiftVersion5_2() { - let input = """ - struct Foo { - var bar: String? - var foo: String? = nil - } - """ - let output = """ - struct Foo { - var bar: String? = nil - var foo: String? = nil - } - """ - let options = FormatOptions(nilInit: .insert, swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertNilInitInStructWithCustomInit() { - let input = """ - struct Foo { - var bar: String? - var foo: String? = nil - init() { - bar = "bar" - foo = "foo" - } - } - """ - let output = """ - struct Foo { - var bar: String? = nil - var foo: String? = nil - init() { - bar = "bar" - foo = "foo" - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInViewBuilder() { - // Not insert `nil` in result builder - let input = """ - struct TestView: View { - var body: some View { - var foo: String? - Text(foo ?? "") - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInIfStatementInViewBuilder() { - // Not insert `nil` in result builder - let input = """ - struct TestView: View { - var body: some View { - if true { - var foo: String? - Text(foo ?? "") - } else { - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInSwitchStatementInViewBuilder() { - // Not insert `nil` in result builder - let input = """ - struct TestView: View { - var body: some View { - switch foo { - case .bar: - var foo: String? - Text(foo ?? "") - - default: - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInSingleLineComputedProperty() { - let input = """ - var bar: String? { "some string" } - var foo: String? { nil } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInMultilineComputedProperty() { - let input = """ - var foo: String? { - print("some") - } - - var bar: String? { - nil - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInCustomGetterAndSetterProperty() { - let input = """ - var _foo: String? = nil - var foo: String? { - set { _foo = newValue } - get { newValue } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - func testInsertNilInitInInstancePropertyWithBody() { - let input = """ - var foo: String? { - didSet { print(foo) } - } - """ - - let output = """ - var foo: String? = nil { - didSet { print(foo) } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, - options: options) - } - - func testNoInsertNilInitInAs() { - let input = """ - let json: Any = ["key": 1] - var jsonObject = json as? [String: Int] - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, - options: options) - } - - // MARK: - redundantLet - - func testRemoveRedundantLet() { - let input = "let _ = bar {}" - let output = "_ = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetWithType() { - let input = "let _: String = bar {}" - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testRemoveRedundantLetInCase() { - let input = "if case .foo(let _) = bar {}" - let output = "if case .foo(_) = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantLet, exclude: ["redundantPattern"]) - } - - func testRemoveRedundantVarsInCase() { - let input = "if case .foo(var _, var /* unused */ _) = bar {}" - let output = "if case .foo(_, /* unused */ _) = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInIf() { - let input = "if let _ = foo {}" - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInMultiIf() { - let input = "if foo == bar, /* comment! */ let _ = baz {}" - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInGuard() { - let input = "guard let _ = foo else {}" - testFormatting(for: input, rule: FormatRules.redundantLet, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveLetInWhile() { - let input = "while let _ = foo {}" - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInViewBuilder() { - let input = """ - HStack { - let _ = print("Hi") - Text("Some text") - } - """ - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInViewBuilderModifier() { - let input = """ - VStack { - Text("Some text") - } - .overlay( - HStack { - let _ = print("") - } - ) - """ - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInIfStatementInViewBuilder() { - let input = """ - VStack { - if visible == "YES" { - let _ = print("") - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetInSwitchStatementInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - var foo = "" - switch (self.min, self.max) { - case let (nil, max as Int): - let _ = { - foo = "\\(max)" - }() - - default: - EmptyView() - } - - Text(foo) - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveAsyncLet() { - let input = "async let _ = foo()" - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetImmediatelyAfterMainActorAttribute() { - let input = """ - let foo = bar { @MainActor - let _ = try await baz() - } - """ - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - func testNoRemoveLetImmediatelyAfterSendableAttribute() { - let input = """ - let foo = bar { @Sendable - let _ = try await baz() - } - """ - testFormatting(for: input, rule: FormatRules.redundantLet) - } - - // MARK: - redundantPattern - - func testRemoveRedundantPatternInIfCase() { - let input = "if case let .foo(_, _) = bar {}" - let output = "if case .foo = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantPattern) - } - - func testNoRemoveRequiredPatternInIfCase() { - let input = "if case (_, _) = bar {}" - testFormatting(for: input, rule: FormatRules.redundantPattern) - } - - func testRemoveRedundantPatternInSwitchCase() { - let input = "switch foo {\ncase let .bar(_, _): break\ndefault: break\n}" - let output = "switch foo {\ncase .bar: break\ndefault: break\n}" - testFormatting(for: input, output, rule: FormatRules.redundantPattern) - } - - func testNoRemoveRequiredPatternLetInSwitchCase() { - let input = "switch foo {\ncase let .bar(_, a): break\ndefault: break\n}" - testFormatting(for: input, rule: FormatRules.redundantPattern) - } - - func testNoRemoveRequiredPatternInSwitchCase() { - let input = "switch foo {\ncase (_, _): break\ndefault: break\n}" - testFormatting(for: input, rule: FormatRules.redundantPattern) - } - - func testSimplifyLetPattern() { - let input = "let(_, _) = bar" - let output = "let _ = bar" - testFormatting(for: input, output, rule: FormatRules.redundantPattern, exclude: ["redundantLet"]) - } - - func testNoRemoveVoidFunctionCall() { - let input = "if case .foo() = bar {}" - testFormatting(for: input, rule: FormatRules.redundantPattern) - } - - func testNoRemoveMethodSignature() { - let input = "func foo(_, _) {}" - testFormatting(for: input, rule: FormatRules.redundantPattern) - } - - // MARK: - redundantRawValues - - func testRemoveRedundantRawString() { - let input = "enum Foo: String {\n case bar = \"bar\"\n case baz = \"baz\"\n}" - let output = "enum Foo: String {\n case bar\n case baz\n}" - testFormatting(for: input, output, rule: FormatRules.redundantRawValues) - } - - func testRemoveCommaDelimitedCaseRawStringCases() { - let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }" - let output = "enum Foo: String { case bar, baz }" - testFormatting(for: input, output, rule: FormatRules.redundantRawValues, - exclude: ["wrapEnumCases"]) - } - - func testRemoveBacktickCaseRawStringCases() { - let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }" - let output = "enum Foo: String { case `as`, `let` }" - testFormatting(for: input, output, rule: FormatRules.redundantRawValues, - exclude: ["wrapEnumCases"]) - } - - func testNoRemoveRawStringIfNameDoesntMatch() { - let input = "enum Foo: String {\n case bar = \"foo\"\n}" - testFormatting(for: input, rule: FormatRules.redundantRawValues) - } - - // MARK: - redundantVoidReturnType - - func testRemoveRedundantVoidReturnType() { - let input = "func foo() -> Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantVoidReturnType2() { - let input = "func foo() ->\n Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantSwiftDotVoidReturnType() { - let input = "func foo() -> Swift.Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantSwiftDotVoidReturnType2() { - let input = "func foo() -> Swift\n .Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantEmptyReturnType() { - let input = "func foo() -> () {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantVoidTupleReturnType() { - let input = "func foo() -> (Void) {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testNoRemoveCommentFollowingRedundantVoidReturnType() { - let input = "func foo() -> Void /* void */ {}" - let output = "func foo() /* void */ {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testNoRemoveRequiredVoidReturnType() { - let input = "typealias Foo = () -> Void" - testFormatting(for: input, rule: FormatRules.redundantVoidReturnType) - } - - func testNoRemoveChainedVoidReturnType() { - let input = "func foo() -> () -> Void {}" - testFormatting(for: input, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantVoidInClosureArguments() { - let input = "{ (foo: Bar) -> Void in foo() }" - let output = "{ (foo: Bar) in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantEmptyReturnTypeInClosureArguments() { - let input = "{ (foo: Bar) -> () in foo() }" - let output = "{ (foo: Bar) in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantVoidInClosureArguments2() { - let input = "methodWithTrailingClosure { foo -> Void in foo() }" - let output = "methodWithTrailingClosure { foo in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testRemoveRedundantSwiftDotVoidInClosureArguments2() { - let input = "methodWithTrailingClosure { foo -> Swift.Void in foo() }" - let output = "methodWithTrailingClosure { foo in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - func testNoRemoveRedundantVoidInClosureArgument() { - let input = "{ (foo: Bar) -> Void in foo() }" - let options = FormatOptions(closureVoidReturn: .preserve) - testFormatting(for: input, rule: FormatRules.redundantVoidReturnType, options: options) - } - - func testRemoveRedundantVoidInProtocolDeclaration() { - let input = """ - protocol Foo { - func foo() -> Void - func bar() -> () - var baz: Int { get } - func bazz() -> ( ) - } - """ - - let output = """ - protocol Foo { - func foo() - func bar() - var baz: Int { get } - func bazz() - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) - } - - // MARK: - redundantReturn - - func testRemoveRedundantReturnInClosure() { - let input = "foo(with: { return 5 })" - let output = "foo(with: { 5 })" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["trailingClosures"]) - } - - func testRemoveRedundantReturnInClosureWithArgs() { - let input = "foo(with: { foo in return foo })" - let output = "foo(with: { foo in foo })" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["trailingClosures"]) - } - - func testRemoveRedundantReturnInMap() { - let input = "let foo = bar.map { return 1 }" - let output = "let foo = bar.map { 1 }" - testFormatting(for: input, output, rule: FormatRules.redundantReturn) - } - - func testNoRemoveReturnInComputedVar() { - let input = "var foo: Int { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn) - } - - func testRemoveReturnInComputedVar() { - let input = "var foo: Int { return 5 }" - let output = "var foo: Int { 5 }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInGet() { - let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" - testFormatting(for: input, rule: FormatRules.redundantReturn) - } - - func testRemoveReturnInGet() { - let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" - let output = "var foo: Int {\n get { 5 }\n set { _foo = newValue }\n}" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInGetClosure() { - let input = "let foo = get { return 5 }" - let output = "let foo = get { 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantReturn) - } - - func testRemoveReturnInVarClosure() { - let input = "var foo = { return 5 }()" - let output = "var foo = { 5 }()" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["redundantClosure"]) - } - - func testRemoveReturnInParenthesizedClosure() { - let input = "var foo = ({ return 5 }())" - let output = "var foo = ({ 5 }())" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["redundantParens", "redundantClosure"]) - } - - func testNoRemoveReturnInFunction() { - let input = "func foo() -> Int { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn) - } - - func testRemoveReturnInFunction() { - let input = "func foo() -> Int { return 5 }" - let output = "func foo() -> Int { 5 }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInOperatorFunction() { - let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["unusedArguments"]) - } - - func testRemoveReturnInOperatorFunction() { - let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" - let output = "func + (lhs: Int, rhs: Int) -> Int { 5 }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options, - exclude: ["unusedArguments"]) - } - - func testNoRemoveReturnInFailableInit() { - let input = "init?() { return nil }" - testFormatting(for: input, rule: FormatRules.redundantReturn) - } - - func testNoRemoveReturnInFailableInitWithConditional() { - let input = """ - init?(optionalHex: String?) { - if let optionalHex { - self.init(hex: optionalHex) - } else { - return nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInFailableInitWithNestedConditional() { - let input = """ - init?(optionalHex: String?) { - if let optionalHex { - self.init(hex: optionalHex) - } else { - switch foo { - case .foo: - self.init() - case .bar: - return nil - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRemoveReturnInFailableInit() { - let input = "init?() { return nil }" - let output = "init?() { nil }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInSubscript() { - let input = "subscript(index: Int) -> String { return nil }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["unusedArguments"]) - } - - func testRemoveReturnInSubscript() { - let input = "subscript(index: Int) -> String { return nil }" - let output = "subscript(index: Int) -> String { nil }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options, - exclude: ["unusedArguments"]) - } - - func testNoRemoveReturnInDoCatch() { - let input = """ - func foo() -> Int { - do { - return try Bar() - } catch { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInDoThrowsCatch() { - let input = """ - func foo() -> Int { - do throws(Foo) { - return try Bar() - } catch { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInDoCatchLet() { - let input = """ - func foo() -> Int { - do { - return try Bar() - } catch let e as Error { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInDoThrowsCatchLet() { - let input = """ - func foo() -> Int { - do throws(Foo) { - return try Bar() - } catch let e as Error { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInForIn() { - let input = "for foo in bar { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["wrapLoopBodies"]) - } - - func testNoRemoveReturnInForWhere() { - let input = "for foo in bar where baz { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["wrapLoopBodies"]) - } - - func testNoRemoveReturnInIfLetTry() { - let input = "if let foo = try? bar() { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveReturnInMultiIfLetTry() { - let input = "if let foo = bar, let bar = baz { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveReturnAfterMultipleAs() { - let input = "if foo as? bar as? baz { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, - exclude: ["wrapConditionalBodies"]) - } - - func testRemoveVoidReturn() { - let input = "{ _ in return }" - let output = "{ _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantReturn) - } - - func testNoRemoveReturnAfterKeyPath() { - let input = "func foo() { if bar == #keyPath(baz) { return 5 } }" - testFormatting(for: input, rule: FormatRules.redundantReturn, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveReturnAfterParentheses() { - let input = "if let foo = (bar as? String) { return foo }" - testFormatting(for: input, rule: FormatRules.redundantReturn, - exclude: ["redundantParens", "wrapConditionalBodies"]) - } - - func testRemoveReturnInTupleVarGetter() { - let input = "var foo: (Int, Int) { return (1, 2) }" - let output = "var foo: (Int, Int) { (1, 2) }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveReturnInIfLetWithNoSpaceAfterParen() { - let input = """ - var foo: String? { - if let bar = baz(){ - return bar - } else { - return nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, - exclude: ["spaceAroundBraces", "spaceAroundParens"]) - } - - func testNoRemoveReturnInIfWithUnParenthesizedClosure() { - let input = """ - if foo { $0.bar } { - return true - } - """ - testFormatting(for: input, rule: FormatRules.redundantReturn) - } - - func testRemoveBlankLineWithReturn() { - let input = """ - foo { - return - bar - } - """ - let output = """ - foo { - bar - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn, - exclude: ["indent"]) - } - - func testRemoveRedundantReturnInFunctionWithWhereClause() { - let input = """ - func foo(_ name: String) -> T where T: Equatable { - return name - } - """ - let output = """ - func foo(_ name: String) -> T where T: Equatable { - name - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, - options: options) - } - - func testRemoveRedundantReturnInSubscriptWithWhereClause() { - let input = """ - subscript(_ name: String) -> T where T: Equatable { - return name - } - """ - let output = """ - subscript(_ name: String) -> T where T: Equatable { - name - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, - options: options) - } - - func testNoRemoveReturnFollowedByMoreCode() { - let input = """ - var foo: Bar = { - return foo - let bar = baz - return bar - }() - """ - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["redundantProperty"]) - } - - func testNoRemoveReturnInForWhereLoop() { - let input = """ - func foo() -> Bool { - for bar in baz where !bar { - return false - } - return true - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRedundantReturnInVoidFunction() { - let input = """ - func foo() { - return - } - """ - let output = """ - func foo() { - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn, - exclude: ["emptyBraces"]) - } - - func testRedundantReturnInVoidFunction2() { - let input = """ - func foo() { - print("") - return - } - """ - let output = """ - func foo() { - print("") - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn) - } - - func testRedundantReturnInVoidFunction3() { - let input = """ - func foo() { - // empty - return - } - """ - let output = """ - func foo() { - // empty - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn) - } - - func testRedundantReturnInVoidFunction4() { - let input = """ - func foo() { - return // empty - } - """ - let output = """ - func foo() { - // empty - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn) - } - - func testNoRemoveVoidReturnInCatch() { - let input = """ - func foo() { - do { - try Foo() - } catch Feature.error { - print("feature error") - return - } - print("foo") - } - """ - testFormatting(for: input, rule: FormatRules.redundantReturn) - } - - func testNoRemoveReturnInIfCase() { - let input = """ - var isSessionDeinitializedError: Bool { - if case .sessionDeinitialized = self { return true } - return false - } - """ - testFormatting(for: input, rule: FormatRules.redundantReturn, - options: FormatOptions(swiftVersion: "5.1"), - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveReturnInForCasewhere() { - let input = """ - for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] - where names.contains(name) - { - return true - } - """ - testFormatting(for: input, rule: FormatRules.redundantReturn, - options: FormatOptions(swiftVersion: "5.1")) - } - - func testNoRemoveRequiredReturnInFunctionInsideClosure() { - let input = """ - foo { - func bar() -> Bar { - let bar = Bar() - return bar - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantReturn, - options: FormatOptions(swiftVersion: "5.1"), exclude: ["redundantProperty"]) - } - - func testNoRemoveRequiredReturnInIfClosure() { - let input = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { - return btn - } - return btns.first - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNoRemoveRequiredReturnInIfClosure2() { - let input = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let foo, let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { - return btn - } - return btns.first - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRemoveRedundantReturnInIfClosure() { - let input = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let btn = btns.first { return !$0.isHidden && $0.alpha > 0.01 } { - print("hello") - } - return btns.first - } - """ - let output = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { - print("hello") - } - return btns.first - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testDisableNextRedundantReturn() { - let input = """ - func foo() -> Foo { - // swiftformat:disable:next redundantReturn - return Foo() - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRedundantIfStatementReturnSwift5_8() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else { - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantReturn, - options: options) - } - - func testNonRedundantIfStatementReturnSwift5_9() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else if !condition { - return "bar" - } - return "baaz" - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRedundantIfStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else if otherCondition { - if anotherCondition { - return "bar" - } else { - return "baaz" - } - } else { - return "quux" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - if condition { - "foo" - } else if otherCondition { - if anotherCondition { - "bar" - } else { - "baaz" - } - } else { - "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testNoRemoveRedundantIfStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else if otherCondition { - if anotherCondition { - return "bar" - } else { - return "baaz" - } - } else { - return "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, - exclude: ["conditionalAssignment"]) - } - - func testRedundantIfStatementReturnInClosure() { - let input = """ - let closure: (Bool) -> String = { condition in - if condition { - return "foo" - } else { - return "bar" - } - } - """ - let output = """ - let closure: (Bool) -> String = { condition in - if condition { - "foo" - } else { - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testNoRemoveRedundantIfStatementReturnInClosure() { - let input = """ - let closure: (Bool) -> String = { condition in - if condition { - return "foo" - } else { - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, - exclude: ["conditionalAssignment"]) - } - - func testNoRemoveReturnInConsecutiveIfStatements() { - let input = """ - func foo() -> String? { - if bar { - return nil - } - if baz { - return "baz" - } else { - return "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRedundantIfStatementReturnInRedundantClosure() { - let input = """ - let value = { - if condition { - return "foo" - } else { - return "bar" - } - }() - """ - let output = """ - let value = if condition { - "foo" - } else { - "bar" - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure, FormatRules.indent], - options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - func testRedundantSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - case false: - return "bar" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - "foo" - case false: - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testNoRemoveRedundantSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - case false: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, - exclude: ["conditionalAssignment"]) - } - - func testClosureAroundConditionalAssignmentNotRedundantForExplicitReturn() { - let input = """ - let myEnum = MyEnum.a - let test: Int = { - switch myEnum { - case .a: - return 0 - case .b: - return 1 - } - }() - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options, - exclude: ["redundantReturn", "propertyType"]) - } - - func testNonRedundantSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - case false: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRedundantSwitchStatementReturnInFunctionWithDefault() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - default: - return "bar" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - "foo" - default: - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testRedundantSwitchStatementReturnInFunctionWithComment() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - // foo - return "foo" - - default: - /* bar */ - return "bar" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - // foo - "foo" - - default: - /* bar */ - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testNonRedundantSwitchStatementReturnInFunctionWithDefault() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - default: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testNonRedundantSwitchStatementReturnInFunctionWithFallthrough() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - fallthrough - case false: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testVoidReturnNotStrippedFromSwitch() { - let input = """ - func foo(condition: Bool) { - switch condition { - case true: - print("foo") - case false: - return - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRedundantNestedSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - switch condition { - case true: - return "foo" - case false: - if condition { - return "bar" - } else { - return "baaz" - } - } - - case false: - return "quux" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - switch condition { - case true: - "foo" - case false: - if condition { - "bar" - } else { - "baaz" - } - } - - case false: - "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testRedundantSwitchStatementReturnWithAssociatedValueMatchingInFunction() { - let input = """ - func test(_ value: SomeEnum) -> String { - switch value { - case let .first(str): - return "first \\(str)" - case .second("str"): - return "second" - default: - return "default" - } - } - """ - let output = """ - func test(_ value: SomeEnum) -> String { - switch value { - case let .first(str): - "first \\(str)" - case .second("str"): - "second" - default: - "default" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testRedundantReturnDoesntFailToTerminateOnLongSwitch() { - let input = """ - func test(_ value: SomeEnum) -> String { - switch value { - case .one: - return "" - case .two: - return "" - case .three: - return "" - case .four: - return "" - case .five: - return "" - case .six: - return "" - case .seven: - return "" - case .eight: - return "" - case .nine: - return "" - case .ten: - return "" - case .eleven: - return "" - case .twelve: - return "" - case .thirteen: - return "" - case .fourteen: - return "" - case .fifteen: - return "" - case .sixteen: - return "" - case .seventeen: - return "" - case .eighteen: - return "" - case .nineteen: - return "" - } - } - """ - let output = """ - func test(_ value: SomeEnum) -> String { - switch value { - case .one: - "" - case .two: - "" - case .three: - "" - case .four: - "" - case .five: - "" - case .six: - "" - case .seven: - "" - case .eight: - "" - case .nine: - "" - case .ten: - "" - case .eleven: - "" - case .twelve: - "" - case .thirteen: - "" - case .fourteen: - "" - case .fifteen: - "" - case .sixteen: - "" - case .seventeen: - "" - case .eighteen: - "" - case .nineteen: - "" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testNoRemoveDebugReturnFollowedBySwitch() { - let input = """ - func swiftFormatBug() -> Foo { - return .foo - - switch state { - case .foo, .bar: - return state - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) - } - - func testDoesntRemoveReturnFromIfExpressionConditionalCastInSwift5_9() { - // The following code doesn't compile in Swift 5.9 due to this issue: - // https://github.com/apple/swift/issues/68764 - // - // var result: String { - // if condition { - // foo as? String - // } else { - // "bar" - // } - // } - // - let input = """ - var result1: String { - if condition { - return foo as? String - } else { - return "bar" - } - } - - var result2: String { - switch condition { - case true: - return foo as? String - case false: - return "bar" - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) - } - - func testRemovseReturnFromIfExpressionNestedConditionalCastInSwift5_9() { - let input = """ - var result1: String { - if condition { - return method(foo as? String) - } else { - return "bar" - } - } - """ - - let output = """ - var result1: String { - if condition { - method(foo as? String) - } else { - "bar" - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testRemovesReturnFromIfExpressionConditionalCastInSwift5_10() { - let input = """ - var result: String { - if condition { - return foo as? String - } else { - return "bar" - } - } - """ - - let output = """ - var result: String { - if condition { - foo as? String - } else { - "bar" - } - } - """ - - let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testRemovesRedundantReturnBeforeIfExpression() { - let input = """ - func foo() -> Foo { - return if condition { - Foo.foo() - } else { - Foo.bar() - } - } - """ - - let output = """ - func foo() -> Foo { - if condition { - Foo.foo() - } else { - Foo.bar() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testRemovesRedundantReturnBeforeSwitchExpression() { - let input = """ - func foo() -> Foo { - return switch condition { - case true: - Foo.foo() - case false: - Foo.bar() - } - } - """ - - let output = """ - func foo() -> Foo { - switch condition { - case true: - Foo.foo() - case false: - Foo.bar() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) - } - - func testReturnNotRemovedFromSwitchBodyWithOpaqueReturnType() { - // https://github.com/nicklockwood/SwiftFormat/issues/1819 - let input = """ - extension View { - func foo() -> some View { - if #available(iOS 16.0, *) { - return self.scrollIndicators(.hidden) - } else { - return self - } - } - - func bar() -> (some View) { - if #available(iOS 16.0, *) { - return self.scrollIndicators(.hidden) - } else { - return self - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testReturnNotRemovedFromCatchWhere() { - // https://github.com/nicklockwood/SwiftFormat/issues/1843 - let input = """ - func decodeError(from data: Data, urlResponse: HTTPURLResponse) -> Error { - do { - let decoder = JSONDecoder() - return try decoder.decode(T.self, from: data) - } catch where urlResponse.statusCode >= 400 { - return CustomError() // <- return removed here, introducing a compile time error. - } catch { - return error - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - // MARK: - redundantBackticks - - func testRemoveRedundantBackticksInLet() { - let input = "let `foo` = bar" - let output = "let foo = bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundKeyword() { - let input = "let `let` = foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundSelf() { - let input = "let `self` = foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundClassSelfInTypealias() { - let input = "typealias `Self` = Foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundClassSelfAsReturnType() { - let input = "func foo(bar: `Self`) { print(bar) }" - let output = "func foo(bar: Self) { print(bar) }" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundClassSelfAsParameterType() { - let input = "func foo() -> `Self` {}" - let output = "func foo() -> Self {}" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundClassSelfArgument() { - let input = "func foo(`Self`: Foo) { print(Self) }" - let output = "func foo(Self: Foo) { print(Self) }" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundKeywordFollowedByType() { - let input = "let `default`: Int = foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundContextualGet() { - let input = "var foo: Int {\n `get`()\n return 5\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundGetArgument() { - let input = "func foo(`get` value: Int) { print(value) }" - let output = "func foo(get value: Int) { print(value) }" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundTypeAtRootLevel() { - let input = "enum `Type` {}" - let output = "enum Type {}" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundTypeInsideType() { - let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks, exclude: ["enumNamespaces"]) - } - - func testNoRemoveBackticksAroundLetArgument() { - let input = "func foo(`let`: Foo) { print(`let`) }" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundTrueArgument() { - let input = "func foo(`true`: Foo) { print(`true`) }" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundTrueArgument() { - let input = "func foo(`true`: Foo) { print(`true`) }" - let output = "func foo(true: Foo) { print(`true`) }" - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options) - } - - func testNoRemoveBackticksAroundTypeProperty() { - let input = "var type: Foo.`Type`" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundTypePropertyInsideType() { - let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks, exclude: ["enumNamespaces"]) - } - - func testNoRemoveBackticksAroundTrueProperty() { - let input = "var type = Foo.`true`" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundTrueProperty() { - let input = "var type = Foo.`true`" - let output = "var type = Foo.true" - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options, exclude: ["propertyType"]) - } - - func testRemoveBackticksAroundProperty() { - let input = "var type = Foo.`bar`" - let output = "var type = Foo.bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, exclude: ["propertyType"]) - } - - func testRemoveBackticksAroundKeywordProperty() { - let input = "var type = Foo.`default`" - let output = "var type = Foo.default" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, exclude: ["propertyType"]) - } - - func testRemoveBackticksAroundKeypathProperty() { - let input = "var type = \\.`bar`" - let output = "var type = \\.bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundKeypathKeywordProperty() { - let input = "var type = \\.`default`" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundKeypathKeywordPropertyInSwift5() { - let input = "var type = \\.`default`" - let output = "var type = \\.default" - let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options) - } - - func testNoRemoveBackticksAroundInitPropertyInSwift5() { - let input = "let foo: Foo = .`init`" - let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options, exclude: ["propertyType"]) - } - - func testNoRemoveBackticksAroundAnyProperty() { - let input = "enum Foo {\n case `Any`\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundGetInSubscript() { - let input = """ - subscript(_ name: String) -> T where T: Equatable { - `get`(name) - } - """ - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundActorProperty() { - let input = "let `actor`: Foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundActorRvalue() { - let input = "let foo = `actor`" - let output = "let foo = actor" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundActorLabel() { - let input = "init(`actor`: Foo)" - let output = "init(actor: Foo)" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testRemoveBackticksAroundActorLabel2() { - let input = "init(`actor` foo: Foo)" - let output = "init(actor foo: Foo)" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundUnderscore() { - let input = "func `_`(_ foo: T) -> T { foo }" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - func testNoRemoveBackticksAroundShadowedSelf() { - let input = """ - struct Foo { - let `self`: URL - - func printURL() { - print("My URL is \\(self.`self`)") - } - } - """ - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options) - } - - func testNoRemoveBackticksAroundDollar() { - let input = "@attached(peer, names: prefixed(`$`))" - testFormatting(for: input, rule: FormatRules.redundantBackticks) - } - - // MARK: - redundantSelf - - // explicitSelf = .remove - - func testSimpleRemoveRedundantSelf() { - let input = "func foo() { self.bar() }" - let output = "func foo() { bar() }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInsideStringInterpolation() { - let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" - let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForArgument() { - let input = "func foo(bar: Int) { self.bar = bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForLocalVariable() { - let input = "func foo() { var bar = self.bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfForLocalVariableOn5_4() { - let input = "func foo() { var bar = self.bar }" - let output = "func foo() { var bar = bar }" - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: options) - } - - func testNoRemoveSelfForCommaDelimitedLocalVariables() { - let input = "func foo() { let foo = self.foo, bar = self.bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfForCommaDelimitedLocalVariablesOn5_4() { - let input = "func foo() { let foo = self.foo, bar = self.bar }" - let output = "func foo() { let foo = self.foo, bar = bar }" - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: options) - } - - func testNoRemoveSelfForCommaDelimitedLocalVariables2() { - let input = "func foo() {\n let foo: Foo, bar: Bar\n foo = self.foo\n bar = self.bar\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForTupleAssignedVariables() { - let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - // TODO: make this work -// func testRemoveSelfForTupleAssignedVariablesOn5_4() { -// let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" -// let output = "func foo() { let (bar, baz) = (bar, baz) }" -// let options = FormatOptions(swiftVersion: "5.4") -// testFormatting(for: input, output, rule: FormatRules.redundantSelf, -// options: options) -// } - - func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularVariable() { - let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar), baz = self.baz\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularLet() { - let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar)\n let baz = self.baz\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveNonRedundantNestedFunctionSelf() { - let input = "func foo() { func bar() { self.bar() } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveNonRedundantNestedFunctionSelf2() { - let input = "func foo() {\n func bar() {}\n self.bar()\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveNonRedundantNestedFunctionSelf3() { - let input = "func foo() { let bar = 5; func bar() { self.bar = bar } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveClosureSelf() { - let input = "func foo() { bar { self.bar = 5 } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfAfterOptionalReturn() { - let input = "func foo() -> String? {\n var index = startIndex\n if !matching(self[index]) {\n break\n }\n index = self.index(after: index)\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveRequiredSelfInExtensions() { - let input = "extension Foo {\n func foo() {\n var index = 5\n if true {\n break\n }\n index = self.index(after: index)\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfBeforeInit() { - let input = "convenience init() { self.init(5) }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInsideSwitch() { - let input = "func foo() {\n switch self.bar {\n case .foo:\n self.baz()\n }\n}" - let output = "func foo() {\n switch bar {\n case .foo:\n baz()\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInsideSwitchWhere() { - let input = "func foo() {\n switch self.bar {\n case .foo where a == b:\n self.baz()\n }\n}" - let output = "func foo() {\n switch bar {\n case .foo where a == b:\n baz()\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInsideSwitchWhereAs() { - let input = "func foo() {\n switch self.bar {\n case .foo where a == b as C:\n self.baz()\n }\n}" - let output = "func foo() {\n switch bar {\n case .foo where a == b as C:\n baz()\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInsideClassInit() { - let input = "class Foo {\n var bar = 5\n init() { self.bar = 6 }\n}" - let output = "class Foo {\n var bar = 5\n init() { bar = 6 }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureInsideIf() { - let input = "if foo { bar { self.baz() } }" - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveSelfForErrorInCatch() { - let input = "do {} catch { self.error = error }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForErrorInDoThrowsCatch() { - let input = "do throws(Foo) {} catch { self.error = error }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForNewValueInSet() { - let input = "var foo: Int { set { self.newValue = newValue } get { return 0 } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForCustomNewValueInSet() { - let input = "var foo: Int { set(n00b) { self.n00b = n00b } get { return 0 } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForNewValueInWillSet() { - let input = "var foo: Int { willSet { self.newValue = newValue } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForCustomNewValueInWillSet() { - let input = "var foo: Int { willSet(n00b) { self.n00b = n00b } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForOldValueInDidSet() { - let input = "var foo: Int { didSet { self.oldValue = oldValue } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForCustomOldValueInDidSet() { - let input = "var foo: Int { didSet(oldz) { self.oldz = oldz } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForIndexVarInFor() { - let input = "for foo in bar { self.foo = foo }" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) - } - - func testNoRemoveSelfForKeyValueTupleInFor() { - let input = "for (foo, bar) in baz { self.foo = foo; self.bar = bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) - } - - func testRemoveSelfFromComputedVar() { - let input = "var foo: Int { return self.bar }" - let output = "var foo: Int { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromOptionalComputedVar() { - let input = "var foo: Int? { return self.bar }" - let output = "var foo: Int? { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromNamespacedComputedVar() { - let input = "var foo: Swift.String { return self.bar }" - let output = "var foo: Swift.String { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromGenericComputedVar() { - let input = "var foo: Foo { return self.bar }" - let output = "var foo: Foo { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromComputedArrayVar() { - let input = "var foo: [Int] { return self.bar }" - let output = "var foo: [Int] { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromVarSetter() { - let input = "var foo: Int { didSet { self.bar() } }" - let output = "var foo: Int { didSet { bar() } }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromVarClosure() { - let input = "var foo = { self.bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromLazyVar() { - let input = "lazy var foo = self.bar" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromLazyVar() { - let input = "lazy var foo = self.bar" - let output = "lazy var foo = bar" - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { - let input = """ - var baz = bar - lazy var foo = self.bar - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { - let input = """ - var baz = bar - lazy var foo = self.bar - """ - let output = """ - var baz = bar - lazy var foo = bar - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfFromLazyVarClosure() { - let input = "lazy var foo = { self.bar }()" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["redundantClosure"]) - } - - func testNoRemoveSelfFromLazyVarClosure2() { - let input = "lazy var foo = { let bar = self.baz }()" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromLazyVarClosure3() { - let input = "lazy var foo = { [unowned self] in let bar = self.baz }()" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromVarInFuncWithUnusedArgument() { - let input = "func foo(bar _: Int) { self.baz = 5 }" - let output = "func foo(bar _: Int) { baz = 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfFromVarMatchingUnusedArgument() { - let input = "func foo(bar _: Int) { self.bar = 5 }" - let output = "func foo(bar _: Int) { bar = 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromVarMatchingRenamedArgument() { - let input = "func foo(bar baz: Int) { self.baz = baz }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromVarRedeclaredInSubscope() { - let input = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = self.bar\n}" - let output = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = bar\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromVarDeclaredLaterInScope() { - let input = "func foo() {\n let bar = self.baz\n let baz = quux\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfFromVarDeclaredLaterInOuterScope() { - let input = "func foo() {\n if quux {\n let bar = self.baz\n }\n let baz = 6\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInWhilePreceededByVarDeclaration() { - let input = "var index = start\nwhile index < end {\n index = self.index(after: index)\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInLocalVarPrecededByLocalVarFollowedByIfComma() { - let input = "func foo() {\n let bar = Bar()\n let baz = Baz()\n self.baz = baz\n if let bar = bar, bar > 0 {}\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInLocalVarPrecededByIfLetContainingClosure() { - let input = "func foo() {\n if let bar = 5 { baz { _ in } }\n let quux = self.quux\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveSelfForVarCreatedInGuardScope() { - let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["wrapConditionalBodies"]) - } - - func testRemoveSelfForVarCreatedInIfScope() { - let input = "func foo() {\n if let bar = bar {}\n let baz = self.bar\n}" - let output = "func foo() {\n if let bar = bar {}\n let baz = bar\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForVarDeclaredInWhileCondition() { - let input = "while let foo = bar { self.foo = foo }" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) - } - - func testRemoveSelfForVarNotDeclaredInWhileCondition() { - let input = "while let foo == bar { self.baz = 5 }" - let output = "while let foo == bar { baz = 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) - } - - func testNoRemoveSelfForVarDeclaredInSwitchCase() { - let input = "switch foo {\ncase bar: let baz = self.baz\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfAfterGenericInit() { - let input = "init(bar: Int) {\n self = Foo()\n self.bar(bar)\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInClassFunction() { - let input = "class Foo {\n class func foo() {\n func bar() { self.foo() }\n }\n}" - let output = "class Foo {\n class func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfInStaticFunction() { - let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}" - let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf, exclude: ["enumNamespaces"]) - } - - func testRemoveSelfInClassFunctionWithModifiers() { - let input = "class Foo {\n class private func foo() {\n func bar() { self.foo() }\n }\n}" - let output = "class Foo {\n class private func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - exclude: ["modifierOrder"]) - } - - func testNoRemoveSelfInClassFunction() { - let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { self.foo() }\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForVarDeclaredAfterRepeatWhile() { - let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n let foo = 6\n self.foo()\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfForVarInClosureAfterRepeatWhile() { - let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n ({ self.foo() })()\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureAfterVar() { - let input = "var foo: String\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureAfterNamespacedVar() { - let input = "var foo: Swift.String\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureAfterOptionalVar() { - let input = "var foo: String?\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureAfterGenericVar() { - let input = "var foo: Foo\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureAfterArray() { - let input = "var foo: [Int]\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInExpectFunction() { // Special case to support the Nimble framework - let input = """ - class FooTests: XCTestCase { - let foo = 1 - func testFoo() { - expect(self.foo) == 1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveNestedSelfInExpectFunction() { - let input = """ - func testFoo() { - expect(Foo.validate(bar: self.bar)).to(equal(1)) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveNestedSelfInArrayInExpectFunction() { - let input = """ - func testFoo() { - expect(Foo.validate(bar: [self.bar])).to(equal(1)) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveNestedSelfInSubscriptInExpectFunction() { - let input = """ - func testFoo() { - expect(Foo.validations[self.bar]).to(equal(1)) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfInOSLogFunction() { - let input = """ - func testFoo() { - os_log("error: \\(self.bar) is nil") - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfInExcludedFunction() { - let input = """ - class Foo { - let foo = 1 - func testFoo() { - log(self.foo) - } - } - """ - let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfForExcludedFunction() { - let input = """ - class Foo { - let foo = 1 - func testFoo() { - self.log(foo) - } - } - """ - let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfInInterpolatedStringInExcludedFunction() { - let input = """ - class Foo { - let foo = 1 - func testFoo() { - log("\\(self.foo)") - } - } - """ - let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfInExcludedInitializer() { - let input = """ - let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) - """ - let options = FormatOptions(selfRequired: ["InspectionView"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["propertyType"]) - } - - func testNoMistakeProtocolClassModifierForClassFunction() { - let input = "protocol Foo: class {}\nfunc bar() {}" - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) - XCTAssertNoThrow(try format(input, rules: FormatRules.all)) - } - - func testSelfRemovedFromSwitchCaseWhere() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where self.bar.baz: - return self.bar - default: - return nil - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where bar.baz: - return bar - default: - return nil - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testSwitchCaseLetVarRecognized() { - let input = """ - switch foo { - case .bar: - baz = nil - case let baz: - self.baz = baz - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSwitchCaseHoistedLetVarRecognized() { - let input = """ - switch foo { - case .bar: - baz = nil - case let .foo(baz): - self.baz = baz - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSwitchCaseWhereMemberNotTreatedAsVar() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar where self.bar.baz: - return self.bar - default: - return nil - } - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedInClosureAfterSwitch() { - let input = """ - switch x { - default: - break - } - let foo = { y in - switch y { - default: - self.bar() - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedInClosureInCaseWithWhereClause() { - let input = """ - switch foo { - case bar where baz: - quux = { self.foo } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfRemovedInDidSet() { - let input = """ - class Foo { - var bar = false { - didSet { - self.bar = !self.bar - } - } - } - """ - let output = """ - class Foo { - var bar = false { - didSet { - bar = !bar - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedInGetter() { - let input = """ - class Foo { - var bar: Int { - return self.bar - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedInIfdef() { - let input = """ - func foo() { - #if os(macOS) - let bar = self.bar - #endif - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfRemovedWhenFollowedBySwitchContainingIfdef() { - let input = """ - struct Foo { - func bar() { - self.method(self.value) - switch x { - #if BAZ - case .baz: - break - #endif - default: - break - } - } - } - """ - let output = """ - struct Foo { - func bar() { - method(value) - switch x { - #if BAZ - case .baz: - break - #endif - default: - break - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfRemovedInsideConditionalCase() { - let input = """ - struct Foo { - func bar() { - let method2 = () -> Void - switch x { - #if BAZ - case .baz: - self.method1(self.value) - #else - case .quux: - self.method2(self.value) - #endif - default: - break - } - } - } - """ - let output = """ - struct Foo { - func bar() { - let method2 = () -> Void - switch x { - #if BAZ - case .baz: - method1(value) - #else - case .quux: - self.method2(value) - #endif - default: - break - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfRemovedAfterConditionalLet() { - let input = """ - class Foo { - var bar: Int? - var baz: Bool - - func foo() { - if let bar = bar, self.baz { - // ... - } - } - } - """ - let output = """ - class Foo { - var bar: Int? - var baz: Bool - - func foo() { - if let bar = bar, baz { - // ... - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNestedClosureInNotMistakenForForLoop() { - let input = """ - func f() { - let str = "hello" - try! str.withCString(encodedAs: UTF8.self) { _ throws in - try! str.withCString(encodedAs: UTF8.self) { _ throws in } - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testTypedThrowingNestedClosureInNotMistakenForForLoop() { - let input = """ - func f() { - let str = "hello" - try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in - try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in } - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { [self] in - print(self.bar) - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRemovesSelfInClosureWithExplicitStrongCapture() { - let input = """ - class Foo { - let foo: Int - - func baaz() { - closure { [self, bar] baaz, quux in - print(self.foo) - } - } - } - """ - - let output = """ - class Foo { - let foo: Int - - func baaz() { - closure { [self, bar] baaz, quux in - print(foo) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options, exclude: ["unusedArguments"]) - } - - func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { - print(self.bar) - closure { [self] in - print(self.bar) - } - print(self.bar) - } - } - } - """ - - let output = """ - class Foo { - let bar: Int - - func baaz() { - closure { - print(self.bar) - closure { [self] in - print(bar) - } - print(self.bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfKeepsSelfInNestedClosureWithNoExplicitStrongCapture() { - let input = """ - class Foo { - let bar: Int - let baaz: Int? - - func baaz() { - closure { [self] in - print(self.bar) - closure { - print(self.bar) - if let baaz = self.baaz { - print(baaz) - } - } - print(self.bar) - if let baaz = self.baaz { - print(baaz) - } - } - } - } - """ - - let output = """ - class Foo { - let bar: Int - let baaz: Int? - - func baaz() { - closure { [self] in - print(bar) - closure { - print(self.bar) - if let baaz = self.baaz { - print(baaz) - } - } - print(bar) - if let baaz = baaz { - print(baaz) - } - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRemovesSelfInClosureCapturingStruct() { - let input = """ - struct Foo { - let bar: Int - - func baaz() { - closure { - print(self.bar) - } - } - } - """ - - let output = """ - struct Foo { - let bar: Int - - func baaz() { - closure { - print(bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRemovesSelfInClosureCapturingSelfWeakly() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { [weak self] in - print(self?.bar) - guard let self else { - return - } - print(self.bar) - closure { - print(self.bar) - } - closure { [self] in - print(self.bar) - } - print(self.bar) - } - - closure { [weak self] in - guard let self = self else { - return - } - - print(self.bar) - } - - closure { [weak self] in - guard let self = self ?? somethingElse else { - return - } - - print(self.bar) - } - } - } - """ - - let output = """ - class Foo { - let bar: Int - - func baaz() { - closure { [weak self] in - print(self?.bar) - guard let self else { - return - } - print(bar) - closure { - print(self.bar) - } - closure { [self] in - print(bar) - } - print(bar) - } - - closure { [weak self] in - guard let self = self else { - return - } - - print(bar) - } - - closure { [weak self] in - guard let self = self ?? somethingElse else { - return - } - - print(self.bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: options, exclude: ["redundantOptionalBinding"]) - } - - func testWeakSelfNotRemovedIfNotUnwrapped() { - let input = """ - class A { - weak var delegate: ADelegate? - - func testFunction() { - DispatchQueue.main.async { [weak self] in - self.flatMap { $0.delegate?.aDidSomething($0) } - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testClosureParameterListShadowingPropertyOnSelf() { - let input = """ - class Foo { - var bar = "bar" - - func method() { - closure { [self] bar in - self.bar = bar - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testClosureParameterListShadowingPropertyOnSelfInStruct() { - let input = """ - struct Foo { - var bar = "bar" - - func method() { - closure { bar in - self.bar = bar - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testClosureCaptureListShadowingPropertyOnSelf() { - let input = """ - class Foo { - var bar = "bar" - var baaz = "baaz" - - func method() { - closure { [self, bar, baaz = bar] in - self.bar = bar - self.baaz = baaz - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfKeepsSelfInClosureCapturingSelfWeaklyBefore5_8() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { [weak self] in - print(self?.bar) - guard let self else { - return - } - print(self.bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNonRedundantSelfNotRemovedAfterConditionalLet() { - let input = """ - class Foo { - var bar: Int? - var baz: Bool - - func foo() { - let baz = 5 - if let bar = bar, self.baz { - // ... - } - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfDoesntGetStuckIfNoParensFound() { - let input = "init_ foo: T {}" - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["spaceAroundOperators"]) - } - - func testNoRemoveSelfInIfLetSelf() { - let input = """ - func foo() { - if let self = self as? Foo { - self.bar() - } - self.bar() - } - """ - let output = """ - func foo() { - if let self = self as? Foo { - self.bar() - } - bar() - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInIfLetEscapedSelf() { - let input = """ - func foo() { - if let `self` = self as? Foo { - self.bar() - } - self.bar() - } - """ - let output = """ - func foo() { - if let `self` = self as? Foo { - self.bar() - } - bar() - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfAfterGuardLetSelf() { - let input = """ - func foo() { - guard let self = self as? Foo else { - return - } - self.bar() - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInClosureInIfCondition() { - let input = """ - class Foo { - func foo() { - if bar({ self.baz() }) {} - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInTrailingClosureInVarAssignment() { - let input = """ - func broken() { - var bad = abc { - self.foo() - self.bar - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedWhenPropertyIsKeyword() { - let input = """ - class Foo { - let `default` = 5 - func foo() { - print(self.default) - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedWhenPropertyIsContextualKeyword() { - let input = """ - class Foo { - let `self` = 5 - func foo() { - print(self.self) - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfRemovedForContextualKeywordThatRequiresNoEscaping() { - let input = """ - class Foo { - let get = 5 - func foo() { - print(self.get) - } - } - """ - let output = """ - class Foo { - let get = 5 - func foo() { - print(get) - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveSelfForMemberNamedLazy() { - let input = "func foo() { self.lazy() }" - let output = "func foo() { lazy() }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveRedundantSelfInArrayLiteral() { - let input = """ - class Foo { - func foo() { - print([self.bar.x, self.bar.y]) - } - } - """ - let output = """ - class Foo { - func foo() { - print([bar.x, bar.y]) - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveRedundantSelfInArrayLiteralVar() { - let input = """ - class Foo { - func foo() { - var bars = [self.bar.x, self.bar.y] - print(bars) - } - } - """ - let output = """ - class Foo { - func foo() { - var bars = [bar.x, bar.y] - print(bars) - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemoveRedundantSelfInGuardLet() { - let input = """ - class Foo { - func foo() { - guard let bar = self.baz else { - return - } - } - } - """ - let output = """ - class Foo { - func foo() { - guard let bar = baz else { - return - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedInClosureInIf() { - let input = """ - if let foo = bar(baz: { [weak self] in - guard let self = self else { return } - _ = self.myVar - }) {} - """ - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["wrapConditionalBodies"]) - } - - func testStructSelfRemovedInTrailingClosureInIfCase() { - let input = """ - struct A { - func doSomething() { - B.method { mode in - if case .edit = mode { - self.doA() - } else { - self.doB() - } - } - } - - func doA() {} - func doB() {} - } - """ - let output = """ - struct A { - func doSomething() { - B.method { mode in - if case .edit = mode { - doA() - } else { - doB() - } - } - } - - func doA() {} - func doB() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: FormatOptions(swiftVersion: "5.8")) - } - - func testSelfNotRemovedInDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo { - subscript(dynamicMember foo: String) -> String { - foo + "bar" - } - - func bar() { - if self.foo == "foobar" { - return - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotRemovedInDeclarationWithDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo { - subscript(dynamicMember foo: String) -> String { - foo + "bar" - } - - func bar() { - let foo = self.foo - print(foo) - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotRemovedInExtensionOfTypeWithDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo {} - - extension Foo { - subscript(dynamicMember foo: String) -> String { - foo + "bar" - } - - func bar() { - if self.foo == "foobar" { - return - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfRemovedInNestedExtensionOfTypeWithDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo { - var foo: Int - struct Foo {} - extension Foo { - func bar() { - if self.foo == "foobar" { - return - } - } - } - } - """ - let output = """ - @dynamicMemberLookup - struct Foo { - var foo: Int - struct Foo {} - extension Foo { - func bar() { - if foo == "foobar" { - return - } - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: options) - } - - func testNoRemoveSelfAfterGuardCaseLetWithExplicitNamespace() { - let input = """ - class Foo { - var name: String? - - func bug(element: Something) { - guard case let Something.a(name) = element - else { return } - self.name = name - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["wrapConditionalBodies"]) - } - - func testNoRemoveSelfInAssignmentInsideIfAsStatement() { - let input = """ - if let foo = foo as? Foo, let bar = baz { - self.bar = bar - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testNoRemoveSelfInAssignmentInsideIfLetWithPostfixOperator() { - let input = """ - if let foo = baz?.foo, let bar = baz?.bar { - self.foo = foo - self.bar = bar - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfParsingBug() { - let input = """ - private class Foo { - mutating func bar() -> Statement? { - let start = self - guard case Token.identifier(let name)? = self.popFirst() else { - self = start - return nil - } - return Statement.declaration(name: name) - } - } - """ - let output = """ - private class Foo { - mutating func bar() -> Statement? { - let start = self - guard case Token.identifier(let name)? = popFirst() else { - self = start - return nil - } - return Statement.declaration(name: name) - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - exclude: ["hoistPatternLet"]) - } - - func testRedundantSelfParsingBug2() { - let input = """ - extension Foo { - private enum NonHashableEnum: RawRepresentable { - case foo - case bar - - var rawValue: RuntimeTypeTests.TestStruct { - return TestStruct(foo: 0) - } - - init?(rawValue: RuntimeTypeTests.TestStruct) { - switch rawValue.foo { - case 0: - self = .foo - case 1: - self = .bar - default: - return nil - } - } - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfParsingBug3() { - let input = """ - final class ViewController { - private func bottomBarModels() -> [BarModeling] { - if let url = URL(string: "..."){ - // ... - } - - models.append( - Footer.barModel( - content: FooterContent( - primaryTitleText: "..."), - style: style) - .setBehaviors { context in - context.view.primaryButtonState = self.isLoading ? .waiting : .normal - context.view.primaryActionHandler = { [weak self] _ in - self?.acceptButtonWasTapped() - } - }) - } - - } - """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) - } - - func testRedundantSelfParsingBug4() { - let input = """ - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let row: Row = promotionSections[indexPath.section][indexPath.row] else { return UITableViewCell() } - let cell = tableView.dequeueReusable(RowTableViewCell.self, forIndexPath: indexPath) - cell.update(row: row) - return cell - } - """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) - } - - func testRedundantSelfParsingBug5() { - let input = """ - Button.primary( - title: "Title", - tapHandler: { [weak self] in - self?.dismissBlock? { - // something - } - } - ) - """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) - } - - func testRedundantSelfParsingBug6() { - let input = """ - if let foo = bar, foo.tracking[jsonDict: "something"] != nil {} - """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) - } - - func testRedundantSelfWithStaticMethodAfterForLoop() { - let input = """ - struct Foo { - init() { - for foo in self.bar {} - } - - static func foo() {} - } - - """ - let output = """ - struct Foo { - init() { - for foo in bar {} - } - - static func foo() {} - } - - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfWithStaticMethodAfterForWhereLoop() { - let input = """ - struct Foo { - init() { - for foo in self.bar where !bar.isEmpty {} - } - - static func foo() {} - } - - """ - let output = """ - struct Foo { - init() { - for foo in bar where !bar.isEmpty {} - } - - static func foo() {} - } - - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfRuleDoesntErrorInForInTryLoop() { - let input = "for foo in try bar() {}" - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfInInitWithActorLabel() { - let input = """ - class Foo { - init(actor: Actor, bar: Bar) { - self.actor = actor - self.bar = bar - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfRuleFailsInGuardWithParenthesizedClosureAfterComma() { - let input = """ - guard let foo = bar, foo.bar(baz: { $0 }) else { - return nil - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testMinSelfNotRemoved() { - let input = """ - extension Array where Element: Comparable { - func foo() -> Int { - self.min() - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testMinSelfNotRemovedOnSwift5_4() { - let input = """ - extension Array where Element == Foo { - func smallest() -> Foo? { - let bar = self.min(by: { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - }) - return bar - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) - } - - func testDisableRedundantSelfDirective() { - let input = """ - func smallest() -> Foo? { - // swiftformat:disable:next redundantSelf - let bar = self.foo { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) - } - - func testDisableRedundantSelfDirective2() { - let input = """ - func smallest() -> Foo? { - let bar = - // swiftformat:disable:next redundantSelf - self.foo { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) - } - - func testSelfInsertDirective() { - let input = """ - func smallest() -> Foo? { - // swiftformat:options:next --self insert - let bar = self.foo { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) - } - - func testNoRemoveVariableShadowedLaterInScopeInOlderSwiftVersions() { - let input = """ - func foo() -> Bar? { - guard let baz = self.bar else { - return nil - } - - let bar = Foo() - return Bar(baz) - } - """ - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testStillRemoveVariableShadowedInSameDecalarationInOlderSwiftVersions() { - let input = """ - func foo() -> Bar? { - guard let bar = self.bar else { - return nil - } - return bar - } - """ - let output = """ - func foo() -> Bar? { - guard let bar = bar else { - return nil - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.0") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testShadowedSelfRemovedInGuardLet() { - let input = """ - func foo() { - guard let optional = self.optional else { - return - } - print(optional) - } - """ - let output = """ - func foo() { - guard let optional = optional else { - return - } - print(optional) - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testShadowedStringValueNotRemovedInInit() { - let input = """ - init() { - let value = "something" - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testShadowedIntValueNotRemovedInInit() { - let input = """ - init() { - let value = 5 - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testShadowedPropertyValueNotRemovedInInit() { - let input = """ - init() { - let value = foo - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testShadowedFuncCallValueNotRemovedInInit() { - let input = """ - init() { - let value = foo() - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testShadowedFuncParamRemovedInInit() { - let input = """ - init() { - let value = foo(self.value) - } - """ - let output = """ - init() { - let value = foo(value) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoRemoveSelfInMacro() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: self.__myVar) - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - // explicitSelf = .insert - - func testInsertSelf() { - let input = "class Foo {\n let foo: Int\n init() { foo = 5 }\n}" - let output = "class Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfInActor() { - let input = "actor Foo {\n let foo: Int\n init() { foo = 5 }\n}" - let output = "actor Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfAfterReturn() { - let input = "class Foo {\n let foo: Int\n func bar() -> Int { return foo }\n}" - let output = "class Foo {\n let foo: Int\n func bar() -> Int { return self.foo }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfInsideStringInterpolation() { - let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" - let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInterpretGenericTypesAsMembers() { - let input = "class Foo {\n let foo: Bar\n init() { self.foo = Int(5) }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfForStaticMemberInClassFunction() { - let input = "class Foo {\n static var foo: Int\n class func bar() { foo = 5 }\n}" - let output = "class Foo {\n static var foo: Int\n class func bar() { self.foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForInstanceMemberInClassFunction() { - let input = "class Foo {\n var foo: Int\n class func bar() { foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForStaticMemberInInstanceFunction() { - let input = "class Foo {\n static var foo: Int\n func bar() { foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForShadowedClassMemberInClassFunction() { - let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { foo = 5 }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInForLoopTuple() { - let input = "class Foo {\n var bar: Int\n func foo() { for (bar, baz) in quux {} }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForTupleTypeMembers() { - let input = "class Foo {\n var foo: (Int, UIColor) {\n let bar = UIColor.red\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForArrayElements() { - let input = "class Foo {\n var foo = [1, 2, nil]\n func bar() { baz(nil) }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForNestedVarReference() { - let input = "class Foo {\n func bar() {\n var bar = 5\n repeat { bar = 6 } while true\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["wrapLoopBodies"]) - } - - func testNoInsertSelfInSwitchCaseLet() { - let input = "class Foo {\n var foo: Bar? {\n switch bar {\n case let .baz(foo, _):\n return nil\n }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInFuncAfterImportedClass() { - let input = "import class Foo.Bar\nfunc foo() {\n var bar = 5\n if true {\n bar = 6\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, - exclude: ["blankLineAfterImports"]) - } - - func testNoInsertSelfForSubscriptGetSet() { - let input = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return get(key) }\n set { set(key, newValue) }\n }\n}" - let output = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return self.get(key) }\n set { self.set(key, newValue) }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInIfCaseLet() { - let input = "enum Foo {\n case bar(Int)\n var value: Int? {\n if case let .bar(value) = self { return value }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, - exclude: ["wrapConditionalBodies"]) - } - - func testNoInsertSelfForPatternLet() { - let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForPatternLet2() { - let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case let .foo(baz): print(baz)\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForTypeOf() { - let input = "class Foo {\n var type: String?\n func bar() {\n print(\"\\(type(of: self))\")\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForConditionalLocal() { - let input = "class Foo {\n func foo() {\n #if os(watchOS)\n var foo: Int\n #else\n var foo: Float\n #endif\n print(foo)\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfInExtension() { - let input = """ - struct Foo { - var bar = 5 - } - - extension Foo { - func baz() { - bar = 6 - } - } - """ - let output = """ - struct Foo { - var bar = 5 - } - - extension Foo { - func baz() { - self.bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testGlobalAfterTypeNotTreatedAsMember() { - let input = """ - struct Foo { - var foo = 1 - } - - var bar = 5 - - extension Foo { - func baz() { - bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testForWhereVarNotTreatedAsMember() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - for bar in self where bar.baz { - return bar - } - return nil - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSwitchCaseWhereVarNotTreatedAsMember() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar where bar.baz: - return bar - default: - return nil - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSwitchCaseVarDoesntLeak() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar: - return bar - default: - return bar - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar: - return bar - default: - return self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedInSwitchCaseLet() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo: - return bar - default: - return bar - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo: - return self.bar - default: - return self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedInSwitchCaseWhere() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where bar.baz: - return bar - default: - return bar - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where self.bar.baz: - return self.bar - default: - return self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedInDidSet() { - let input = """ - class Foo { - var bar = false { - didSet { - bar = !bar - } - } - } - """ - let output = """ - class Foo { - var bar = false { - didSet { - self.bar = !self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedAfterLet() { - let input = """ - struct Foo { - let foo = "foo" - func bar() { - let x = foo - baz(x) - } - - func baz(_: String) {} - } - """ - let output = """ - struct Foo { - let foo = "foo" - func bar() { - let x = self.foo - self.baz(x) - } - - func baz(_: String) {} - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotInsertedInParameterNames() { - let input = """ - class Foo { - let a: String - - func bar() { - foo(a: a) - } - } - """ - let output = """ - class Foo { - let a: String - - func bar() { - foo(a: self.a) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotInsertedInCaseLet() { - let input = """ - class Foo { - let a: String? - let b: String - - func bar() { - if case let .some(a) = self.a, case var .some(b) = self.b {} - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotInsertedInCaseLet2() { - let input = """ - class Foo { - let a: String? - let b: String - - func baz() { - if case let .foos(a, b) = foo, case let .bars(a, b) = bar {} - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedInTupleAssignment() { - let input = """ - class Foo { - let a: String? - let b: String - - func bar() { - (a, b) = ("foo", "bar") - } - } - """ - let output = """ - class Foo { - let a: String? - let b: String - - func bar() { - (self.a, self.b) = ("foo", "bar") - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotInsertedInTupleAssignment() { - let input = """ - class Foo { - let a: String? - let b: String - - func bar() { - let (a, b) = (self.a, self.b) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfForMemberNamedLazy() { - let input = """ - class Foo { - var lazy = "foo" - func foo() { - print(lazy) - } - } - """ - let output = """ - class Foo { - var lazy = "foo" - func foo() { - print(self.lazy) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForVarDefinedInIfCaseLet() { - let input = """ - struct A { - var localVar = "" - - var B: String { - if case let .c(localVar) = self.d, localVar == .e { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForVarDefinedInUnhoistedIfCaseLet() { - let input = """ - struct A { - var localVar = "" - - var B: String { - if case .c(let localVar) = self.d, localVar == .e { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, - exclude: ["hoistPatternLet"]) - } - - func testNoInsertSelfForVarDefinedInFor() { - let input = """ - struct A { - var localVar = "" - - var B: String { - for localVar in 0 ..< 6 where localVar < 5 { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfForVarDefinedInWhileLet() { - let input = """ - struct A { - var localVar = "" - - var B: String { - while let localVar = self.localVar, localVar < 5 { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInCaptureList() { - let input = """ - class Thing { - var a: String? { nil } - - func foo() { - let b = "" - { [weak a = b] _ in } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInCaptureList2() { - let input = """ - class Thing { - var a: String? { nil } - - func foo() { - { [weak a] _ in } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInCaptureList3() { - let input = """ - class A { - var thing: B? { fatalError() } - - func foo() { - let thing2 = B() - let _: (Bool) -> Void = { [weak thing = thing2] _ in - thing?.bar() - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testBodilessFunctionDoesntBreakParser() { - let input = """ - @_silgen_name("foo") - func foo(_: CFString, _: CFTypeRef) -> Int? - - enum Bar { - static func baz() { - fatalError() - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfBeforeSet() { - let input = """ - class Foo { - var foo: Bool - - var bar: Bool { - get { self.foo } - set { self.foo = newValue } - } - - required init() {} - - func set() {} - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInMacro() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: __myVar) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfBeforeBinding() { - let input = """ - struct MyView: View { - @Environment(ViewModel.self) var viewModel - - var body: some View { - @Bindable var viewModel = self.viewModel - ZStack { - MySubview( - navigationPath: $viewModel.navigationPath - ) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert, swiftVersion: "5.10") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - // explicitSelf = .initOnly - - func testPreserveSelfInsideClassInit() { - let input = """ - class Foo { - var bar = 5 - init() { - self.bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRemoveSelfIfNotInsideClassInit() { - let input = """ - class Foo { - var bar = 5 - func baz() { - self.bar = 6 - } - } - """ - let output = """ - class Foo { - var bar = 5 - func baz() { - bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testInsertSelfInsideClassInit() { - let input = """ - class Foo { - var bar = 5 - init() { - bar = 6 - } - } - """ - let output = """ - class Foo { - var bar = 5 - init() { - self.bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testNoInsertSelfInsideClassInitIfNotLvalue() { - let input = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - bar = baz - } - } - """ - let output = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - self.bar = baz - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testRemoveSelfInsideClassInitIfNotLvalue() { - let input = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - self.bar = self.baz - } - } - """ - let output = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - self.bar = baz - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfDotTypeInsideClassInitEdgeCase() { - let input = """ - class Foo { - let type: Int - - init() { - self.type = 5 - } - - func baz() { - switch type {} - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedInTupleInInit() { - let input = """ - class Foo { - let a: String? - let b: String - - init() { - (a, b) = ("foo", "bar") - } - } - """ - let output = """ - class Foo { - let a: String? - let b: String - - init() { - (self.a, self.b) = ("foo", "bar") - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfInsertedAfterLetInInit() { - let input = """ - class Foo { - var foo: String - init(bar: Bar) { - let baz = bar.quux - foo = baz - } - } - """ - let output = """ - class Foo { - var foo: String - init(bar: Bar) { - let baz = bar.quux - self.foo = baz - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRuleDoesntErrorForStaticFuncInProtocolWithWhere() { - let input = """ - protocol Foo where Self: Bar { - static func baz() -> Self - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRuleDoesntErrorForStaticFuncInStructWithWhere() { - let input = """ - struct Foo where T: Bar { - static func baz() -> Foo {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRuleDoesntErrorForClassFuncInClassWithWhere() { - let input = """ - class Foo where T: Bar { - class func baz() -> Foo {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfRuleFailsInInitOnlyMode() { - let input = """ - class Foo { - func foo() -> Foo? { - guard let bar = { nil }() else { - return nil - } - } - - static func baz() -> String? {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantClosure"]) - } - - func testRedundantSelfRuleFailsInInitOnlyMode2() { - let input = """ - struct Mesh { - var storage: Storage - init(vertices: [Vertex]) { - let isConvex = pointsAreConvex(vertices) - storage = Storage(vertices: vertices) - } - } - """ - let output = """ - struct Mesh { - var storage: Storage - init(vertices: [Vertex]) { - let isConvex = pointsAreConvex(vertices) - self.storage = Storage(vertices: vertices) - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: options) - } - - func testSelfNotRemovedInInitForSwift5_4() { - let input = """ - init() { - let foo = 1234 - self.bar = foo - } - """ - let options = FormatOptions(explicitSelf: .initOnly, swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testPropertyInitNotInterpretedAsTypeInit() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: __myVar) - init(initialValue) { - __myVar = initialValue - } - set { - __myVar = newValue - } - get { - __myVar - } - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testPropertyInitNotInterpretedAsTypeInit2() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: __myVar) - init { - __myVar = newValue - } - set { - __myVar = newValue - } - get { - __myVar - } - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - // parsing bugs - - func testSelfRemovalParsingBug() { - let input = """ - extension Dictionary where Key == String { - func requiredValue(for keyPath: String) throws -> T { - return keyPath as! T - } - - func optionalValue(for keyPath: String) throws -> T? { - guard let anyValue = self[keyPath] else { - return nil - } - guard let value = anyValue as? T else { - return nil - } - return value - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfRemovalParsingBug2() { - let input = """ - if let test = value()["hi"] { - print("hi") - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfRemovalParsingBug3() { - let input = """ - func handleGenericError(_ error: Error) { - if let requestableError = error as? RequestableError, - case let .underlying(error as NSError) = requestableError, - error.code == NSURLErrorNotConnectedToInternet - {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfRemovalParsingBug4() { - let input = """ - struct Foo { - func bar() { - for flag in [] where [].filter({ true }) {} - } - - static func baz() {} - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfRemovalParsingBug5() { - let input = """ - extension Foo { - func method(foo: Bar) { - self.foo = foo - - switch foo { - case let .foo(bar): - closure { - Foo.draw() - } - } - } - - private static func draw() {} - } - """ - - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfRemovalParsingBug6() { - let input = """ - something.do(onSuccess: { result in - if case .success((let d, _)) = result { - self.relay.onNext(d) - } - }) - """ - testFormatting(for: input, rule: FormatRules.redundantSelf, - exclude: ["hoistPatternLet"]) - } - - func testSelfRemovalParsingBug7() { - let input = """ - extension Dictionary where Key == String { - func requiredValue(for keyPath: String) throws(Foo) -> T { - return keyPath as! T - } - - func optionalValue(for keyPath: String) throws(Foo) -> T? { - guard let anyValue = self[keyPath] else { - return nil - } - guard let value = anyValue as? T else { - return nil - } - return value - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfNotRemovedInCaseIfElse() { - let input = """ - class Foo { - let bar = true - let someOptionalBar: String? = "bar" - - func test() { - guard let bar: String = someOptionalBar else { - return - } - - let result = Result.success(bar) - switch result { - case let .success(value): - if self.bar { - if self.bar { - print(self.bar) - } - } else { - if self.bar { - print(self.bar) - } - } - - case .failure: - if self.bar { - print(self.bar) - } - } - } - } - """ - - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testSelfCallAfterIfStatementInSwitchStatement() { - let input = """ - closure { [weak self] in - guard let self else { - return - } - - switch result { - case let .success(value): - if value != nil { - if value != nil { - self.method() - } - } - self.method() - - case .failure: - break - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testSelfNotRemovedFollowingNestedSwitchStatements() { - let input = """ - class Foo { - let bar = true - let someOptionalBar: String? = "bar" - - func test() { - guard let bar: String = someOptionalBar else { - return - } - - let result = Result.success(bar) - switch result { - case let .success(value): - switch result { - case .success: - print("success") - case .value: - print("value") - } - - case .failure: - guard self.bar else { - print(self.bar) - return - } - print(self.bar) - } - } - } - """ - - testFormatting(for: input, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfWithStaticAsyncSendableClosureFunction() { - let input = """ - class Foo: Bar { - static func bar( - _ closure: @escaping @Sendable () async -> Foo - ) -> @Sendable () async -> Foo { - self.foo = closure - return closure - } - - static func bar() {} - } - """ - let output = """ - class Foo: Bar { - static func bar( - _ closure: @escaping @Sendable () async -> Foo - ) -> @Sendable () async -> Foo { - foo = closure - return closure - } - - static func bar() {} - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - // enable/disable - - func testDisableRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantSelf - self.bar = 1 - // swiftformat:enable redundantSelf - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantSelf - self.bar = 1 - // swiftformat:enable redundantSelf - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testDisableRemoveSelfCaseInsensitive() { - let input = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantself - self.bar = 1 - // swiftformat:enable RedundantSelf - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantself - self.bar = 1 - // swiftformat:enable RedundantSelf - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testDisableNextRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable:next redundantSelf - self.bar = 1 - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable:next redundantSelf - self.bar = 1 - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testMultilineDisableRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testMultilineDisableNextRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable:next redundantSelf */ - self.bar = 1 - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable:next redundantSelf */ - self.bar = 1 - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRemovesSelfInNestedFunctionInStrongSelfClosure() { - let input = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [self] in - doWork { - // Not allowed. Warning in Swift 5 and error in Swift 6. - self.test() - } - - func innerFunc() { - // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 - self.test() - } - - innerFunc() - } - } - } - """ - - let output = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [self] in - doWork { - // Not allowed. Warning in Swift 5 and error in Swift 6. - self.test() - } - - func innerFunc() { - // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 - test() - } - - innerFunc() - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: FormatOptions(swiftVersion: "5.8")) - } - - func testPreservesSelfInNestedFunctionInWeakSelfClosure() { - let input = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [weak self] in - func innerFunc() { - self?.test() - } - - guard let self else { - return - } - - self.test() - - func innerFunc() { - self.test() - } - - self.test() - } - } - } - """ - - let output = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [weak self] in - func innerFunc() { - self?.test() - } - - guard let self else { - return - } - - test() - - func innerFunc() { - self.test() - } - - test() - } - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.redundantSelf, - options: FormatOptions(swiftVersion: "5.8")) - } - - func testRedundantSelfAfterScopedImport() { - let input = """ - import struct Foundation.Date - - struct Foo { - let foo: String - init(bar: String) { - self.foo = bar - } - } - """ - let output = """ - import struct Foundation.Date - - struct Foo { - let foo: String - init(bar: String) { - foo = bar - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) - } - - func testRedundantSelfNotConfusedByParameterPack() { - let input = """ - func pairUp(firstPeople: repeat each T, secondPeople: repeat each U) -> (repeat (first: each T, second: each U)) { - (repeat (each firstPeople, each secondPeople)) - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - func testRedundantSelfNotConfusedByStaticAfterSwitch() { - let input = """ - public final class MyClass { - private static func privateStaticFunction1() -> Bool { - switch Result(catching: { try someThrowingFunction() }) { - case .success: - return true - case .failure: - return false - } - } - - private static func privateStaticFunction2() -> Bool { - return false - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["enumNamespaces"]) - } - - func testRedundantSelfNotConfusedByMainActor() { - let input = """ - class Test { - private var p: Int - - func f() { - self.f2( - closure: { @MainActor [weak self] p in - print(p) - } - ) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) - } - - // MARK: - redundantStaticSelf - - func testRedundantStaticSelfInStaticVar() { - let input = "enum E { static var x: Int { Self.y } }" - let output = "enum E { static var x: Int { y } }" - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testRedundantStaticSelfInStaticMethod() { - let input = "enum E { static func foo() { Self.bar() } }" - let output = "enum E { static func foo() { bar() } }" - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testRedundantStaticSelfOnNextLine() { - let input = """ - enum E { - static func foo() { - Self - .bar() - } - } - """ - let output = """ - enum E { - static func foo() { - bar() - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testRedundantStaticSelfWithReturn() { - let input = "enum E { static func foo() { return Self.bar() } }" - let output = "enum E { static func foo() { return bar() } }" - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testRedundantStaticSelfInConditional() { - let input = """ - enum E { - static func foo() { - if Bool.random() { - Self.bar() - } - } - } - """ - let output = """ - enum E { - static func foo() { - if Bool.random() { - bar() - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testRedundantStaticSelfInNestedFunction() { - let input = """ - enum E { - static func foo() { - func bar() { - Self.foo() - } - } - } - """ - let output = """ - enum E { - static func foo() { - func bar() { - foo() - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testRedundantStaticSelfInNestedType() { - let input = """ - enum Outer { - enum Inner { - static func foo() {} - static func bar() { Self.foo() } - } - } - """ - let output = """ - enum Outer { - enum Inner { - static func foo() {} - static func bar() { foo() } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testStaticSelfNotRemovedWhenUsedAsImplicitInitializer() { - let input = "enum E { static func foo() { Self().bar() } }" - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) - } - - func testStaticSelfNotRemovedWhenUsedAsExplicitInitializer() { - let input = "enum E { static func foo() { Self.init().bar() } }" - testFormatting(for: input, rule: FormatRules.redundantStaticSelf, exclude: ["redundantInit"]) - } - - func testPreservesStaticSelfInFunctionAfterStaticVar() { - let input = """ - enum MyFeatureCacheStrategy { - case networkOnly - case cacheFirst - - static let defaultCacheAge = TimeInterval.minutes(5) - - func requestStrategy() -> SingleRequestStrategy { - switch self { - case .networkOnly: - return .networkOnly(writeResultToCache: true) - case .cacheFirst: - return .cacheFirst(maxCacheAge: Self.defaultCacheAge) - } - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf, exclude: ["propertyType"]) - } - - func testPreserveStaticSelfInInstanceFunction() { - let input = """ - enum Foo { - static var value = 0 - - func f() { - Self.value = value - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) - } - - func testPreserveStaticSelfForShadowedProperty() { - let input = """ - enum Foo { - static var value = 0 - - static func f(value: Int) { - Self.value = value - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) - } - - func testPreserveStaticSelfInGetter() { - let input = """ - enum Foo { - static let foo: String = "foo" - - var sharedFoo: String { - Self.foo - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) - } - - func testRemoveStaticSelfInStaticGetter() { - let input = """ - public enum Foo { - static let foo: String = "foo" - - static var getFoo: String { - Self.foo - } - } - """ - let output = """ - public enum Foo { - static let foo: String = "foo" - - static var getFoo: String { - foo - } - } - """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) - } - - func testPreserveStaticSelfInGuardLet() { - let input = """ - class LocationDeeplink: Deeplink { - convenience init?(warnRegion: String) { - guard let value = Self.location(for: warnRegion) else { - return nil - } - self.init(location: value) - } - } - """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) - } - - func testPreserveStaticSelfInSingleLineClassInit() { - let input = """ - class A { static let defaultName = "A"; let name: String; init() { name = Self.defaultName }} - """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) - } - - // MARK: - semicolons - - func testSemicolonRemovedAtEndOfLine() { - let input = "print(\"hello\");\n" - let output = "print(\"hello\")\n" - testFormatting(for: input, output, rule: FormatRules.semicolons) - } - - func testSemicolonRemovedAtStartOfLine() { - let input = "\n;print(\"hello\")" - let output = "\nprint(\"hello\")" - testFormatting(for: input, output, rule: FormatRules.semicolons) - } - - func testSemicolonRemovedAtEndOfProgram() { - let input = "print(\"hello\");" - let output = "print(\"hello\")" - testFormatting(for: input, output, rule: FormatRules.semicolons) - } - - func testSemicolonRemovedAtStartOfProgram() { - let input = ";print(\"hello\")" - let output = "print(\"hello\")" - testFormatting(for: input, output, rule: FormatRules.semicolons) - } - - func testIgnoreInlineSemicolon() { - let input = "print(\"hello\"); print(\"goodbye\")" - let options = FormatOptions(allowInlineSemicolons: true) - testFormatting(for: input, rule: FormatRules.semicolons, options: options) - } - - func testReplaceInlineSemicolon() { - let input = "print(\"hello\"); print(\"goodbye\")" - let output = "print(\"hello\")\nprint(\"goodbye\")" - let options = FormatOptions(allowInlineSemicolons: false) - testFormatting(for: input, output, rule: FormatRules.semicolons, options: options) - } - - func testReplaceSemicolonFollowedByComment() { - let input = "print(\"hello\"); // comment\nprint(\"goodbye\")" - let output = "print(\"hello\") // comment\nprint(\"goodbye\")" - let options = FormatOptions(allowInlineSemicolons: true) - testFormatting(for: input, output, rule: FormatRules.semicolons, options: options) - } - - func testSemicolonNotReplacedAfterReturn() { - let input = "return;\nfoo()" - testFormatting(for: input, rule: FormatRules.semicolons) - } - - func testSemicolonReplacedAfterReturnIfEndOfScope() { - let input = "do { return; }" - let output = "do { return }" - testFormatting(for: input, output, rule: FormatRules.semicolons) - } - - func testRequiredSemicolonNotRemovedAfterInferredVar() { - let input = """ - func foo() { - @Environment(\\.colorScheme) var colorScheme; - print(colorScheme) - } - """ - testFormatting(for: input, rule: FormatRules.semicolons) - } - - // MARK: - duplicateImports - - func testRemoveDuplicateImport() { - let input = "import Foundation\nimport Foundation" - let output = "import Foundation" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - func testRemoveDuplicateConditionalImport() { - let input = "#if os(iOS)\n import Foo\n import Foo\n#else\n import Bar\n import Bar\n#endif" - let output = "#if os(iOS)\n import Foo\n#else\n import Bar\n#endif" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - func testNoRemoveOverlappingImports() { - let input = "import MyModule\nimport MyModule.Private" - testFormatting(for: input, rule: FormatRules.duplicateImports) - } - - func testNoRemoveCaseDifferingImports() { - let input = "import Auth0.Authentication\nimport Auth0.authentication" - testFormatting(for: input, rule: FormatRules.duplicateImports) - } - - func testRemoveDuplicateImportFunc() { - let input = "import func Foo.bar\nimport func Foo.bar" - let output = "import func Foo.bar" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - func testNoRemoveTestableDuplicateImport() { - let input = "import Foo\n@testable import Foo" - let output = "\n@testable import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - func testNoRemoveTestableDuplicateImport2() { - let input = "@testable import Foo\nimport Foo" - let output = "@testable import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - func testNoRemoveExportedDuplicateImport() { - let input = "import Foo\n@_exported import Foo" - let output = "\n@_exported import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - func testNoRemoveExportedDuplicateImport2() { - let input = "@_exported import Foo\nimport Foo" - let output = "@_exported import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) - } - - // MARK: - unusedArguments - - // closures - - func testUnusedTypedClosureArguments() { - let input = "let foo = { (bar: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" - let output = "let foo = { (_: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedUntypedClosureArguments() { - let input = "let foo = { bar, baz in\n print(\"Hello \\(baz)\")\n}" - let output = "let foo = { _, baz in\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testNoRemoveClosureReturnType() { - let input = "let foo = { () -> Foo.Bar in baz() }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testNoRemoveClosureThrows() { - let input = "let foo = { () throws in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testNoRemoveClosureTypedThrows() { - let input = "let foo = { () throws(Foo) in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testNoRemoveClosureGenericReturnTypes() { - let input = "let foo = { () -> Promise in bar }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testNoRemoveClosureTupleReturnTypes() { - let input = "let foo = { () -> (Int, Int) in (5, 6) }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testNoRemoveClosureGenericArgumentTypes() { - let input = "let foo = { (_: Foo) in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testNoRemoveFunctionNameBeforeForLoop() { - let input = "{\n func foo() -> Int {}\n for a in b {}\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testClosureTypeInClosureArgumentsIsNotMangled() { - let input = "{ (foo: (Int) -> Void) in }" - let output = "{ (_: (Int) -> Void) in }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedUnnamedClosureArguments() { - let input = "{ (_ foo: Int, _ bar: Int) in }" - let output = "{ (_: Int, _: Int) in }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedInoutClosureArgumentsNotMangled() { - let input = "{ (foo: inout Foo, bar: inout Bar) in }" - let output = "{ (_: inout Foo, _: inout Bar) in }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testMalformedFunctionNotMisidentifiedAsClosure() { - let input = "func foo() { bar(5) {} in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArguments() { - let input = """ - forEach { foo, bar in - guard let foo = foo, let bar = bar else { - return - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedPartUsedArguments() { - let input = """ - forEach { foo, bar in - guard let foo = baz, bar == baz else { - return - } - } - """ - let output = """ - forEach { _, bar in - guard let foo = baz, bar == baz else { - return - } - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testShadowedParameterUsedInSameGuard() { - let input = """ - forEach { foo in - guard let foo = bar, baz = foo else { - return - } - } - """ - let output = """ - forEach { _ in - guard let foo = bar, baz = foo else { - return - } - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testParameterUsedInForIn() { - let input = """ - forEach { foos in - for foo in foos { - print(foo) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testParameterUsedInWhereClause() { - let input = """ - forEach { foo in - if bar where foo { - print(bar) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testParameterUsedInSwitchCase() { - let input = """ - forEach { foo in - switch bar { - case let baz: - foo = baz - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testParameterUsedInStringInterpolation() { - let input = """ - forEach { foo in - print("\\(foo)") - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedClosureArgument() { - let input = """ - _ = Parser { input in - let parser = Parser.with(input) - return parser - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty", "propertyType"]) - } - - func testShadowedClosureArgument2() { - let input = """ - _ = foo { input in - let input = ["foo": "Foo", "bar": "Bar"][input] - return input - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) - } - - func testUnusedPropertyWrapperArgument() { - let input = """ - ForEach($list.notes) { $note in - Text(note.foobar) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedThrowingClosureArgument() { - let input = "foo = { bar throws in \"\" }" - let output = "foo = { _ throws in \"\" }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedTypedThrowingClosureArgument() { - let input = "foo = { bar throws(Foo) in \"\" }" - let output = "foo = { _ throws(Foo) in \"\" }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUsedThrowingClosureArgument() { - let input = "let foo = { bar throws in bar + \"\" }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUsedTypedThrowingClosureArgument() { - let input = "let foo = { bar throws(Foo) in bar + \"\" }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedTrailingAsyncClosureArgument() { - let input = """ - app.get { foo async in - print("No foo") - } - """ - let output = """ - app.get { _ async in - print("No foo") - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedTrailingAsyncClosureArgument2() { - let input = """ - app.get { foo async -> String in - "No foo" - } - """ - let output = """ - app.get { _ async -> String in - "No foo" - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedTrailingAsyncClosureArgument3() { - let input = """ - app.get { (foo: String) async -> String in - "No foo" - } - """ - let output = """ - app.get { (_: String) async -> String in - "No foo" - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUsedTrailingAsyncClosureArgument() { - let input = """ - app.get { foo async -> String in - "\\(foo)" - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testTrailingAsyncClosureArgumentAlreadyMarkedUnused() { - let input = "app.get { _ async in 5 }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedTrailingClosureArgumentCalledAsync() { - let input = """ - app.get { async -> String in - "No async" - } - """ - let output = """ - app.get { _ -> String in - "No async" - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testClosureArgumentUsedInGuardNotRemoved() { - let input = """ - bar(for: quux) { _, _, foo in - guard - let baz = quux.baz, - foo.contains(where: { $0.baz == baz }) - else { - return - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testClosureArgumentUsedInIfNotRemoved() { - let input = """ - foo = { reservations, _ in - if let reservations, eligibleToShow( - reservations, - accountService: accountService - ) { - coordinator.startFlow() - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - // init - - func testParameterUsedInInit() { - let input = """ - init(m: Rotation) { - let x = sqrt(max(0, m)) / 2 - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedParametersShadowedInTupleAssignment() { - let input = """ - init(x: Int, y: Int, v: Vector) { - let (x, y) = v - } - """ - let output = """ - init(x _: Int, y _: Int, v: Vector) { - let (x, y) = v - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUsedParametersShadowedInAssignmentFromFunctionCall() { - let input = """ - init(r: Double) { - let r = max(abs(r), epsilon) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArgumentInSwitch() { - let input = """ - init(_ action: Action, hub: Hub) { - switch action { - case let .get(hub, key): - self = .get(key, hub) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testParameterUsedInSwitchCaseAfterShadowing() { - let input = """ - func issue(name: String) -> String { - switch self { - case .b(let name): return name - case .a: return name - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, - exclude: ["hoistPatternLet"]) - } - - // functions - - func testMarkUnusedFunctionArgument() { - let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - let output = "func foo(bar _: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testMarkUnusedArgumentsInNonVoidFunction() { - let input = "func foo(bar: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" - let output = "func foo(bar _: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testMarkUnusedArgumentsInThrowsFunction() { - let input = "func foo(bar: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" - let output = "func foo(bar _: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testMarkUnusedArgumentsInOptionalReturningFunction() { - let input = "func foo(bar: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" - let output = "func foo(bar _: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testNoMarkUnusedArgumentsInProtocolFunction() { - let input = "protocol Foo {\n func foo(bar: Int) -> Int\n var bar: Int { get }\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedUnnamedFunctionArgument() { - let input = "func foo(_ foo: Int) {}" - let output = "func foo(_: Int) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedInoutFunctionArgumentIsNotMangled() { - let input = "func foo(_ foo: inout Foo) {}" - let output = "func foo(_: inout Foo) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedInternallyRenamedFunctionArgument() { - let input = "func foo(foo bar: Int) {}" - let output = "func foo(foo _: Int) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testNoMarkProtocolFunctionArgument() { - let input = "func foo(foo bar: Int)\nvar bar: Bool { get }" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testMembersAreNotArguments() { - let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(bar.baz)\")\n}" - let output = "func foo(bar: Int, baz _: String) {\n print(\"Hello \\(bar.baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testLabelsAreNotArguments() { - let input = "func foo(bar: Int, baz: String) {\n bar: while true { print(baz) }\n}" - let output = "func foo(bar _: Int, baz: String) {\n bar: while true { print(baz) }\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments, exclude: ["wrapLoopBodies"]) - } - - func testDictionaryLiteralsRuinEverything() { - let input = "func foo(bar: Int, baz: Int) {\n let quux = [bar: 1, baz: 2]\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testOperatorArgumentsAreUnnamed() { - let input = "func == (lhs: Int, rhs: Int) { false }" - let output = "func == (_: Int, _: Int) { false }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedtFailableInitArgumentsAreNotMangled() { - let input = "init?(foo: Bar) {}" - let output = "init?(foo _: Bar) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testTreatEscapedArgumentsAsUsed() { - let input = "func foo(default: Int) -> Int {\n return `default`\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testPartiallyMarkedUnusedArguments() { - let input = "func foo(bar: Bar, baz _: Baz) {}" - let output = "func foo(bar _: Bar, baz _: Baz) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testPartiallyMarkedUnusedArguments2() { - let input = "func foo(bar _: Bar, baz: Baz) {}" - let output = "func foo(bar _: Bar, baz _: Baz) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnownedUnsafeNotStripped() { - let input = """ - func foo() { - var num = 0 - Just(1) - .sink { [unowned(unsafe) self] in - num += $0 - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUnusedArguments() { - let input = """ - func foo(bar: String, baz: Int) { - let bar = "bar", baz = 5 - print(bar, baz) - } - """ - let output = """ - func foo(bar _: String, baz _: Int) { - let bar = "bar", baz = 5 - print(bar, baz) - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArguments2() { - let input = """ - func foo(things: [String], form: Form) { - let form = FormRequest( - things: things, - form: form - ) - print(form) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArguments3() { - let input = """ - func zoomTo(locations: [Foo], count: Int) { - let num = count - guard num > 0, locations.count >= count else { - return - } - print(locations) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArguments4() { - let input = """ - func foo(bar: Int) { - if let bar = baz { - return - } - print(bar) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArguments5() { - let input = """ - func doSomething(with number: Int) { - if let number = Int?(123), - number == 456 - { - print("Not likely") - } - - if number == 180 { - print("Bullseye!") - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedUsedArgumentInSwitchCase() { - let input = """ - func foo(bar baz: Foo) -> Foo? { - switch (a, b) { - case (0, _), - (_, nil): - return .none - case let (1, baz?): - return .bar(baz) - default: - return baz - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, - exclude: ["sortSwitchCases"]) - } - - func testTryArgumentNotMarkedUnused() { - let input = """ - func foo(bar: String) throws -> String? { - let bar = - try parse(bar) - return bar - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) - } - - func testTryAwaitArgumentNotMarkedUnused() { - let input = """ - func foo(bar: String) async throws -> String? { - let bar = try - await parse(bar) - return bar - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) - } - - func testTypedTryAwaitArgumentNotMarkedUnused() { - let input = """ - func foo(bar: String) async throws(Foo) -> String? { - let bar = try - await parse(bar) - return bar - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) - } - - func testConditionalIfLetMarkedAsUnused() { - let input = """ - func foo(bar: UIViewController) { - if let bar = baz { - bar.loadViewIfNeeded() - } - } - """ - let output = """ - func foo(bar _: UIViewController) { - if let bar = baz { - bar.loadViewIfNeeded() - } - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testConditionAfterIfCaseHoistedLetNotMarkedUnused() { - let input = """ - func isLoadingFirst(for tabID: String) -> Bool { - if case let .loading(.first(loadingTabID, _)) = requestState.status, loadingTabID == tabID { - return true - } else { - return false - } - - print(tabID) - } - """ - let options = FormatOptions(hoistPatternLet: true) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused2() { - let input = """ - func isLoadingFirst(for tabID: String) -> Bool { - if case .loading(.first(let loadingTabID, _)) = requestState.status, loadingTabID == tabID { - return true - } else { - return false - } - - print(tabID) - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused3() { - let input = """ - private func isFocusedView(formDataID: FormDataID) -> Bool { - guard - case .selected(let selectedFormDataID) = currentState.selectedFormItemAction, - selectedFormDataID == formDataID - else { - return false - } - - return true - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused4() { - let input = """ - private func totalRowContent(priceItemsCount: Int, priceBreakdownStyle: PriceBreakdownStyle) { - if - case .all(let shouldCollapseByDefault, _) = priceBreakdownStyle, - priceItemsCount > 0 - { - // .. - } - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused5() { - let input = """ - private mutating func clearPendingRemovals(itemIDs: Set) { - for change in changes { - if case .removal(itemID: let itemID) = change, !itemIDs.contains(itemID) { - // .. - } - } - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - func testSecondConditionAfterTupleMarkedUnused() { - let input = """ - func foobar(bar: Int) { - let (foo, baz) = (1, 2), bar = 3 - print(foo, bar, baz) - } - """ - let output = """ - func foobar(bar _: Int) { - let (foo, baz) = (1, 2), bar = 3 - print(foo, bar, baz) - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedParamsInTupleAssignment() { - let input = """ - func foobar(_ foo: Int, _ bar: Int, _ baz: Int, _ quux: Int) { - let ((foo, bar), baz) = ((foo, quux), bar) - print(foo, bar, baz, quux) - } - """ - let output = """ - func foobar(_ foo: Int, _ bar: Int, _: Int, _ quux: Int) { - let ((foo, bar), baz) = ((foo, quux), bar) - print(foo, bar, baz, quux) - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testShadowedIfLetNotMarkedAsUnused() { - let input = """ - func method(_ foo: Int?, _ bar: String?) { - if let foo = foo, let bar = bar {} - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShorthandIfLetNotMarkedAsUnused() { - let input = """ - func method(_ foo: Int?, _ bar: String?) { - if let foo, let bar {} - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShorthandLetMarkedAsUnused() { - let input = """ - func method(_ foo: Int?, _ bar: Int?) { - var foo, bar: Int? - } - """ - let output = """ - func method(_: Int?, _: Int?) { - var foo, bar: Int? - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testShadowedClosureNotMarkedUnused() { - let input = """ - func foo(bar: () -> Void) { - let bar = { - print("log") - bar() - } - bar() - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testShadowedClosureMarkedUnused() { - let input = """ - func foo(bar: () -> Void) { - let bar = { - print("log") - } - bar() - } - """ - let output = """ - func foo(bar _: () -> Void) { - let bar = { - print("log") - } - bar() - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testViewBuilderAnnotationDoesntBreakUnusedArgDetection() { - let input = """ - struct Foo { - let content: View - - public init( - responsibleFileID: StaticString = #fileID, - @ViewBuilder content: () -> View) - { - self.content = content() - } - } - """ - let output = """ - struct Foo { - let content: View - - public init( - responsibleFileID _: StaticString = #fileID, - @ViewBuilder content: () -> View) - { - self.content = content() - } - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments, - exclude: ["braces", "wrapArguments"]) - } - - func testArgumentUsedInDictionaryLiteral() { - let input = """ - class MyClass { - func testMe(value: String) { - let value = [ - "key": value - ] - print(value) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, - exclude: ["trailingCommas"]) - } - - func testArgumentUsedAfterIfDefInsideSwitchBlock() { - let input = """ - func test(string: String) { - let number = 5 - switch number { - #if DEBUG - case 1: - print("ONE") - #endif - default: - print("NOT ONE") - } - print(string) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUsedConsumingArgument() { - let input = """ - func close(file: consuming FileHandle) { - file.close() - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) - } - - func testUsedConsumingBorrowingArguments() { - let input = """ - func foo(a: consuming Foo, b: borrowing Bar) { - consume(a) - borrow(b) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) - } - - func testUnusedConsumingArgument() { - let input = """ - func close(file: consuming FileHandle) { - print("no-op") - } - """ - let output = """ - func close(file _: consuming FileHandle) { - print("no-op") - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) - } - - func testUnusedConsumingBorrowingArguments() { - let input = """ - func foo(a: consuming Foo, b: borrowing Bar) { - print("no-op") - } - """ - let output = """ - func foo(a _: consuming Foo, b _: borrowing Bar) { - print("no-op") - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) - } - - func testFunctionArgumentUsedInGuardNotRemoved() { - let input = """ - func scrollViewDidEndDecelerating(_ visibleDayRange: DayRange) { - guard - store.state.request.isIdle, - let nextDayToLoad = store.state.request.nextCursor?.lowerBound, - visibleDayRange.upperBound.distance(to: nextDayToLoad) < 30 - else { - return - } - - store.handle(.loadNext) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testFunctionArgumentUsedInGuardNotRemoved2() { - let input = """ - func convert( - filter: Filter, - accounts: [Account], - outgoingTotal: MulticurrencyTotal? - ) -> History? { - guard - let firstParameter = incomingTotal?.currency, - let secondParameter = outgoingTotal?.currency, - isFilter(filter, accounts: accounts) - else { - return nil - } - return History(firstParameter, secondParameter) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testFunctionArgumentUsedInGuardNotRemoved3() { - let input = """ - public func flagMessage(_ message: Message) { - model.withState { state in - guard - let flagMessageFeature, - shouldAllowFlaggingMessage( - message, - thread: state.thread) - else { return } - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, - exclude: ["wrapArguments", "wrapConditionalBodies", "indent"]) - } - - // functions (closure-only) - - func testNoMarkFunctionArgument() { - let input = "func foo(_ bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - let options = FormatOptions(stripUnusedArguments: .closureOnly) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - // functions (unnamed-only) - - func testNoMarkNamedFunctionArgument() { - let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - func testRemoveUnnamedFunctionArgument() { - let input = "func foo(_ foo: Int) {}" - let output = "func foo(_: Int) {}" - let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, output, rule: FormatRules.unusedArguments, options: options) - } - - func testNoRemoveInternalFunctionArgumentName() { - let input = "func foo(foo bar: Int) {}" - let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) - } - - // init - - func testMarkUnusedInitArgument() { - let input = "init(bar: Int, baz: String) {\n self.baz = baz\n}" - let output = "init(bar _: Int, baz: String) {\n self.baz = baz\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - // subscript - - func testMarkUnusedSubscriptArgument() { - let input = "subscript(foo: Int, baz: String) -> String {\n return get(baz)\n}" - let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testMarkUnusedUnnamedSubscriptArgument() { - let input = "subscript(_ foo: Int, baz: String) -> String {\n return get(baz)\n}" - let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testMarkUnusedNamedSubscriptArgument() { - let input = "subscript(foo foo: Int, baz: String) -> String {\n return get(baz)\n}" - let output = "subscript(foo _: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) - } - - func testUnusedArgumentWithClosureShadowingParamName() { - let input = """ - func test(foo: Foo) { - let foo = { - if foo.bar { - baaz - } else { - bar - } - }() - print(foo) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedArgumentWithConditionalAssignmentShadowingParamName() { - let input = """ - func test(foo: Foo) { - let foo = - if foo.bar { - baaz - } else { - bar - } - print(foo) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedArgumentWithSwitchAssignmentShadowingParamName() { - let input = """ - func test(foo: Foo) { - let foo = - switch foo.bar { - case true: - baaz - case false: - bar - } - print(foo) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testUnusedArgumentWithConditionalAssignmentNotShadowingParamName() { - let input = """ - func test(bar: Bar) { - let quux = - if foo { - bar - } else { - baaz - } - print(quux) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testIssue1688_1() { - let input = #""" - func urlTestContains(path: String, strict _: Bool = true) -> Bool { - let path = if path.hasSuffix("/") { path } else { "\(path)/" } - - return false - } - """# - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["wrapConditionalBodies"]) - } - - func testIssue1688_2() { - let input = """ - enum Sample { - func invite(lang: String, randomValue: Int) -> String { - let flag: String? = if randomValue > 0 { "hello" } else { nil } - - let lang = if let flag { flag } else { lang } - - return lang - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["wrapConditionalBodies", "redundantProperty"]) - } - - func testIssue1694() { - let input = """ - listenForUpdates() { [weak self] update, error in - guard let update, error == nil else { - return - } - self?.configure(update) - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantParens"]) - } - - func testIssue1696() { - let input = """ - func someFunction(with parameter: Int) -> Int { - let parameter = max( - 200, - parameter - ) - return parameter - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) - } - - func testArgumentUsedInsideMultilineStringLiteral() { - // https://github.com/nicklockwood/SwiftFormat/issues/1847 - let input = #""" - public func foo(message: String = "hi") { - let message = - """ - Message: \(message) - """ - print(message) - } - """# - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testArgumentUsedInsideMultilineStringLiteral2() { - let input = #""" - func foo(message: String) { - let message = - """ - \(1 + 1) - Message: \(message) - """ - print(message) - } - """# - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func testArgumentUsedInsideMultilineArrayLiteral() { - let input = #""" - func foo(message: String) { - let message = [ - message, - ] - print(message) - } - """# - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - func test1850() { - let input = """ - init(a3: A42.ID) { - a15.a22 - .sink { - Task { - switch a23.a27 { - #if !A34 - case .a35: - A31.a32(.a33(.a34Failed)) - #endif - case .a36: - break - } - } - } - .store(in: &a14) - - if a4.a57 == nil { - a51 = a3.a.b?.a54 - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedArguments) - } - - // MARK: redundantClosure - - func testRemoveRedundantClosureInSingleLinePropertyDeclaration() { - let input = """ - let foo = { "Foo" }() - let bar = { "Bar" }() - - let baaz = { "baaz" }() - - let quux = { "quux" }() - """ - - let output = """ - let foo = "Foo" - let bar = "Bar" - - let baaz = "baaz" - - let quux = "quux" - """ - - testFormatting(for: input, output, rule: FormatRules.redundantClosure) - } - - func testRedundantClosureWithExplicitReturn() { - let input = """ - let foo = { return "Foo" }() - - let bar = { - return if Bool.random() { - "Bar" - } else { - "Baaz" - } - }() - """ - - let output = """ - let foo = "Foo" - - let bar = if Bool.random() { - "Bar" - } else { - "Baaz" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure], - options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) - } - - func testRedundantClosureWithExplicitReturn2() { - let input = """ - func foo() -> String { - methodCall() - return { return "Foo" }() - } - - func bar() -> String { - methodCall() - return { "Bar" }() - } - - func baaz() -> String { - { return "Baaz" }() - } - """ - - let output = """ - func foo() -> String { - methodCall() - return "Foo" - } - - func bar() -> String { - methodCall() - return "Bar" - } - - func baaz() -> String { - "Baaz" - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure]) - } - - func testKeepsClosureThatIsNotCalled() { - let input = """ - let foo = { "Foo" } - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testKeepsEmptyClosures() { - let input = """ - let foo = {}() - let bar = { /* comment */ }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRemoveRedundantClosureInMultiLinePropertyDeclaration() { - let input = """ - lazy var bar = { - Bar() - }() - """ - - let output = """ - lazy var bar = Bar() - """ - - testFormatting(for: input, output, rule: FormatRules.redundantClosure, exclude: ["propertyType"]) - } - - func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { - let input = #""" - lazy var bar = { - """ - Multiline string literal - """ - }() - """# - - let output = #""" - lazy var bar = """ - Multiline string literal - """ - """# - - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.indent]) - } - - func testRemoveRedundantClosureInMultiLinePropertyDeclarationInClass() { - let input = """ - class Foo { - lazy var bar = { - return Bar(); - }() - } - """ - - let output = """ - class Foo { - lazy var bar = Bar() - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure, - FormatRules.semicolons], exclude: ["propertyType"]) - } - - func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { - let input = """ - lazy var baaz = { - Baaz( - foo: foo, - bar: bar) - }() - """ - - let output = """ - lazy var baaz = Baaz( - foo: foo, - bar: bar) - """ - - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, [output], - rules: [FormatRules.redundantClosure, FormatRules.wrapArguments], - options: options, exclude: ["propertyType"]) - } - - func testRemoveRedundantClosureInWrappedPropertyDeclaration_afterFirst() { - let input = """ - lazy var baaz = { - Baaz(foo: foo, - bar: bar) - }() - """ - - let output = """ - lazy var baaz = Baaz(foo: foo, - bar: bar) - """ - - let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) - testFormatting(for: input, [output], - rules: [FormatRules.redundantClosure, FormatRules.wrapArguments], - options: options, exclude: ["propertyType"]) - } - - func testRedundantClosureKeepsMultiStatementClosureThatSetsProperty() { - let input = """ - lazy var baaz = { - let baaz = Baaz(foo: foo, bar: bar) - baaz.foo = foo2 - return baaz - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRedundantClosureKeepsMultiStatementClosureWithMultipleStatements() { - let input = """ - lazy var quux = { - print("hello world") - return "quux" - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRedundantClosureKeepsClosureWithInToken() { - let input = """ - lazy var double = { () -> Double in - 100 - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRedundantClosureKeepsMultiStatementClosureOnSameLine() { - let input = """ - lazy var baaz = { - print("Foo"); return baaz - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRedundantClosureRemovesComplexMultilineClosure() { - let input = """ - lazy var closureInClosure = { - { - print("Foo") - print("Bar"); return baaz - } - }() - """ - - let output = """ - lazy var closureInClosure = { - print("Foo") - print("Bar"); return baaz - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.indent]) - } - - func testKeepsClosureWithIfStatement() { - let input = """ - lazy var baaz = { - if let foo == foo { - return foo - } else { - return Foo() - } - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testKeepsClosureWithIfStatementOnSingleLine() { - let input = """ - lazy var baaz = { - if let foo == foo { return foo } else { return Foo() } - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure, - exclude: ["wrapConditionalBodies"]) - } - - func testRemovesClosureWithIfStatementInsideOtherClosure() { - let input = """ - lazy var baaz = { - { - if let foo == foo { - return foo - } else { - return Foo() - } - } - }() - """ - - let output = """ - lazy var baaz = { - if let foo == foo { - return foo - } else { - return Foo() - } - } - """ - - testFormatting(for: input, [output], - rules: [FormatRules.redundantClosure, FormatRules.indent]) - } - - func testKeepsClosureWithSwitchStatement() { - let input = """ - lazy var baaz = { - switch foo { - case let .some(foo): - return foo: - case .none: - return Foo() - } - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testKeepsClosureWithIfDirective() { - let input = """ - lazy var baaz = { - #if DEBUG - return DebugFoo() - #else - return Foo() - #endif - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testKeepsClosureThatCallsMethodThatReturnsNever() { - let input = """ - lazy var foo: String = { fatalError("no default value has been set") }() - lazy var bar: String = { return preconditionFailure("no default value has been set") }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure, - exclude: ["redundantReturn"]) - } - - func testRemovesClosureThatHasNestedFatalError() { - let input = """ - lazy var foo = { - Foo(handle: { fatalError() }) - }() - """ - - let output = """ - lazy var foo = Foo(handle: { fatalError() }) - """ - - testFormatting(for: input, output, rule: FormatRules.redundantClosure, exclude: ["propertyType"]) - } - - func testPreservesClosureWithMultipleVoidMethodCalls() { - let input = """ - lazy var triggerSomething: Void = { - logger.trace("log some stuff before Triggering") - TriggerClass.triggerTheThing() - logger.trace("Finished triggering the thing") - }() - """ - - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRemovesClosureWithMultipleNestedVoidMethodCalls() { - let input = """ - lazy var foo: Foo = { - Foo(handle: { - logger.trace("log some stuff before Triggering") - TriggerClass.triggerTheThing() - logger.trace("Finished triggering the thing") - }) - }() - """ - - let output = """ - lazy var foo: Foo = Foo(handle: { - logger.trace("log some stuff before Triggering") - TriggerClass.triggerTheThing() - logger.trace("Finished triggering the thing") - }) - """ - - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.indent], exclude: ["redundantType"]) - } - - func testKeepsClosureThatThrowsError() { - let input = "let foo = try bar ?? { throw NSError() }()" - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testKeepsDiscardableResultClosure() { - let input = """ - @discardableResult - func discardableResult() -> String { "hello world" } - - /// We can't remove this closure, since the method called inline - /// would return a String instead. - let void: Void = { discardableResult() }() - """ - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testKeepsDiscardableResultClosure2() { - let input = """ - @discardableResult - func discardableResult() -> String { "hello world" } - - /// We can't remove this closure, since the method called inline - /// would return a String instead. - let void: () = { discardableResult() }() - """ - testFormatting(for: input, rule: FormatRules.redundantClosure) - } - - func testRedundantClosureDoesntLeaveStrayTry() { - let input = """ - let user2: User? = try { - if let data2 = defaults.data(forKey: defaultsKey) { - return try PropertyListDecoder().decode(User.self, from: data2) - } else { - return nil - } - }() - """ - let output = """ - let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { - try PropertyListDecoder().decode(User.self, from: data2) - } else { - nil - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure], - options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) - } - - func testRedundantClosureDoesntLeaveStrayTryAwait() { - let input = """ - let user2: User? = try await { - if let data2 = defaults.data(forKey: defaultsKey) { - return try await PropertyListDecoder().decode(User.self, from: data2) - } else { - return nil - } - }() - """ - let output = """ - let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { - try await PropertyListDecoder().decode(User.self, from: data2) - } else { - nil - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure], - options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) - } - - func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInOperatorChain() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes: Int { - { - switch self { - case .uint8: UInt8.bitWidth - case .uint16: UInt16.bitWidth - } - }() / 8 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes: Int { - { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() / 8 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain2() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes: Int { - 8 / { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain3() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes = 8 / { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testRedundantClosureDoesRemoveRedundantIfStatementClosureInAssignmentPosition() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes = { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() - } - """ - - let output = """ - private enum Format { - case uint8 - case uint16 - - var bytes = if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) - } - - func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInArray() { - let input = """ - private func constraint() -> [Int] { - [ - 1, - 2, - { - if Bool.random() { - 3 - } else { - 4 - } - }(), - ] - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement() { - let input = """ - func method() -> Int { - return { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - }() - } - """ - - let output = """ - func method() -> Int { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement2() { - let input = """ - func method() throws -> Int { - return try { - return try if Bool.random() { - randomThrows() - } else { - randomThrows() - } - }() - } - """ - - let output = """ - func method() throws -> Int { - return try if Bool.random() { - randomThrows() - } else { - randomThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement3() { - let input = """ - func method() async throws -> Int { - return try await { - return try await if Bool.random() { - randomAsyncThrows() - } else { - randomAsyncThrows() - } - }() - } - """ - - let output = """ - func method() async throws -> Int { - return try await if Bool.random() { - randomAsyncThrows() - } else { - randomAsyncThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement4() { - let input = """ - func method() -> Int { - return { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - }() - } - """ - - let output = """ - func method() -> Int { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) - } - - func testRedundantClosureRemovesClosureAsReturnStatement() { - let input = """ - func method() -> Int { - return { - return if Bool.random() { - 42 - } else { - 43 - } - }() - } - """ - - let output = """ - func method() -> Int { - return if Bool.random() { - 42 - } else { - 43 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure], - options: options, exclude: ["redundantReturn", "indent"]) - } - - func testRedundantClosureRemovesClosureAsImplicitReturnStatement() { - let input = """ - func method() -> Int { - { - if Bool.random() { - 42 - } else { - 43 - } - }() - } - """ - - let output = """ - func method() -> Int { - if Bool.random() { - 42 - } else { - 43 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["indent"]) - } - - func testClosureNotRemovedAroundIfExpressionInGuard() { - let input = """ - guard let foo = { - if condition { - bar() - } - }() else { - return - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall() { - let input = """ - XCTAssert({ - if foo { - bar - } else { - baaz - } - }()) - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall2() { - let input = """ - method("foo", { - if foo { - bar - } else { - baaz - } - }()) - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall3() { - let input = """ - XCTAssert({ - if foo { - bar - } else { - baaz - } - }(), "message") - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall4() { - let input = """ - method( - "foo", - { - if foo { - bar - } else { - baaz - } - }(), - "bar" - ) - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testDoesntRemoveClosureWithIfExpressionConditionalCastInSwift5_9() { - // The following code doesn't compile in Swift 5.9 due to this issue: - // https://github.com/apple/swift/issues/68764 - // - // let result = if condition { - // foo as? String - // } else { - // "bar" - // } - // - let input = """ - let result1: String? = { - if condition { - return foo as? String - } else { - return "bar" - } - }() - - let result1: String? = { - switch condition { - case true: - return foo as! String - case false: - return "bar" - } - }() - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) - } - - func testDoesRemoveClosureWithIfExpressionConditionalCastInSwift5_10() { - let input = """ - let result1: String? = { - if condition { - foo as? String - } else { - "bar" - } - }() - - let result2: String? = { - switch condition { - case true: - foo as? String - case false: - "bar" - } - }() - """ - - let output = """ - let result1: String? = if condition { - foo as? String - } else { - "bar" - } - - let result2: String? = switch condition { - case true: - foo as? String - case false: - "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) - } - - func testRedundantClosureDoesntBreakBuildWithRedundantReturnRuleDisabled() { - let input = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = { - return 0 - }() - """ - - let output = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = 0 - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, - exclude: ["redundantReturn", "blankLinesBetweenScopes", "propertyType"]) - } - - func testRedundantClosureWithSwitchExpressionDoesntBreakBuildWithRedundantReturnRuleDisabled() { - // From https://github.com/nicklockwood/SwiftFormat/issues/1565 - let input = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = { - switch myEnum { - case .a: - return 0 - case .b: - return 1 - } - }() - """ - - let output = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = switch myEnum { - case .a: - 0 - case .b: - 1 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure], - options: options, - exclude: ["indent", "blankLinesBetweenScopes", "wrapMultilineConditionalAssignment", - "propertyType"]) - } - - func testRemovesRedundantClosureWithGenericExistentialTypes() { - let input = """ - let foo: Foo = { DefaultFoo() }() - let foo: any Foo = { DefaultFoo() }() - let foo: any Foo = { DefaultFoo() }() - """ - - let output = """ - let foo: Foo = DefaultFoo() - let foo: any Foo = DefaultFoo() - let foo: any Foo = DefaultFoo() - """ - - testFormatting(for: input, output, rule: FormatRules.redundantClosure) - } - - func testRedundantSwitchStatementReturnInFunctionWithMultipleWhereClauses() { - // https://github.com/nicklockwood/SwiftFormat/issues/1554 - let input = """ - func foo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - return "foo" - case .fooCase2 where count < 100, - .fooCase3 where count < 100, - .fooCase4: - return "bar" - default: - return nil - } - } - """ - let output = """ - func foo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - "foo" - case .fooCase2 where count < 100, - .fooCase3 where count < 100, - .fooCase4: - "bar" - default: - nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - func testRedundantSwitchStatementReturnInFunctionWithSingleWhereClause() { - // https://github.com/nicklockwood/SwiftFormat/issues/1554 - let input = """ - func anotherFoo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - return "foo" - case .fooCase2 where count < 100, - .fooCase4: - return "bar" - default: - return nil - } - } - """ - let output = """ - func anotherFoo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - "foo" - case .fooCase2 where count < 100, - .fooCase4: - "bar" - default: - nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], - options: options) - } - - // MARK: - redundantOptionalBinding - - func testRemovesRedundantOptionalBindingsInSwift5_7() { - let input = """ - if let foo = foo { - print(foo) - } - - else if var bar = bar { - print(bar) - } - - guard let self = self else { - return - } - - while var quux = quux { - break - } - """ - - let output = """ - if let foo { - print(foo) - } - - else if var bar { - print(bar) - } - - guard let self else { - return - } - - while var quux { - break - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.redundantOptionalBinding, options: options, exclude: ["elseOnSameLine"]) - } - - func testRemovesMultipleOptionalBindings() { - let input = """ - if let foo = foo, let bar = bar, let baaz = baaz { - print(foo, bar, baaz) - } - - guard let foo = foo, let bar = bar, let baaz = baaz else { - return - } - """ - - let output = """ - if let foo, let bar, let baaz { - print(foo, bar, baaz) - } - - guard let foo, let bar, let baaz else { - return - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.redundantOptionalBinding, options: options) - } - - func testRemovesMultipleOptionalBindingsOnSeparateLines() { - let input = """ - if - let foo = foo, - let bar = bar, - let baaz = baaz - { - print(foo, bar, baaz) - } - - guard - let foo = foo, - let bar = bar, - let baaz = baaz - else { - return - } - """ - - let output = """ - if - let foo, - let bar, - let baaz - { - print(foo, bar, baaz) - } - - guard - let foo, - let bar, - let baaz - else { - return - } - """ - - let options = FormatOptions(indent: " ", swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.redundantOptionalBinding, options: options) - } - - func testKeepsRedundantOptionalBeforeSwift5_7() { - let input = """ - if let foo = foo { - print(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options) - } - - func testKeepsNonRedundantOptional() { - let input = """ - if let foo = bar { - print(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options) - } - - func testKeepsOptionalNotEligibleForShorthand() { - let input = """ - if let foo = self.foo, let bar = bar(), let baaz = baaz[0] { - print(foo, bar, baaz) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options, exclude: ["redundantSelf"]) - } - - func testRedundantSelfAndRedundantOptionalTogether() { - let input = """ - if let foo = self.foo { - print(foo) - } - """ - - let output = """ - if let foo { - print(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, [output], rules: [FormatRules.redundantOptionalBinding, FormatRules.redundantSelf], options: options) - } - - func testDoesntRemoveShadowingOutsideOfOptionalBinding() { - let input = """ - let foo = foo - - if let bar = baaz({ - let foo = foo - print(foo) - }) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options) - } - - // MARK: - redundantInternal - - func testRemoveRedundantInternalACL() { - let input = """ - internal class Foo { - internal let bar: String - - internal func baaz() {} - - internal init() { - bar = "bar" - } - } - """ - - let output = """ - class Foo { - let bar: String - - func baaz() {} - - init() { - bar = "bar" - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.redundantInternal) - } - - func testPreserveInternalInNonInternalExtensionExtension() { - let input = """ - extension Foo { - /// internal is redundant here since the extension is internal - internal func bar() {} - - public func baaz() {} - - /// internal is redundant here since the extension is internal - internal func bar() {} - } - - public extension Foo { - /// internal is not redundant here since the extension is public - internal func bar() {} - - public func baaz() {} - - /// internal is not redundant here since the extension is public - internal func bar() {} - } - """ - - let output = """ - extension Foo { - /// internal is redundant here since the extension is internal - func bar() {} - - public func baaz() {} - - /// internal is redundant here since the extension is internal - func bar() {} - } - - public extension Foo { - /// internal is not redundant here since the extension is public - internal func bar() {} - - public func baaz() {} - - /// internal is not redundant here since the extension is public - internal func bar() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.redundantInternal, exclude: ["redundantExtensionACL"]) - } - - func testPreserveInternalImport() { - let input = "internal import MyPackage" - testFormatting(for: input, rule: FormatRules.redundantInternal) - } - - func testPreservesInternalInPublicExtensionWithWhereClause() { - let input = """ - public extension SomeProtocol where SomeAssociatedType == SomeOtherType { - internal func fun1() {} - func fun2() {} - } - - public extension OtherProtocol { - internal func fun1() {} - func fun2() {} - } - """ - testFormatting(for: input, rule: FormatRules.redundantInternal) - } - - // MARK: - noExplicitOwnership - - func testRemovesOwnershipKeywordsFromFunc() { - let input = """ - consuming func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} - borrowing func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} - """ - - let output = """ - func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} - func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} - """ - - testFormatting(for: input, output, rule: FormatRules.noExplicitOwnership, exclude: ["unusedArguments"]) - } - - func testRemovesOwnershipKeywordsFromClosure() { - let input = """ - foos.map { (foo: consuming Foo) in - foo.bar - } - - foos.map { (foo: borrowing Foo) in - foo.bar - } - """ - - let output = """ - foos.map { (foo: Foo) in - foo.bar - } - - foos.map { (foo: Foo) in - foo.bar - } - """ - - testFormatting(for: input, output, rule: FormatRules.noExplicitOwnership, exclude: ["unusedArguments"]) - } - - func testRemovesOwnershipKeywordsFromType() { - let input = """ - let consuming: (consuming Foo) -> Bar - let borrowing: (borrowing Foo) -> Bar - """ - - let output = """ - let consuming: (Foo) -> Bar - let borrowing: (Foo) -> Bar - """ - - testFormatting(for: input, output, rule: FormatRules.noExplicitOwnership) - } - - // MARK: - redundantProperty - - func testRemovesRedundantProperty() { - let input = """ - func foo() -> Foo { - let foo = Foo(bar: bar, baaz: baaz) - return foo - } - """ - - let output = """ - func foo() -> Foo { - return Foo(bar: bar, baaz: baaz) - } - """ - - testFormatting(for: input, output, rule: FormatRules.redundantProperty, exclude: ["redundantReturn"]) - } - - func testRemovesRedundantPropertyWithIfExpression() { - let input = """ - func foo() -> Foo { - let foo = - if condition { - Foo.foo() - } else { - Foo.bar() - } - - return foo - } - """ - - let output = """ - func foo() -> Foo { - if condition { - Foo.foo() - } else { - Foo.bar() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.redundantProperty, FormatRules.redundantReturn, FormatRules.indent], options: options) - } - - func testRemovesRedundantPropertyWithSwitchExpression() { - let input = """ - func foo() -> Foo { - let foo: Foo - switch condition { - case true: - foo = Foo(bar) - case false: - foo = Foo(baaz) - } - - return foo - } - """ - - let output = """ - func foo() -> Foo { - switch condition { - case true: - Foo(bar) - case false: - Foo(baaz) - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.redundantProperty, FormatRules.redundantReturn, FormatRules.indent], options: options) - } - - func testRemovesRedundantPropertyWithPreferInferredType() { - let input = """ - func bar() -> Bar { - let bar: Bar = .init(baaz: baaz, quux: quux) - return bar - } - """ - - let output = """ - func bar() -> Bar { - return Bar(baaz: baaz, quux: quux) - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantProperty, FormatRules.redundantInit], exclude: ["redundantReturn"]) - } - - func testRemovesRedundantPropertyWithComments() { - let input = """ - func foo() -> Foo { - // There's a comment before this property - let foo = Foo(bar: bar, baaz: baaz) - // And there's a comment after the property - return foo - } - """ - - let output = """ - func foo() -> Foo { - // There's a comment before this property - return Foo(bar: bar, baaz: baaz) - // And there's a comment after the property - } - """ - - testFormatting(for: input, output, rule: FormatRules.redundantProperty, exclude: ["redundantReturn"]) - } - - func testRemovesRedundantPropertyFollowingOtherProperty() { - let input = """ - func foo() -> Foo { - let bar = Bar(baaz: baaz) - let foo = Foo(bar: bar) - return foo - } - """ - - let output = """ - func foo() -> Foo { - let bar = Bar(baaz: baaz) - return Foo(bar: bar) - } - """ - - testFormatting(for: input, output, rule: FormatRules.redundantProperty) - } - - func testPreservesPropertyWhereReturnIsNotRedundant() { - let input = """ - func foo() -> Foo { - let foo = Foo(bar: bar, baaz: baaz) - return foo.with(quux: quux) - } - - func bar() -> Foo { - let bar = Bar(baaz: baaz) - return bar.baaz - } - - func baaz() -> Foo { - let bar = Bar(baaz: baaz) - print(bar) - return bar - } - """ - - testFormatting(for: input, rule: FormatRules.redundantProperty) - } - - func testPreservesUnwrapConditionInIfStatement() { - let input = """ - func foo() -> Foo { - let foo = Foo(bar: bar, baaz: baaz) - - if let foo = foo.nestedFoo { - print(foo) - } - - return foo - } - """ - - testFormatting(for: input, rule: FormatRules.redundantProperty) - } - - // MARK: - redundantTypedThrows - - func testRemovesRedundantNeverTypeThrows() { - let input = """ - func foo() throws(Never) -> Int { - 0 - } - """ - - let output = """ - func foo() -> Int { - 0 - } - """ - - let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, output, rule: FormatRules.redundantTypedThrows, options: options) - } - - func testRemovesRedundantAnyErrorTypeThrows() { - let input = """ - func foo() throws(any Error) -> Int { - throw MyError.foo - } - """ - - let output = """ - func foo() throws -> Int { - throw MyError.foo - } - """ - - let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, output, rule: FormatRules.redundantTypedThrows, options: options) - } - - func testDontRemovesNonRedundantErrorTypeThrows() { - let input = """ - func bar() throws(BarError) -> Foo { - throw .foo - } - - func foo() throws(Error) -> Int { - throw MyError.foo - } - """ - - let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, rule: FormatRules.redundantTypedThrows, options: options) - } - - // MARK: - unusedPrivateDeclaration - - func testRemoveUnusedPrivate() { - let input = """ - struct Foo { - private var foo = "foo" - var bar = "bar" - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemoveUnusedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoNotRemoveUsedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - } - - struct Hello { - let localFoo = Foo().foo - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemoveMultipleUnusedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - fileprivate var baz = "baz" - var bar = "bar" - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemoveMixedUsedAndUnusedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - fileprivate var baz = "baz" - } - - struct Hello { - let localFoo = Foo().foo - } - """ - let output = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - } - - struct Hello { - let localFoo = Foo().foo - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoNotRemoveFilePrivateUsedInSameStruct() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - - func useFoo() { - print(foo) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemoveUnusedFilePrivateInNestedStruct() { - let input = """ - struct Foo { - var bar = "bar" - - struct Inner { - fileprivate var foo = "foo" - } - } - """ - let output = """ - struct Foo { - var bar = "bar" - - struct Inner { - } - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration, exclude: ["emptyBraces"]) - } - - func testDoNotRemoveFilePrivateUsedInNestedStruct() { - let input = """ - struct Foo { - var bar = "bar" - - struct Inner { - fileprivate var foo = "foo" - func useFoo() { - print(foo) - } - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemoveUnusedFileprivateFunction() { - let input = """ - struct Foo { - var bar = "bar" - - fileprivate func sayHi() { - print("hi") - } - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, [output], rules: [FormatRules.unusedPrivateDeclaration, FormatRules.blankLinesAtEndOfScope]) - } - - func testDoNotRemoveUnusedFileprivateOperatorDefinition() { - let input = """ - private class Foo: Equatable { - fileprivate static func == (_: Foo, _: Foo) -> Bool { - return true - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemovePrivateDeclarationButDoNotRemoveUnusedPrivateType() { - let input = """ - private struct Foo { - private func bar() { - print("test") - } - } - """ - let output = """ - private struct Foo { - } - """ - - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration, exclude: ["emptyBraces"]) - } - - func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { - let input = """ - private extension Foo { - private func doSomething() {} - func anotherFunction() {} - } - """ - let output = """ - private extension Foo { - func anotherFunction() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) - } - - func testRemovesPrivateTypealias() { - let input = """ - enum Foo { - struct Bar {} - private typealias Baz = Bar - } - """ - let output = """ - enum Foo { - struct Bar {} - } - """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoesntRemoveFileprivateInit() { - let input = """ - struct Foo { - fileprivate init() {} - static let foo = Foo() - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, exclude: ["propertyType"]) - } - - func testCanDisableUnusedPrivateDeclarationRule() { - let input = """ - private enum Foo { - // swiftformat:disable:next unusedPrivateDeclaration - fileprivate static func bar() {} - } - """ - - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoesNotRemovePropertyWrapperPrefixesIfUsed() { - let input = """ - struct ContentView: View { - public init() { - _showButton = .init(initialValue: false) - } - - @State private var showButton: Bool - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoesNotRemoveUnderscoredDeclarationIfUsed() { - let input = """ - struct Foo { - private var _showButton: Bool = true - print(_showButton) - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoesNotRemoveBacktickDeclarationIfUsed() { - let input = """ - struct Foo { - fileprivate static var `default`: Bool = true - func printDefault() { - print(Foo.default) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoesNotRemoveBacktickUsage() { - let input = """ - struct Foo { - fileprivate static var foo = true - func printDefault() { - print(Foo.`foo`) - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, exclude: ["redundantBackticks"]) - } - - func testDoNotRemovePreservedPrivateDeclarations() { - let input = """ - enum Foo { - private static let registryAssociation = false - } - """ - let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, options: options) - } - - func testDoNotRemoveOverridePrivateMethodDeclarations() { - let input = """ - class Poodle: Dog { - override private func makeNoise() { - print("Yip!") - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoNotRemoveOverridePrivatePropertyDeclarations() { - let input = """ - class Poodle: Dog { - override private var age: Int { - 7 - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoNotRemoveObjcPrivatePropertyDeclaration() { - let input = """ - struct Foo { - @objc - private var bar = "bar" - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoNotRemoveObjcPrivateFunctionDeclaration() { - let input = """ - struct Foo { - @objc - private func doSomething() {} - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } - - func testDoNotRemoveIBActionPrivateFunctionDeclaration() { - let input = """ - class FooViewController: UIViewController { - @IBAction private func buttonPressed(_: UIButton) { - print("Button pressed!") - } - } - """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) - } -} diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift deleted file mode 100644 index c648ea90..00000000 --- a/Tests/RulesTests+Spacing.swift +++ /dev/null @@ -1,2177 +0,0 @@ -// -// RulesTests+Spacing.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class SpacingTests: RulesTests { - // MARK: - spaceAroundParens - - func testSpaceAfterSet() { - let input = "private(set)var foo: Int" - let output = "private(set) var foo: Int" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenParenAndClass() { - let input = "@objc(XYZFoo)class foo" - let output = "@objc(XYZFoo) class foo" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenConventionAndBlock() { - let input = "@convention(block)() -> Void" - let output = "@convention(block) () -> Void" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenConventionAndEscaping() { - let input = "@convention(block)@escaping () -> Void" - let output = "@convention(block) @escaping () -> Void" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenAutoclosureEscapingAndBlock() { // Swift 2.3 only - let input = "@autoclosure(escaping)() -> Void" - let output = "@autoclosure(escaping) () -> Void" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenSendableAndBlock() { - let input = "@Sendable (Action) -> Void" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenMainActorAndBlock() { - let input = "@MainActor (Action) -> Void" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenMainActorAndBlock2() { - let input = "@MainActor (@MainActor (Action) -> Void) async -> Void" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenMainActorAndClosureParams() { - let input = "{ @MainActor (foo: Int) in foo }" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenUncheckedAndSendable() { - let input = """ - enum Foo: @unchecked Sendable { - case bar - } - """ - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenParenAndAs() { - let input = "(foo.bar) as? String" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, exclude: ["redundantParens"]) - } - - func testNoSpaceAfterParenAtEndOfFile() { - let input = "(foo.bar)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, exclude: ["redundantParens"]) - } - - func testSpaceBetweenParenAndFoo() { - let input = "func foo ()" - let output = "func foo()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenParenAndAny() { - let input = "func any ()" - let output = "func any()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenParenAndAnyType() { - let input = "let foo: any(A & B).Type" - let output = "let foo: any (A & B).Type" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenParenAndSomeType() { - let input = "func foo() -> some(A & B).Type" - let output = "func foo() -> some (A & B).Type" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenParenAndInit() { - let input = "init ()" - let output = "init()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenObjcAndSelector() { - let input = "@objc (XYZFoo) class foo" - let output = "@objc(XYZFoo) class foo" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenHashSelectorAndBrace() { - let input = "#selector(foo)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenHashKeyPathAndBrace() { - let input = "#keyPath (foo.bar)" - let output = "#keyPath(foo.bar)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenHashAvailableAndBrace() { - let input = "#available (iOS 9.0, *)" - let output = "#available(iOS 9.0, *)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenPrivateAndSet() { - let input = "private (set) var foo: Int" - let output = "private(set) var foo: Int" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenLetAndTuple() { - let input = "if let (foo, bar) = baz {}" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenIfAndCondition() { - let input = "if(a || b) == true {}" - let output = "if (a || b) == true {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenArrayLiteralAndParen() { - let input = "[String] ()" - let output = "[String]()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments() { - let input = "{ [weak self](foo) in print(foo) }" - let output = "{ [weak self] (foo) in print(foo) }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantParens"]) - } - - func testAddSpaceBetweenCaptureListAndArguments2() { - let input = "{ [weak self]() -> Void in }" - let output = "{ [weak self] () -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) - } - - func testAddSpaceBetweenCaptureListAndArguments3() { - let input = "{ [weak self]() throws -> Void in }" - let output = "{ [weak self] () throws -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) - } - - func testAddSpaceBetweenCaptureListAndArguments4() { - let input = "{ [weak self](foo: @escaping(Bar?) -> Void) -> Baz? in foo }" - let output = "{ [weak self] (foo: @escaping (Bar?) -> Void) -> Baz? in foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments5() { - let input = "{ [weak self](foo: @autoclosure() -> String) -> Baz? in foo() }" - let output = "{ [weak self] (foo: @autoclosure () -> String) -> Baz? in foo() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments6() { - let input = "{ [weak self](foo: @Sendable() -> String) -> Baz? in foo() }" - let output = "{ [weak self] (foo: @Sendable () -> String) -> Baz? in foo() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments7() { - let input = "Foo(0) { [weak self]() -> Void in }" - let output = "Foo(0) { [weak self] () -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) - } - - func testAddSpaceBetweenCaptureListAndArguments8() { - let input = "{ [weak self]() throws(Foo) -> Void in }" - let output = "{ [weak self] () throws(Foo) -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) - } - - func testAddSpaceBetweenEscapingAndParenthesizedClosure() { - let input = "@escaping(() -> Void)" - let output = "@escaping (() -> Void)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenAutoclosureAndParenthesizedClosure() { - let input = "@autoclosure(() -> String)" - let output = "@autoclosure (() -> String)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBetweenClosingParenAndOpenBrace() { - let input = "func foo(){ foo }" - let output = "func foo() { foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoSpaceBetweenClosingBraceAndParens() { - let input = "{ block } ()" - let output = "{ block }()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantClosure"]) - } - - func testDontRemoveSpaceBetweenOpeningBraceAndParens() { - let input = "a = (b + c)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, - exclude: ["redundantParens"]) - } - - func testKeywordAsIdentifierParensSpacing() { - let input = "if foo.let (foo, bar) {}" - let output = "if foo.let(foo, bar) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceAfterInoutParam() { - let input = "func foo(bar _: inout(Int, String)) {}" - let output = "func foo(bar _: inout (Int, String)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceAfterEscapingAttribute() { - let input = "func foo(bar: @escaping() -> Void)" - let output = "func foo(bar: @escaping () -> Void)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testSpaceAfterAutoclosureAttribute() { - let input = "func foo(bar: @autoclosure () -> Void)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testSpaceAfterSendableAttribute() { - let input = "func foo(bar: @Sendable () -> Void)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testSpaceBeforeTupleIndexArgument() { - let input = "foo.1 (true)" - let output = "foo.1(true)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testRemoveSpaceBetweenParenAndBracket() { - let input = "let foo = bar[5] ()" - let output = "let foo = bar[5]()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testRemoveSpaceBetweenParenAndBracketInsideClosure() { - let input = "let foo = bar { [Int] () }" - let output = "let foo = bar { [Int]() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenParenAndCaptureList() { - let input = "let foo = bar { [self](foo: Int) in foo }" - let output = "let foo = bar { [self] (foo: Int) in foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenParenAndAwait() { - let input = "let foo = await(bar: 5)" - let output = "let foo = await (bar: 5)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenParenAndAwaitForSwift5_5() { - let input = "let foo = await(bar: 5)" - let output = "let foo = await (bar: 5)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoAddSpaceBetweenParenAndAwaitForSwiftLessThan5_5() { - let input = "let foo = await(bar: 5)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, - options: FormatOptions(swiftVersion: "5.4.9")) - } - - func testRemoveSpaceBetweenParenAndConsume() { - let input = "let foo = consume (bar)" - let output = "let foo = consume(bar)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testNoAddSpaceBetweenParenAndAvailableAfterFunc() { - let input = """ - func foo() - - @available(macOS 10.13, *) - func bar() - """ - testFormatting(for: input, rule: FormatRules.spaceAroundParens) - } - - func testNoAddSpaceAroundTypedThrowsFunctionType() { - let input = "func foo() throws (Bar) -> Baz {}" - let output = "func foo() throws(Bar) -> Baz {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenParenAndBorrowing() { - let input = "func foo(_: borrowing(any Foo)) {}" - let output = "func foo(_: borrowing (any Foo)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, - exclude: ["noExplicitOwnership"]) - } - - func testAddSpaceBetweenParenAndIsolated() { - let input = "func foo(isolation _: isolated(any Actor)) {}" - let output = "func foo(isolation _: isolated (any Actor)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - func testAddSpaceBetweenParenAndSending() { - let input = "func foo(_: sending(any Foo)) {}" - let output = "func foo(_: sending (any Foo)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) - } - - // MARK: - spaceInsideParens - - func testSpaceInsideParens() { - let input = "( 1, ( 2, 3 ) )" - let output = "(1, (2, 3))" - testFormatting(for: input, output, rule: FormatRules.spaceInsideParens) - } - - func testSpaceBeforeCommentInsideParens() { - let input = "( /* foo */ 1, 2 )" - let output = "( /* foo */ 1, 2)" - testFormatting(for: input, output, rule: FormatRules.spaceInsideParens) - } - - // MARK: - spaceAroundBrackets - - func testSubscriptNoAddSpacing() { - let input = "foo[bar] = baz" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) - } - - func testSubscriptRemoveSpacing() { - let input = "foo [bar] = baz" - let output = "foo[bar] = baz" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testArrayLiteralSpacing() { - let input = "foo = [bar, baz]" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) - } - - func testAsArrayCastingSpacing() { - let input = "foo as[String]" - let output = "foo as [String]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testAsOptionalArrayCastingSpacing() { - let input = "foo as? [String]" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) - } - - func testIsArrayTestingSpacing() { - let input = "if foo is[String] {}" - let output = "if foo is [String] {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testKeywordAsIdentifierBracketSpacing() { - let input = "if foo.is[String] {}" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) - } - - func testSpaceBeforeTupleIndexSubscript() { - let input = "foo.1 [2]" - let output = "foo.1[2]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testRemoveSpaceBetweenBracketAndParen() { - let input = "let foo = bar[5] ()" - let output = "let foo = bar[5]()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testRemoveSpaceBetweenBracketAndParenInsideClosure() { - let input = "let foo = bar { [Int] () }" - let output = "let foo = bar { [Int]() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testAddSpaceBetweenCaptureListAndParen() { - let input = "let foo = bar { [self](foo: Int) in foo }" - let output = "let foo = bar { [self] (foo: Int) in foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testAddSpaceBetweenInoutAndStringArray() { - let input = "func foo(arg _: inout[String]) {}" - let output = "func foo(arg _: inout [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testAddSpaceBetweenConsumingAndStringArray() { - let input = "func foo(arg _: consuming[String]) {}" - let output = "func foo(arg _: consuming [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets, - exclude: ["noExplicitOwnership"]) - } - - func testAddSpaceBetweenBorrowingAndStringArray() { - let input = "func foo(arg _: borrowing[String]) {}" - let output = "func foo(arg _: borrowing [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets, - exclude: ["noExplicitOwnership"]) - } - - func testAddSpaceBetweenSendingAndStringArray() { - let input = "func foo(arg _: sending[String]) {}" - let output = "func foo(arg _: sending [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) - } - - func testSpaceNotRemovedBetweenAsOperatorAndBracket() { - // https://github.com/nicklockwood/SwiftFormat/issues/1846 - let input = "@Test(arguments: [kSecReturnRef, kSecReturnAttributes] as [String])" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) - } - - // MARK: - spaceInsideBrackets - - func testSpaceInsideBrackets() { - let input = "foo[ 5 ]" - let output = "foo[5]" - testFormatting(for: input, output, rule: FormatRules.spaceInsideBrackets) - } - - func testSpaceInsideWrappedArray() { - let input = "[ foo,\n bar ]" - let output = "[foo,\n bar]" - let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.spaceInsideBrackets, options: options) - } - - func testSpaceBeforeCommentInsideWrappedArray() { - let input = "[ // foo\n bar,\n]" - let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, rule: FormatRules.spaceInsideBrackets, options: options) - } - - // MARK: - spaceAroundBraces - - func testSpaceAroundTrailingClosure() { - let input = "if x{ y }else{ z }" - let output = "if x { y } else { z }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces, - exclude: ["wrapConditionalBodies"]) - } - - func testNoSpaceAroundClosureInsiderParens() { - let input = "foo({ $0 == 5 })" - testFormatting(for: input, rule: FormatRules.spaceAroundBraces, - exclude: ["trailingClosures"]) - } - - func testNoExtraSpaceAroundBracesAtStartOrEndOfFile() { - let input = "{ foo }" - testFormatting(for: input, rule: FormatRules.spaceAroundBraces) - } - - func testNoSpaceAfterPrefixOperator() { - let input = "let foo = ..{ bar }" - testFormatting(for: input, rule: FormatRules.spaceAroundBraces) - } - - func testNoSpaceBeforePostfixOperator() { - let input = "let foo = { bar }.." - testFormatting(for: input, rule: FormatRules.spaceAroundBraces) - } - - func testSpaceAroundBracesAfterOptionalProperty() { - let input = "var: Foo?{}" - let output = "var: Foo? {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) - } - - func testSpaceAroundBracesAfterImplicitlyUnwrappedProperty() { - let input = "var: Foo!{}" - let output = "var: Foo! {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) - } - - func testSpaceAroundBracesAfterNumber() { - let input = "if x = 5{}" - let output = "if x = 5 {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) - } - - func testSpaceAroundBracesAfterString() { - let input = "if x = \"\"{}" - let output = "if x = \"\" {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) - } - - // MARK: - spaceInsideBraces - - func testSpaceInsideBraces() { - let input = "foo({bar})" - let output = "foo({ bar })" - testFormatting(for: input, output, rule: FormatRules.spaceInsideBraces, exclude: ["trailingClosures"]) - } - - func testNoExtraSpaceInsidebraces() { - let input = "{ foo }" - testFormatting(for: input, rule: FormatRules.spaceInsideBraces, exclude: ["trailingClosures"]) - } - - func testNoSpaceAddedInsideEmptybraces() { - let input = "foo({})" - testFormatting(for: input, rule: FormatRules.spaceInsideBraces, exclude: ["trailingClosures"]) - } - - func testNoSpaceAddedBetweenDoublebraces() { - let input = "func foo() -> () -> Void {{ bar() }}" - testFormatting(for: input, rule: FormatRules.spaceInsideBraces) - } - - // MARK: - spaceAroundGenerics - - func testSpaceAroundGenerics() { - let input = "Foo >" - let output = "Foo>" - testFormatting(for: input, output, rule: FormatRules.spaceAroundGenerics) - } - - func testSpaceAroundGenericsFollowedByAndOperator() { - let input = "if foo is Foo && baz {}" - testFormatting(for: input, rule: FormatRules.spaceAroundGenerics, exclude: ["andOperator"]) - } - - func testSpaceAroundGenericResultBuilder() { - let input = "func foo(@SomeResultBuilder builder: () -> Void) {}" - testFormatting(for: input, rule: FormatRules.spaceAroundGenerics) - } - - // MARK: - spaceInsideGenerics - - func testSpaceInsideGenerics() { - let input = "Foo< Bar< Baz > >" - let output = "Foo>" - testFormatting(for: input, output, rule: FormatRules.spaceInsideGenerics) - } - - // MARK: - spaceAroundOperators - - func testSpaceAfterColon() { - let input = "let foo:Bar = 5" - let output = "let foo: Bar = 5" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceBetweenOptionalAndDefaultValue() { - let input = "let foo: String?=nil" - let output = "let foo: String? = nil" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceBetweenImplictlyUnwrappedOptionalAndDefaultValue() { - let input = "let foo: String!=nil" - let output = "let foo: String! = nil" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpacePreservedBetweenOptionalTryAndDot() { - let input = "let foo: Int = try? .init()" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testSpacePreservedBetweenForceTryAndDot() { - let input = "let foo: Int = try! .init()" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceBetweenOptionalAndDefaultValueInFunction() { - let input = "func foo(bar _: String?=nil) {}" - let output = "func foo(bar _: String? = nil) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testNoSpaceAddedAfterColonInSelector() { - let input = "@objc(foo:bar:)" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceAfterColonInSwitchCase() { - let input = "switch x { case .y:break }" - let output = "switch x { case .y: break }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceAfterColonInSwitchDefault() { - let input = "switch x { default:break }" - let output = "switch x { default: break }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceAfterComma() { - let input = "let foo = [1,2,3]" - let output = "let foo = [1, 2, 3]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceBetweenColonAndEnumValue() { - let input = "[.Foo:.Bar]" - let output = "[.Foo: .Bar]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceBetweenCommaAndEnumValue() { - let input = "[.Foo,.Bar]" - let output = "[.Foo, .Bar]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testNoRemoveSpaceAroundEnumInBrackets() { - let input = "[ .red ]" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators, - exclude: ["spaceInsideBrackets"]) - } - - func testSpaceBetweenSemicolonAndEnumValue() { - let input = "statement;.Bar" - let output = "statement; .Bar" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpacePreservedBetweenEqualsAndEnumValue() { - let input = "foo = .Bar" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testNoSpaceBeforeColon() { - let input = "let foo : Bar = 5" - let output = "let foo: Bar = 5" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpacePreservedBeforeColonInTernary() { - let input = "foo ? bar : baz" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testSpacePreservedAroundEnumValuesInTernary() { - let input = "foo ? .Bar : .Baz" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceBeforeColonInNestedTernary() { - let input = "foo ? (hello + a ? b: c) : baz" - let output = "foo ? (hello + a ? b : c) : baz" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testNoSpaceBeforeComma() { - let input = "let foo = [1 , 2 , 3]" - let output = "let foo = [1, 2, 3]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceAtStartOfLine() { - let input = "print(foo\n ,bar)" - let output = "print(foo\n , bar)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators, - exclude: ["leadingDelimiters"]) - } - - func testSpaceAroundInfixMinus() { - let input = "foo-bar" - let output = "foo - bar" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) - } - - func testNoSpaceAroundPrefixMinus() { - let input = "foo + -bar" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) - } - - func testSpaceAroundLessThan() { - let input = "foo () - print(foo) - """ - testFormatting(for: input, rule: FormatRules.void) - } - - func testParensRemovedAroundVoid() { - let input = "() -> (Void)" - let output = "() -> Void" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testVoidArgumentConvertedToEmptyParens() { - let input = "Void -> Void" - let output = "() -> Void" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testVoidArgumentInParensNotConvertedToEmptyParens() { - let input = "(Void) -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testAnonymousVoidArgumentNotConvertedToEmptyParens() { - let input = "{ (_: Void) -> Void in }" - testFormatting(for: input, rule: FormatRules.void, exclude: ["redundantVoidReturnType"]) - } - - func testFuncWithAnonymousVoidArgumentNotStripped() { - let input = "func foo(_: Void) -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testFunctionThatReturnsAFunction() { - let input = "(Void) -> Void -> ()" - let output = "(Void) -> () -> Void" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testFunctionThatReturnsAFunctionThatThrows() { - let input = "(Void) -> Void throws -> ()" - let output = "(Void) -> () throws -> Void" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testFunctionThatReturnsAFunctionThatHasTypedThrows() { - let input = "(Void) -> Void throws(Foo) -> ()" - let output = "(Void) -> () throws(Foo) -> Void" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testChainOfFunctionsIsNotChanged() { - let input = "() -> () -> () -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testChainOfFunctionsWithThrowsIsNotChanged() { - let input = "() -> () throws -> () throws -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testChainOfFunctionsWithTypedThrowsIsNotChanged() { - let input = "() -> () throws(Foo) -> () throws(Foo) -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testVoidThrowsIsNotMangled() { - let input = "(Void) throws -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testVoidTypedThrowsIsNotMangled() { - let input = "(Void) throws(Foo) -> Void" - testFormatting(for: input, rule: FormatRules.void) - } - - func testEmptyClosureArgsNotMangled() { - let input = "{ () in }" - testFormatting(for: input, rule: FormatRules.void) - } - - func testEmptyClosureReturnValueConvertedToVoid() { - let input = "{ () -> () in }" - let output = "{ () -> Void in }" - testFormatting(for: input, output, rule: FormatRules.void, exclude: ["redundantVoidReturnType"]) - } - - func testAnonymousVoidClosureNotChanged() { - let input = "{ (_: Void) in }" - testFormatting(for: input, rule: FormatRules.void, exclude: ["unusedArguments"]) - } - - func testVoidLiteralConvertedToParens() { - let input = "foo(Void())" - let output = "foo(())" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testVoidLiteralConvertedToParens2() { - let input = "let foo = Void()" - let output = "let foo = ()" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testVoidLiteralReturnValueConvertedToParens() { - let input = """ - func foo() { - return Void() - } - """ - let output = """ - func foo() { - return () - } - """ - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testVoidLiteralReturnValueConvertedToParens2() { - let input = "{ _ in Void() }" - let output = "{ _ in () }" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testNamespacedVoidLiteralNotConverted() { - // TODO: it should actually be safe to convert Swift.Void - only unsafe for other namespaces - let input = "let foo = Swift.Void()" - testFormatting(for: input, rule: FormatRules.void) - } - - func testMalformedFuncDoesNotCauseInvalidOutput() throws { - let input = "func baz(Void) {}" - testFormatting(for: input, rule: FormatRules.void) - } - - func testEmptyParensInGenericsConvertedToVoid() { - let input = "Foo<(), ()>" - let output = "Foo" - testFormatting(for: input, output, rule: FormatRules.void) - } - - func testCaseVoidNotUnwrapped() { - let input = "case some(Void)" - testFormatting(for: input, rule: FormatRules.void) - } - - func testLocalVoidTypeNotConverted() { - let input = """ - struct Void {} - let foo = Void() - print(foo) - """ - testFormatting(for: input, rule: FormatRules.void) - } - - func testLocalVoidTypeForwardReferenceNotConverted() { - let input = """ - let foo = Void() - print(foo) - struct Void {} - """ - testFormatting(for: input, rule: FormatRules.void) - } - - func testLocalVoidTypealiasNotConverted() { - let input = """ - typealias Void = MyVoid - let foo = Void() - print(foo) - """ - testFormatting(for: input, rule: FormatRules.void) - } - - func testLocalVoidTypealiasForwardReferenceNotConverted() { - let input = """ - let foo = Void() - print(foo) - typealias Void = MyVoid - """ - testFormatting(for: input, rule: FormatRules.void) - } - - // useVoid = false - - func testUseVoidOptionFalse() { - let input = "(Void) -> Void" - let output = "(()) -> ()" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: FormatRules.void, options: options) - } - - func testNamespacedVoidNotConverted() { - let input = "() -> Swift.Void" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) - } - - func testTypealiasVoidNotConverted() { - let input = "public typealias Void = ()" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) - } - - func testVoidClosureReturnValueConvertedToEmptyTuple() { - let input = "{ () -> Void in }" - let output = "{ () -> () in }" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: FormatRules.void, options: options, exclude: ["redundantVoidReturnType"]) - } - - func testNoConvertVoidSelfToTuple() { - let input = "Void.self" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) - } - - func testNoConvertVoidTypeToTuple() { - let input = "Void.Type" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) - } - - func testCaseVoidConvertedToTuple() { - let input = "case some(Void)" - let output = "case some(())" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: FormatRules.void, options: options) - } - - // MARK: - trailingClosures - - func testAnonymousClosureArgumentMadeTrailing() { - let input = "foo(foo: 5, { /* some code */ })" - let output = "foo(foo: 5) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testNamedClosureArgumentNotMadeTrailing() { - let input = "foo(foo: 5, bar: { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testClosureArgumentPassedToFunctionInArgumentsNotMadeTrailing() { - let input = "foo(bar { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testClosureArgumentInFunctionWithOtherClosureArgumentsNotMadeTrailing() { - let input = "foo(foo: { /* some code */ }, { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testClosureArgumentInExpressionNotMadeTrailing() { - let input = "if let foo = foo(foo: 5, { /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testClosureArgumentInCompoundExpressionNotMadeTrailing() { - let input = "if let foo = foo(foo: 5, { /* some code */ }), let bar = bar(bar: 2, { /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testClosureArgumentAfterLinebreakInGuardNotMadeTrailing() { - let input = "guard let foo =\n bar({ /* some code */ })\nelse { return }" - testFormatting(for: input, rule: FormatRules.trailingClosures, - exclude: ["wrapConditionalBodies"]) - } - - func testClosureMadeTrailingForNumericTupleMember() { - let input = "foo.1(5, { bar })" - let output = "foo.1(5) { bar }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testNoRemoveParensAroundClosureFollowedByOpeningBrace() { - let input = "foo({ bar }) { baz }" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testRemoveParensAroundClosureWithInnerSpacesFollowedByUnwrapOperator() { - let input = "foo( { bar } )?.baz" - let output = "foo { bar }?.baz" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - // solitary argument - - func testParensAroundSolitaryClosureArgumentRemoved() { - let input = "foo({ /* some code */ })" - let output = "foo { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testParensAroundNamedSolitaryClosureArgumentNotRemoved() { - let input = "foo(foo: { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testParensAroundSolitaryClosureArgumentInExpressionNotRemoved() { - let input = "if let foo = foo({ /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testParensAroundSolitaryClosureArgumentInCompoundExpressionNotRemoved() { - let input = "if let foo = foo({ /* some code */ }), let bar = bar({ /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testParensAroundOptionalTrailingClosureInForLoopNotRemoved() { - let input = "for foo in bar?.map({ $0.baz }) ?? [] {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testParensAroundTrailingClosureInGuardCaseLetNotRemoved() { - let input = "guard case let .foo(bar) = baz.filter({ $0 == quux }).isEmpty else {}" - testFormatting(for: input, rule: FormatRules.trailingClosures, - exclude: ["wrapConditionalBodies"]) - } - - func testParensAroundTrailingClosureInWhereClauseLetNotRemoved() { - let input = "for foo in bar where baz.filter({ $0 == quux }).isEmpty {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testParensAroundTrailingClosureInSwitchNotRemoved() { - let input = "switch foo({ $0 == bar }).count {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testSolitaryClosureMadeTrailingInChain() { - let input = "foo.map({ $0.path }).joined()" - let output = "foo.map { $0.path }.joined()" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testSpaceNotInsertedAfterClosureBeforeUnwrap() { - let input = "let foo = bar.map({ foo($0) })?.baz" - let output = "let foo = bar.map { foo($0) }?.baz" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testSpaceNotInsertedAfterClosureBeforeForceUnwrap() { - let input = "let foo = bar.map({ foo($0) })!.baz" - let output = "let foo = bar.map { foo($0) }!.baz" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testSolitaryClosureMadeTrailingForNumericTupleMember() { - let input = "foo.1({ bar })" - let output = "foo.1 { bar }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - // dispatch methods - - func testDispatchAsyncClosureArgumentMadeTrailing() { - let input = "queue.async(execute: { /* some code */ })" - let output = "queue.async { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testDispatchAsyncGroupClosureArgumentMadeTrailing() { - // TODO: async(group: , qos: , flags: , execute: ) - let input = "queue.async(group: g, execute: { /* some code */ })" - let output = "queue.async(group: g) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testDispatchAsyncAfterClosureArgumentMadeTrailing() { - let input = "queue.asyncAfter(deadline: t, execute: { /* some code */ })" - let output = "queue.asyncAfter(deadline: t) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testDispatchAsyncAfterWallClosureArgumentMadeTrailing() { - let input = "queue.asyncAfter(wallDeadline: t, execute: { /* some code */ })" - let output = "queue.asyncAfter(wallDeadline: t) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testDispatchSyncClosureArgumentMadeTrailing() { - let input = "queue.sync(execute: { /* some code */ })" - let output = "queue.sync { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - func testDispatchSyncFlagsClosureArgumentMadeTrailing() { - let input = "queue.sync(flags: f, execute: { /* some code */ })" - let output = "queue.sync(flags: f) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - // autoreleasepool - - func testAutoreleasepoolMadeTrailing() { - let input = "autoreleasepool(invoking: { /* some code */ })" - let output = "autoreleasepool { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) - } - - // explicit trailing closure methods - - func testCustomMethodMadeTrailing() { - let input = "foo(bar: 1, baz: { /* some code */ })" - let output = "foo(bar: 1) { /* some code */ }" - let options = FormatOptions(trailingClosures: ["foo"]) - testFormatting(for: input, output, rule: FormatRules.trailingClosures, options: options) - } - - // explicit non-trailing closure methods - - func testPerformBatchUpdatesNotMadeTrailing() { - let input = "collectionView.performBatchUpdates({ /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testNimbleExpectNotMadeTrailing() { - let input = "expect({ bar }).to(beNil())" - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - func testCustomMethodNotMadeTrailing() { - let input = "foo({ /* some code */ })" - let options = FormatOptions(neverTrailing: ["foo"]) - testFormatting(for: input, rule: FormatRules.trailingClosures, options: options) - } - - // multiple closures - - func testMultipleNestedClosures() throws { - let repeatCount = 10 - let input = """ - override func foo() { - bar { - var baz = 5 - \(String(repeating: """ - fizz { - buzz { - fizzbuzz() - } - } - - """, count: repeatCount)) } - } - """ - testFormatting(for: input, rule: FormatRules.trailingClosures) - } - - // MARK: - enumNamespaces - - func testEnumNamespacesClassAsProtocolRestriction() { - let input = """ - @objc protocol Foo: class { - @objc static var expressionTypes: [String: RuntimeType] { get } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesConformingOtherType() { - let input = "private final class CustomUITableViewCell: UITableViewCell {}" - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesImportClass() { - let input = """ - import class MyUIKit.AutoHeightTableView - - enum Foo { - static var bar: String - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesImportStruct() { - let input = """ - import struct Core.CurrencyFormatter - - enum Foo { - static var bar: String - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesClassFunction() { - let input = """ - class Container { - class func bar() {} - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesRemovingExtraKeywords() { - let input = """ - final class MyNamespace { - static let bar = "bar" - } - """ - let output = """ - enum MyNamespace { - static let bar = "bar" - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNestedTypes() { - let input = """ - enum Namespace {} - extension Namespace { - struct Constants { - static let bar = "bar" - } - } - """ - let output = """ - enum Namespace {} - extension Namespace { - enum Constants { - static let bar = "bar" - } - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNestedTypes2() { - let input = """ - struct Namespace { - struct NestedNamespace { - static let foo: Int - static let bar: Int - } - } - """ - let output = """ - enum Namespace { - enum NestedNamespace { - static let foo: Int - static let bar: Int - } - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNestedTypes3() { - let input = """ - struct Namespace { - struct TypeNestedInNamespace { - let foo: Int - let bar: Int - } - } - """ - let output = """ - enum Namespace { - struct TypeNestedInNamespace { - let foo: Int - let bar: Int - } - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNestedTypes4() { - let input = """ - struct Namespace { - static func staticFunction() { - struct NestedType { - init() {} - } - } - } - """ - let output = """ - enum Namespace { - static func staticFunction() { - struct NestedType { - init() {} - } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNestedTypes5() { - let input = """ - struct Namespace { - static func staticFunction() { - func nestedFunction() { /* ... */ } - } - } - """ - let output = """ - enum Namespace { - static func staticFunction() { - func nestedFunction() { /* ... */ } - } - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesStaticVariable() { - let input = """ - struct Constants { - static let β = 0, 5 - } - """ - let output = """ - enum Constants { - static let β = 0, 5 - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesStaticAndInstanceVariable() { - let input = """ - struct Constants { - static let β = 0, 5 - let Ɣ = 0, 3 - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesStaticFunction() { - let input = """ - struct Constants { - static func remoteConfig() -> Int { - return 10 - } - } - """ - let output = """ - enum Constants { - static func remoteConfig() -> Int { - return 10 - } - } - """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesStaticAndInstanceFunction() { - let input = """ - struct Constants { - static func remoteConfig() -> Int { - return 10 - } - - func instanceConfig(offset: Int) -> Int { - return offset + 10 - } - } - """ - - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespaceDoesNothing() { - let input = """ - struct Foo { - #if BAR - func something() {} - #else - func something() {} - #endif - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespaceDoesNothingForEmptyDeclaration() { - let input = """ - struct Foo {} - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfTypeInitializedInternally() { - let input = """ - struct Foo { - static func bar() { - Foo().baz - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfInitializedInternally() { - let input = """ - struct Foo { - static func bar() { - Self().baz - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfInitializedInternally2() { - let input = """ - struct Foo { - static func bar() -> Foo { - self.init() - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfAssignedInternally() { - let input = """ - class Foo { - public static func bar() { - let bundle = Bundle(for: self) - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfAssignedInternally2() { - let input = """ - class Foo { - public static func bar() { - let `class` = self - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfAssignedInternally3() { - let input = """ - class Foo { - public static func bar() { - let `class` = Foo.self - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testClassFuncNotReplacedByEnum() { - let input = """ - class Foo { - class override func foo() { - Bar.bar() - } - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces, - exclude: ["modifierOrder"]) - } - - func testOpenClassNotReplacedByEnum() { - let input = """ - open class Foo { - public static let bar = "bar" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testClassNotReplacedByEnum() { - let input = """ - class Foo { - public static let bar = "bar" - } - """ - let options = FormatOptions(enumNamespaces: .structsOnly) - testFormatting(for: input, rule: FormatRules.enumNamespaces, options: options) - } - - func testEnumNamespacesAfterImport() { - // https://github.com/nicklockwood/SwiftFormat/issues/1569 - let input = """ - import Foundation - - final class MyViewModel2 { - static let = "A" - } - """ - - let output = """ - import Foundation - - enum MyViewModel2 { - static let = "A" - } - """ - - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesAfterImport2() { - // https://github.com/nicklockwood/SwiftFormat/issues/1569 - let input = """ - final class MyViewModel { - static let = "A" - } - - import Foundation - - final class MyViewModel2 { - static let = "A" - } - """ - - let output = """ - enum MyViewModel { - static let = "A" - } - - import Foundation - - enum MyViewModel2 { - static let = "A" - } - """ - - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNotAppliedToNonFinalClass() { - let input = """ - class Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfObjC() { - let input = """ - @objc(NSFoo) - final class Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfMacro() { - let input = """ - @FooBar - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfParameterizedMacro() { - let input = """ - @FooMacro(arg: "Foo") - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfGenericMacro() { - let input = """ - @FooMacro - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfGenericParameterizedMacro() { - let input = """ - @FooMacro(arg: 5) - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) - } - - // MARK: - numberFormatting - - // hex case - - func testLowercaseLiteralConvertedToUpper() { - let input = "let foo = 0xabcd" - let output = "let foo = 0xABCD" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testMixedCaseLiteralConvertedToUpper() { - let input = "let foo = 0xaBcD" - let output = "let foo = 0xABCD" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testUppercaseLiteralConvertedToLower() { - let input = "let foo = 0xABCD" - let output = "let foo = 0xabcd" - let options = FormatOptions(uppercaseHex: false) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testPInExponentialNotConvertedToUpper() { - let input = "let foo = 0xaBcDp5" - let output = "let foo = 0xABCDp5" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testPInExponentialNotConvertedToLower() { - let input = "let foo = 0xaBcDP5" - let output = "let foo = 0xabcdP5" - let options = FormatOptions(uppercaseHex: false, uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - // exponent case - - func testLowercaseExponent() { - let input = "let foo = 0.456E-5" - let output = "let foo = 0.456e-5" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testUppercaseExponent() { - let input = "let foo = 0.456e-5" - let output = "let foo = 0.456E-5" - let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testUppercaseHexExponent() { - let input = "let foo = 0xFF00p54" - let output = "let foo = 0xFF00P54" - let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testUppercaseGroupedHexExponent() { - let input = "let foo = 0xFF00_AABB_CCDDp54" - let output = "let foo = 0xFF00_AABB_CCDDP54" - let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - // decimal grouping - - func testDefaultDecimalGrouping() { - let input = "let foo = 1234_56_78" - let output = "let foo = 12_345_678" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testIgnoreDecimalGrouping() { - let input = "let foo = 1234_5_678" - let options = FormatOptions(decimalGrouping: .ignore) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) - } - - func testNoDecimalGrouping() { - let input = "let foo = 1234_5_678" - let output = "let foo = 12345678" - let options = FormatOptions(decimalGrouping: .none) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testDecimalGroupingThousands() { - let input = "let foo = 1234" - let output = "let foo = 1_234" - let options = FormatOptions(decimalGrouping: .group(3, 3)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testExponentialGrouping() { - let input = "let foo = 1234e5678" - let output = "let foo = 1_234e5678" - let options = FormatOptions(decimalGrouping: .group(3, 3)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testZeroGrouping() { - let input = "let foo = 1234" - let options = FormatOptions(decimalGrouping: .group(0, 0)) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) - } - - // binary grouping - - func testDefaultBinaryGrouping() { - let input = "let foo = 0b11101000_00111111" - let output = "let foo = 0b1110_1000_0011_1111" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testIgnoreBinaryGrouping() { - let input = "let foo = 0b1110_10_00" - let options = FormatOptions(binaryGrouping: .ignore) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) - } - - func testNoBinaryGrouping() { - let input = "let foo = 0b1110_10_00" - let output = "let foo = 0b11101000" - let options = FormatOptions(binaryGrouping: .none) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testBinaryGroupingCustom() { - let input = "let foo = 0b110011" - let output = "let foo = 0b11_00_11" - let options = FormatOptions(binaryGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - // hex grouping - - func testDefaultHexGrouping() { - let input = "let foo = 0xFF01FF01AE45" - let output = "let foo = 0xFF01_FF01_AE45" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testCustomHexGrouping() { - let input = "let foo = 0xFF00p54" - let output = "let foo = 0xFF_00p54" - let options = FormatOptions(hexGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - // octal grouping - - func testDefaultOctalGrouping() { - let input = "let foo = 0o123456701234" - let output = "let foo = 0o1234_5670_1234" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) - } - - func testCustomOctalGrouping() { - let input = "let foo = 0o12345670" - let output = "let foo = 0o12_34_56_70" - let options = FormatOptions(octalGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - // fraction grouping - - func testIgnoreFractionGrouping() { - let input = "let foo = 1.234_5_678" - let options = FormatOptions(decimalGrouping: .ignore, fractionGrouping: true) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) - } - - func testNoFractionGrouping() { - let input = "let foo = 1.234_5_678" - let output = "let foo = 1.2345678" - let options = FormatOptions(decimalGrouping: .none, fractionGrouping: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testFractionGroupingThousands() { - let input = "let foo = 12.34_56_78" - let output = "let foo = 12.345_678" - let options = FormatOptions(decimalGrouping: .group(3, 3), fractionGrouping: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - func testHexFractionGrouping() { - let input = "let foo = 0x12.34_56_78p56" - let output = "let foo = 0x12.34_5678p56" - let options = FormatOptions(hexGrouping: .group(4, 4), fractionGrouping: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) - } - - // MARK: - andOperator - - func testIfAndReplaced() { - let input = "if true && true {}" - let output = "if true, true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testGuardAndReplaced() { - let input = "guard true && true\nelse { return }" - let output = "guard true, true\nelse { return }" - testFormatting(for: input, output, rule: FormatRules.andOperator, - exclude: ["wrapConditionalBodies"]) - } - - func testWhileAndReplaced() { - let input = "while true && true {}" - let output = "while true, true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testIfDoubleAndReplaced() { - let input = "if true && true && true {}" - let output = "if true, true, true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testIfAndParensReplaced() { - let input = "if true && (true && true) {}" - let output = "if true, (true && true) {}" - testFormatting(for: input, output, rule: FormatRules.andOperator, - exclude: ["redundantParens"]) - } - - func testIfFunctionAndReplaced() { - let input = "if functionReturnsBool() && true {}" - let output = "if functionReturnsBool(), true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testNoReplaceIfOrAnd() { - let input = "if foo || bar && baz {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceIfAndOr() { - let input = "if foo && bar || baz {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testIfAndReplacedInFunction() { - let input = "func someFunc() { if bar && baz {} }" - let output = "func someFunc() { if bar, baz {} }" - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testNoReplaceIfCaseLetAnd() { - let input = "if case let a = foo && bar {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceWhileCaseLetAnd() { - let input = "while case let a = foo && bar {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceRepeatWhileAnd() { - let input = """ - repeat {} while true && !false - foo {} - """ - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceIfLetAndLetAnd() { - let input = "if let a = b && c, let d = e && f {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceIfTryAnd() { - let input = "if try true && explode() {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testHandleAndAtStartOfLine() { - let input = "if a == b\n && b == c {}" - let output = "if a == b,\n b == c {}" - testFormatting(for: input, output, rule: FormatRules.andOperator, exclude: ["indent"]) - } - - func testHandleAndAtStartOfLineAfterComment() { - let input = "if a == b // foo\n && b == c {}" - let output = "if a == b, // foo\n b == c {}" - testFormatting(for: input, output, rule: FormatRules.andOperator, exclude: ["indent"]) - } - - func testNoReplaceAndOperatorWhereGenericsAmbiguous() { - let input = "if x < y && z > (a * b) {}" - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceAndOperatorWhereGenericsAmbiguous2() { - let input = "if x < y && z && w > (a * b) {}" - let output = "if x < y, z && w > (a * b) {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testAndOperatorCrash() { - let input = """ - DragGesture().onChanged { gesture in - if gesture.translation.width < 50 && gesture.translation.height > 50 { - offset = gesture.translation - } - } - """ - let output = """ - DragGesture().onChanged { gesture in - if gesture.translation.width < 50, gesture.translation.height > 50 { - offset = gesture.translation - } - } - """ - testFormatting(for: input, output, rule: FormatRules.andOperator) - } - - func testNoReplaceAndInViewBuilder() { - let input = """ - SomeView { - if foo == 5 && bar { - Text("5") - } else { - Text("Not 5") - } - } - """ - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testNoReplaceAndInViewBuilder2() { - let input = """ - var body: some View { - ZStack { - if self.foo && self.bar { - self.closedPath - } - } - } - """ - testFormatting(for: input, rule: FormatRules.andOperator) - } - - func testReplaceAndInViewBuilderInSwift5_3() { - let input = """ - SomeView { - if foo == 5 && bar { - Text("5") - } else { - Text("Not 5") - } - } - """ - let output = """ - SomeView { - if foo == 5, bar { - Text("5") - } else { - Text("Not 5") - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.andOperator, options: options) - } - - // MARK: - isEmpty - - // count == 0 - - func testCountEqualsZero() { - let input = "if foo.count == 0 {}" - let output = "if foo.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testFunctionCountEqualsZero() { - let input = "if foo().count == 0 {}" - let output = "if foo().isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testExpressionCountEqualsZero() { - let input = "if foo || bar.count == 0 {}" - let output = "if foo || bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCompoundIfCountEqualsZero() { - let input = "if foo, bar.count == 0 {}" - let output = "if foo, bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testOptionalCountEqualsZero() { - let input = "if foo?.count == 0 {}" - let output = "if foo?.isEmpty == true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testOptionalChainCountEqualsZero() { - let input = "if foo?.bar.count == 0 {}" - let output = "if foo?.bar.isEmpty == true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCompoundIfOptionalCountEqualsZero() { - let input = "if foo, bar?.count == 0 {}" - let output = "if foo, bar?.isEmpty == true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testTernaryCountEqualsZero() { - let input = "foo ? bar.count == 0 : baz.count == 0" - let output = "foo ? bar.isEmpty : baz.isEmpty" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - // count != 0 - - func testCountNotEqualToZero() { - let input = "if foo.count != 0 {}" - let output = "if !foo.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testFunctionCountNotEqualToZero() { - let input = "if foo().count != 0 {}" - let output = "if !foo().isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testExpressionCountNotEqualToZero() { - let input = "if foo || bar.count != 0 {}" - let output = "if foo || !bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCompoundIfCountNotEqualToZero() { - let input = "if foo, bar.count != 0 {}" - let output = "if foo, !bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - // count > 0 - - func testCountGreaterThanZero() { - let input = "if foo.count > 0 {}" - let output = "if !foo.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCountExpressionGreaterThanZero() { - let input = "if a.count - b.count > 0 {}" - testFormatting(for: input, rule: FormatRules.isEmpty) - } - - // optional count - - func testOptionalCountNotEqualToZero() { - let input = "if foo?.count != 0 {}" // nil evaluates to true - let output = "if foo?.isEmpty != true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testOptionalChainCountNotEqualToZero() { - let input = "if foo?.bar.count != 0 {}" // nil evaluates to true - let output = "if foo?.bar.isEmpty != true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCompoundIfOptionalCountNotEqualToZero() { - let input = "if foo, bar?.count != 0 {}" - let output = "if foo, bar?.isEmpty != true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - // edge cases - - func testTernaryCountNotEqualToZero() { - let input = "foo ? bar.count != 0 : baz.count != 0" - let output = "foo ? !bar.isEmpty : !baz.isEmpty" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCountEqualsZeroAfterOptionalOnPreviousLine() { - let input = "_ = foo?.bar\nbar.count == 0 ? baz() : quux()" - let output = "_ = foo?.bar\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCountEqualsZeroAfterOptionalCallOnPreviousLine() { - let input = "foo?.bar()\nbar.count == 0 ? baz() : quux()" - let output = "foo?.bar()\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCountEqualsZeroAfterTrailingCommentOnPreviousLine() { - let input = "foo?.bar() // foobar\nbar.count == 0 ? baz() : quux()" - let output = "foo?.bar() // foobar\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCountGreaterThanZeroAfterOpenParen() { - let input = "foo(bar.count > 0)" - let output = "foo(!bar.isEmpty)" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - func testCountGreaterThanZeroAfterArgumentLabel() { - let input = "foo(bar: baz.count > 0)" - let output = "foo(bar: !baz.isEmpty)" - testFormatting(for: input, output, rule: FormatRules.isEmpty) - } - - // MARK: - anyObjectProtocol - - func testClassReplacedByAnyObject() { - let input = "protocol Foo: class {}" - let output = "protocol Foo: AnyObject {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: FormatRules.anyObjectProtocol, options: options) - } - - func testClassReplacedByAnyObjectWithOtherProtocols() { - let input = "protocol Foo: class, Codable {}" - let output = "protocol Foo: AnyObject, Codable {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: FormatRules.anyObjectProtocol, options: options) - } - - func testClassReplacedByAnyObjectImmediatelyAfterImport() { - let input = "import Foundation\nprotocol Foo: class {}" - let output = "import Foundation\nprotocol Foo: AnyObject {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: FormatRules.anyObjectProtocol, options: options, - exclude: ["blankLineAfterImports"]) - } - - func testClassDeclarationNotReplacedByAnyObject() { - let input = "class Foo: Codable {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options) - } - - func testClassImportNotReplacedByAnyObject() { - let input = "import class Foo.Bar" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options) - } - - func testClassNotReplacedByAnyObjectIfSwiftVersionLessThan4_1() { - let input = "protocol Foo: class {}" - let options = FormatOptions(swiftVersion: "4.0") - testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options) - } - - // MARK: - applicationMain - - func testUIApplicationMainReplacedByMain() { - let input = """ - @UIApplicationMain - class AppDelegate: UIResponder, UIApplicationDelegate {} - """ - let output = """ - @main - class AppDelegate: UIResponder, UIApplicationDelegate {} - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.applicationMain, options: options) - } - - func testNSApplicationMainReplacedByMain() { - let input = """ - @NSApplicationMain - class AppDelegate: NSObject, NSApplicationDelegate {} - """ - let output = """ - @main - class AppDelegate: NSObject, NSApplicationDelegate {} - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.applicationMain, options: options) - } - - func testNSApplicationMainNotReplacedInSwift5_2() { - let input = """ - @NSApplicationMain - class AppDelegate: NSObject, NSApplicationDelegate {} - """ - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.applicationMain, options: options) - } - - // MARK: - typeSugar - - // arrays - - func testArrayTypeConvertedToSugar() { - let input = "var foo: Array" - let output = "var foo: [String]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testSwiftArrayTypeConvertedToSugar() { - let input = "var foo: Swift.Array" - let output = "var foo: [String]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testArrayNestedTypeAliasNotConvertedToSugar() { - let input = "typealias Indices = Array.Indices" - testFormatting(for: input, rule: FormatRules.typeSugar) - } - - func testArrayTypeReferenceConvertedToSugar() { - let input = "let type = Array.Type" - let output = "let type = [Foo].Type" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testSwiftArrayTypeReferenceConvertedToSugar() { - let input = "let type = Swift.Array.Type" - let output = "let type = [Foo].Type" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testArraySelfReferenceConvertedToSugar() { - let input = "let type = Array.self" - let output = "let type = [Foo].self" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testSwiftArraySelfReferenceConvertedToSugar() { - let input = "let type = Swift.Array.self" - let output = "let type = [Foo].self" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testArrayDeclarationNotConvertedToSugar() { - let input = "struct Array {}" - testFormatting(for: input, rule: FormatRules.typeSugar) - } - - func testExtensionTypeSugar() { - let input = """ - extension Array {} - extension Optional {} - extension Dictionary {} - extension Optional>>> {} - """ - - let output = """ - extension [Foo] {} - extension Foo? {} - extension [Foo: Bar] {} - extension [[Foo: [Bar]]]? {} - """ - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - // dictionaries - - func testDictionaryTypeConvertedToSugar() { - let input = "var foo: Dictionary" - let output = "var foo: [String: Int]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testSwiftDictionaryTypeConvertedToSugar() { - let input = "var foo: Swift.Dictionary" - let output = "var foo: [String: Int]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - // optionals - - func testOptionalPropertyTypeNotConvertedToSugarByDefault() { - let input = "var bar: Optional" - testFormatting(for: input, rule: FormatRules.typeSugar) - } - - func testOptionalTypeConvertedToSugar() { - let input = "var foo: Optional" - let output = "var foo: String?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testSwiftOptionalTypeConvertedToSugar() { - let input = "var foo: Swift.Optional" - let output = "var foo: String?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testOptionalClosureParenthesizedConvertedToSugar() { - let input = "var foo: Optional<(Int) -> String>" - let output = "var foo: ((Int) -> String)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testOptionalTupleWrappedInParensConvertedToSugar() { - let input = "let foo: Optional<(foo: Int, bar: String)>" - let output = "let foo: (foo: Int, bar: String)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testOptionalComposedProtocolWrappedInParensConvertedToSugar() { - let input = "let foo: Optional" - let output = "let foo: (UIView & Foo)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testSwiftOptionalClosureParenthesizedConvertedToSugar() { - let input = "var foo: Swift.Optional<(Int) -> String>" - let output = "var foo: ((Int) -> String)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testStrippingSwiftNamespaceInOptionalTypeWhenConvertedToSugar() { - let input = "Swift.Optional" - let output = "String?" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - func testStrippingSwiftNamespaceDoesNotStripPreviousSwiftNamespaceReferences() { - let input = "let a: Swift.String = Optional" - let output = "let a: Swift.String = String?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testOptionalTypeInsideCaseConvertedToSugar() { - let input = "if case .some(Optional.some(let foo)) = bar else {}" - let output = "if case .some(Any?.some(let foo)) = bar else {}" - testFormatting(for: input, output, rule: FormatRules.typeSugar, exclude: ["hoistPatternLet"]) - } - - func testSwitchCaseOptionalNotReplaced() { - let input = """ - switch foo { - case Optional.none: - } - """ - testFormatting(for: input, rule: FormatRules.typeSugar) - } - - func testCaseOptionalNotReplaced2() { - let input = "if case Optional.none = foo {}" - testFormatting(for: input, rule: FormatRules.typeSugar) - } - - func testUnwrappedOptionalSomeParenthesized() { - let input = "func foo() -> Optional> {}" - let output = "func foo() -> (some Publisher)? {}" - testFormatting(for: input, output, rule: FormatRules.typeSugar) - } - - // swift parser bug - - func testAvoidSwiftParserBugWithClosuresInsideArrays() { - let input = "var foo = Array<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) - } - - func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { - let input = "var foo = Dictionary Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) - } - - func testAvoidSwiftParserBugWithClosuresInsideOptionals() { - let input = "var foo = Optional<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) - } - - func testDontOverApplyBugWorkaround() { - let input = "var foo: Array<(_ image: Data?) -> Void>" - let output = "var foo: [(_ image: Data?) -> Void]" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testDontOverApplyBugWorkaround2() { - let input = "var foo: Dictionary Void>" - let output = "var foo: [String: (_ image: Data?) -> Void]" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testDontOverApplyBugWorkaround3() { - let input = "var foo: Optional<(_ image: Data?) -> Void>" - let output = "var foo: ((_ image: Data?) -> Void)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) - } - - func testDontOverApplyBugWorkaround4() { - let input = "var foo = Array<(image: Data?) -> Void>()" - let output = "var foo = [(image: Data?) -> Void]()" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) - } - - func testDontOverApplyBugWorkaround5() { - let input = "var foo = Array<(Data?) -> Void>()" - let output = "var foo = [(Data?) -> Void]()" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) - } - - func testDontOverApplyBugWorkaround6() { - let input = "var foo = Dictionary Void>>()" - let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) - } - - // MARK: - preferKeyPath - - func testMapPropertyToKeyPath() { - let input = "let foo = bar.map { $0.foo }" - let output = "let foo = bar.map(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, - options: options) - } - - func testCompactMapPropertyToKeyPath() { - let input = "let foo = bar.compactMap { $0.foo }" - let output = "let foo = bar.compactMap(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, - options: options) - } - - func testFlatMapPropertyToKeyPath() { - let input = "let foo = bar.flatMap { $0.foo }" - let output = "let foo = bar.flatMap(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, - options: options) - } - - func testMapNestedPropertyWithSpacesToKeyPath() { - let input = "let foo = bar.map { $0 . foo . bar }" - let output = "let foo = bar.map(\\ . foo . bar)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, - options: options, exclude: ["spaceAroundOperators"]) - } - - func testMultilineMapPropertyToKeyPath() { - let input = """ - let foo = bar.map { - $0.foo - } - """ - let output = "let foo = bar.map(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, - options: options) - } - - func testParenthesizedMapPropertyToKeyPath() { - let input = "let foo = bar.map({ $0.foo })" - let output = "let foo = bar.map(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, - options: options) - } - - func testNoMapSelfToKeyPath() { - let input = "let foo = bar.map { $0 }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForSwiftLessThan5_2() { - let input = "let foo = bar.map { $0.foo }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForFunctionCalls() { - let input = "let foo = bar.map { $0.foo() }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForCompoundExpressions() { - let input = "let foo = bar.map { $0.foo || baz }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForOptionalChaining() { - let input = "let foo = bar.map { $0?.foo }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForTrailingContains() { - let input = "let foo = bar.contains { $0.foo }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - func testMapPropertyToKeyPathForContainsWhere() { - let input = "let foo = bar.contains(where: { $0.foo })" - let output = "let foo = bar.contains(where: \\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, options: options) - } - - func testMultipleTrailingClosuresNotConvertedToKeyPath() { - let input = "foo.map { $0.bar } reverse: { $0.bar }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) - } - - // MARK: - assertionFailures - - func testAssertionFailuresForAssertFalse() { - let input = "assert(false)" - let output = "assertionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForAssertFalseWithSpaces() { - let input = "assert ( false )" - let output = "assertionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForAssertFalseWithLinebreaks() { - let input = """ - assert( - false - ) - """ - let output = "assertionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForAssertTrue() { - let input = "assert(true)" - testFormatting(for: input, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForAssertFalseWithArgs() { - let input = "assert(false, msg, 20, 21)" - let output = "assertionFailure(msg, 20, 21)" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForPreconditionFalse() { - let input = "precondition(false)" - let output = "preconditionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForPreconditionTrue() { - let input = "precondition(true)" - testFormatting(for: input, rule: FormatRules.assertionFailures) - } - - func testAssertionFailuresForPreconditionFalseWithArgs() { - let input = "precondition(false, msg, 0, 1)" - let output = "preconditionFailure(msg, 0, 1)" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) - } - - // MARK: - acronyms - - func testUppercaseAcronyms() { - let input = """ - let url: URL - let destinationUrl: URL - let id: ID - let screenId = "screenId" // We intentionally don't change the content of strings - let validUrls: Set - let validUrlschemes: Set - - let uniqueIdentifier = UUID() - - /// Opens Urls based on their scheme - struct UrlRouter {} - - /// The Id of a screen that can be displayed in the app - struct ScreenId {} - """ - - let output = """ - let url: URL - let destinationURL: URL - let id: ID - let screenID = "screenId" // We intentionally don't change the content of strings - let validURLs: Set - let validUrlschemes: Set - - let uniqueIdentifier = UUID() - - /// Opens URLs based on their scheme - struct URLRouter {} - - /// The ID of a screen that can be displayed in the app - struct ScreenID {} - """ - - testFormatting(for: input, output, rule: FormatRules.acronyms, exclude: ["propertyType"]) - } - - func testUppercaseCustomAcronym() { - let input = """ - let url: URL - let destinationUrl: URL - let pngData: Data - let imageInPngFormat: UIImage - """ - - let output = """ - let url: URL - let destinationUrl: URL - let pngData: Data - let imageInPNGFormat: UIImage - """ - - testFormatting(for: input, output, rule: FormatRules.acronyms, options: FormatOptions(acronyms: ["png"])) - } - - func testDisableUppercaseAcronym() { - let input = """ - // swiftformat:disable:next acronyms - typeNotOwnedByAuthor.destinationUrl = URL() - typeOwnedByAuthor.destinationURL = URL() - """ - - testFormatting(for: input, rule: FormatRules.acronyms) - } - - // MARK: - blockComments - - func testBlockCommentsOneLine() { - let input = "foo = bar /* comment */" - let output = "foo = bar // comment" - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testDocBlockCommentsOneLine() { - let input = "foo = bar /** doc comment */" - let output = "foo = bar /// doc comment" - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testPreservesBlockCommentInSingleLineScope() { - let input = "if foo { /* code */ }" - testFormatting(for: input, rule: FormatRules.blockComments) - } - - func testBlockCommentsMultiLine() { - let input = """ - /* - * foo - * bar - */ - """ - let output = """ - // foo - // bar - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentsWithoutBlankFirstLine() { - let input = """ - /* foo - * bar - */ - """ - let output = """ - // foo - // bar - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentsWithBlankLine() { - let input = """ - /* - * foo - * - * bar - */ - """ - let output = """ - // foo - // - // bar - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockDocCommentsWithAsterisksOnEachLine() { - let input = """ - /** - * This is a documentation comment, - * not a standard comment. - */ - """ - let output = """ - /// This is a documentation comment, - /// not a standard comment. - """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) - } - - func testBlockDocCommentsWithoutAsterisksOnEachLine() { - let input = """ - /** - This is a documentation comment, - not a standard comment. - */ - """ - let output = """ - /// This is a documentation comment, - /// not a standard comment. - """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) - } - - func testBlockCommentWithBulletPoints() { - let input = """ - /* - This is a list of nice colors: - - * green - * blue - * red - - Yellow is also great. - */ - - /* - * Another comment. - */ - """ - let output = """ - // This is a list of nice colors: - // - // * green - // * blue - // * red - // - // Yellow is also great. - - // Another comment. - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentsNested() { - let input = """ - /* - * comment - * /* inside */ - * a comment - */ - """ - let output = """ - // comment - // inside - // a comment - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentsIndentPreserved() { - let input = """ - func foo() { - /* - foo - bar. - */ - } - """ - let output = """ - func foo() { - // foo - // bar. - } - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentsIndentPreserved2() { - let input = """ - func foo() { - /* - * foo - * bar. - */ - } - """ - let output = """ - func foo() { - // foo - // bar. - } - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockDocCommentsIndentPreserved() { - let input = """ - func foo() { - /** - * foo - * bar. - */ - } - """ - let output = """ - func foo() { - /// foo - /// bar. - } - """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) - } - - func testLongBlockCommentsWithoutPerLineMarkersFullyConverted() { - let input = """ - /* - The beginnings of the lines in this multiline comment body - have only spaces in them. There are no asterisks, only spaces. - - This should not cause the blockComments rule to convert only - part of the comment body and leave the rest hanging. - - The comment must have at least this many lines to trigger the bug. - */ - """ - let output = """ - // The beginnings of the lines in this multiline comment body - // have only spaces in them. There are no asterisks, only spaces. - // - // This should not cause the blockComments rule to convert only - // part of the comment body and leave the rest hanging. - // - // The comment must have at least this many lines to trigger the bug. - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentImmediatelyFollowedByCode() { - let input = """ - /** - foo - - bar - */ - func foo() {} - """ - let output = """ - /// foo - /// - /// bar - func foo() {} - """ - testFormatting(for: input, output, rule: FormatRules.blockComments) - } - - func testBlockCommentImmediatelyFollowedByCode2() { - let input = """ - /** - Line 1. - - Line 2. - - Line 3. - */ - foo(bar) - """ - let output = """ - /// Line 1. - /// - /// Line 2. - /// - /// Line 3. - foo(bar) - """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) - } - - func testBlockCommentImmediatelyFollowedByCode3() { - let input = """ - /* foo - bar */ - func foo() {} - """ - let output = """ - // foo - // bar - func foo() {} - """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) - } - - func testBlockCommentFollowedByBlankLine() { - let input = """ - /** - foo - - bar - */ - - func foo() {} - """ - let output = """ - /// foo - /// - /// bar - - func foo() {} - """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) - } - - // MARK: - opaqueGenericParameters - - func testGenericNotModifiedBelowSwift5_7() { - let input = """ - func foo(_ value: T) { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithNoConstraint() { - let input = """ - func foo(_ value: T) { - print(value) - } - - init(_ value: T) { - print(value) - } - - subscript(_ value: T) -> Foo { - Foo(value) - } - - subscript(_ value: T) -> Foo { - get { - Foo(value) - } - set { - print(newValue) - } - } - """ - - let output = """ - func foo(_ value: some Any) { - print(value) - } - - init(_ value: some Any) { - print(value) - } - - subscript(_ value: some Any) -> Foo { - Foo(value) - } - - subscript(_ value: some Any) -> Foo { - get { - Foo(value) - } - set { - print(newValue) - } - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testDisableSomeAnyGenericType() { - let input = """ - func foo(_ value: T) { - print(value) - } - """ - - let options = FormatOptions(useSomeAny: false, swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithConstraintInBracket() { - let input = """ - func foo(_ fooable: T, barable: U) -> Baaz { - print(fooable, barable) - } - - init(_ fooable: T, barable: U) { - print(fooable, barable) - } - - subscript(_ fooable: T, barable: U) -> Any { - (fooable, barable) - } - """ - - let output = """ - func foo(_ fooable: some Fooable, barable: some Barable) -> Baaz { - print(fooable, barable) - } - - init(_ fooable: some Fooable, barable: some Barable) { - print(fooable, barable) - } - - subscript(_ fooable: some Fooable, barable: some Barable) -> Any { - (fooable, barable) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithConstraintsInWhereClause() { - let input = """ - func foo(_ t: T, _ u: U) -> Baaz where T: Fooable, T: Barable, U: Baazable { - print(t, u) - } - - init(_ t: T, _ u: U) where T: Fooable, T: Barable, U: Baazable { - print(t, u) - } - """ - - let output = """ - func foo(_ t: some Fooable & Barable, _ u: some Baazable) -> Baaz { - print(t, u) - } - - init(_ t: some Fooable & Barable, _ u: some Baazable) { - print(t, u) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterCanRemoveOneButNotOthers_onOneLine() { - let input = """ - func foo(_ foo: T, bar1: U, bar2: U) where S.AssociatedType == Baaz, T: Quuxable, U: Qaaxable { - print(foo, bar1, bar2) - } - """ - - let output = """ - func foo(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where S.AssociatedType == Baaz, U: Qaaxable { - print(foo, bar1, bar2) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterCanRemoveOneButNotOthers_onMultipleLines() { - let input = """ - func foo< - S: Baazable, - T: Fooable, - U: Barable - >(_ foo: T, bar1: U, bar2: U) where - S.AssociatedType == Baaz, - T: Quuxable, - U: Qaaxable - { - print(foo, bar1, bar2) - } - """ - - let output = """ - func foo< - S: Baazable, - U: Barable - >(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where - S.AssociatedType == Baaz, - U: Qaaxable - { - print(foo, bar1, bar2) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithUnknownAssociatedTypeConstraint() { - // If we knew that `T.AssociatedType` was the protocol's primary - // associated type we could update this to `value: some Fooable`, - // but we don't necessarily have that type information available. - // - If primary associated types become very widespread, it may make - // sense to assume (or have an option to assume) that this would work. - let input = """ - func foo(_ value: T) where T.AssociatedType == Bar { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithAssociatedTypeConformance() { - // There is no opaque generic parameter syntax that supports this type of constraint - let input = """ - func foo(_ value: T) where T.AssociatedType: Bar { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithKnownAssociatedTypeConstraint() { - // For known types (like those in the standard library), - // we are able to know their primary associated types - let input = """ - func foo(_ value: T) where T.Element == Foo { - print(value) - } - """ - - let output = """ - func foo(_ value: some Collection) { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithAssociatedTypeConstraint() { - let input = """ - func foo>(_: T) {} - func bar(_: T) where T: Collection {} - func baaz(_: T) where T == any Collection {} - """ - - let output = """ - func foo(_: some Collection) {} - func bar(_: some Collection) {} - func baaz(_: any Collection) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedInMultipleParameters() { - let input = """ - func foo(_ first: T, second: T) { - print(first, second) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedInClosureMultipleTimes() { - let input = """ - func foo(_ closure: (T) -> T) { - closure(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedAsReturnType() { - // A generic used as a return type is different from an opaque result type (SE-244). - // In `-> T where T: Fooable`, the generic type is caller-specified, but with - // `-> some Fooable` the generic type is specified by the function implementation. - // Because those represent different concepts, we can't convert between them. - let input = """ - func foo() -> T { - // ... - } - - func bar() -> T where T: Barable { - // ... - } - - func baaz() -> Set> { - // ... - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedAsReturnTypeAndParameter() { - // Since we can't change the return value, we can't change any of the use cases of T - let input = """ - func foo(_ value: T) -> T { - value - } - - func bar(_ value: T) -> T where T: Barable { - value - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericTypeWithClosureInWhereClauseDoesntCrash() { - let input = """ - struct Foo { - func bar(_ value: V) where U == @Sendable (V) -> Int {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericExtensionSameTypeConstraint() { - let input = """ - func foo(_ u: U) where U == String { - print(u) - } - """ - - let output = """ - func foo(_ u: String) { - print(u) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericExtensionSameTypeGenericConstraint() { - let input = """ - func foo(_ u: U, _ v: V) where U == V { - print(u, v) - } - - func foo(_ u: U, _ v: V) where V == U { - print(u, v) - } - """ - - let output = """ - func foo(_ u: V, _ v: V) { - print(u, v) - } - - func foo(_ u: U, _ v: U) { - print(u, v) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testIssue1269() { - let input = """ - func bar( - _ value: V, - _ work: () -> R - ) -> R - where Value == @Sendable () -> V, - V: Sendable - { - work() - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testVariadicParameterNotConvertedToOpaqueGeneric() { - let input = """ - func variadic(_ t: T...) { - print(t) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testNonGenericVariadicParametersDoesntPreventUsingOpaqueGenerics() { - let input = """ - func variadic(t: Any..., u: U, v: Any...) { - print(t, u, v) - } - """ - - let output = """ - func variadic(t: Any..., u: some Any, v: Any...) { - print(t, u, v) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testIssue1275() { - let input = """ - func loggedKeypath( - by _: KeyPath..., - actionKeyword _: UserActionKeyword, - identifier _: String - ) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testIssue1278() { - let input = """ - public struct Foo { - public func withValue( - _: V, - operation _: () throws -> R - ) rethrows -> R - where Value == @Sendable () -> V, - V: Sendable - {} - - public func withValue( - _: V, - operation _: () async throws -> R - ) async rethrows -> R - where Value == () -> V - {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testIssue1392() { - let input = """ - public struct Ref {} - - public extension Ref { - static func weak( - _: Base, - _: ReferenceWritableKeyPath - ) -> Ref where T? == Value {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testIssue1684() { - let input = """ - @_specialize(where S == Int) - func foo>(t: S) { - print(t) - } - """ - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericSimplifiedInMethodWithAttributeOrMacro() { - let input = """ - @MyResultBuilder - func foo(foo: T, bar: U) -> MyResult { - foo - bar - } - - @MyFunctionBodyMacro(withArgument: true) - func foo(foo: T, bar: U) { - print(foo, bar) - } - """ - - let output = """ - @MyResultBuilder - func foo(foo: some Foo, bar: some Bar) -> MyResult { - foo - bar - } - - @MyFunctionBodyMacro(withArgument: true) - func foo(foo: some Foo, bar: some Bar) { - print(foo, bar) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericThrowsTypeNotTreatedAsAny() { - let input = """ - func sample(error: ErrorType) throws(ErrorType) { - throw error - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - // MARK: - genericExtensions - - func testGenericExtensionNotModifiedBeforeSwift5_7() { - let input = "extension Array where Element == Foo {}" - - let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testUpdatesArrayGenericExtensionToAngleBracketSyntax() { - let input = "extension Array where Element == Foo {}" - let output = "extension Array {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) - } - - func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { - let input = "extension Optional where Wrapped == Foo {}" - let output = "extension Optional {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) - } - - func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { - let input = "extension Array where Self.Element == Foo {}" - let output = "extension Array {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) - } - - func testUpdatesArrayWithGenericElement() { - let input = "extension Array where Element == Foo {}" - let output = "extension Array> {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) - } - - func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { - let input = "extension Dictionary where Key == Foo, Value == Bar {}" - let output = "extension Dictionary {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) - } - - func testRequiresAllGenericTypesToBeProvided() { - // No type provided for `Value`, so we can't use the angle bracket syntax - let input = "extension Dictionary where Key == Foo {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.genericExtensions, options: options) - } - - func testHandlesNestedCollectionTypes() { - let input = "extension Array where Element == [[Foo: Bar]] {}" - let output = "extension Array<[[Foo: Bar]]> {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) - } - - func testDoesntUpdateIneligibleConstraints() { - // This could potentially by `extension Optional` in a future language version - // but that syntax isn't implemented as of Swift 5.7 - let input = "extension Optional where Wrapped: Fooable {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.genericExtensions, options: options) - } - - func testPreservesOtherConstraintsInWhereClause() { - let input = "extension Collection where Element == String, Index == Int {}" - let output = "extension Collection where Index == Int {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options) - } - - func testSupportsUserProvidedGenericTypes() { - let input = """ - extension StateStore where State == FooState, Action == FooAction {} - extension LinkedList where Element == Foo {} - """ - let output = """ - extension StateStore {} - extension LinkedList {} - """ - - let options = FormatOptions( - genericTypes: "LinkedList;StateStore", - swiftVersion: "5.7" - ) - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options) - } - - func testSupportsMultilineUserProvidedGenericTypes() { - let input = """ - extension Reducer where - State == MyFeatureState, - Action == MyFeatureAction, - Environment == ApplicationEnvironment - {} - """ - let output = """ - extension Reducer {} - """ - - let options = FormatOptions( - genericTypes: "Reducer", - swiftVersion: "5.7" - ) - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options) - } - - func testOpaqueGenericParametersRuleSuccessfullyTerminatesInSampleCode() { - let input = """ - class Service { - public func run() {} - private let foo: Foo - private func a() -> Eventual {} - private func b() -> Eventual {} - private func c() -> Eventual {} - private func d() -> Eventual {} - private func e() -> Eventual {} - private func f() -> Eventual {} - private func g() -> Eventual {} - private func h() -> Eventual {} - private func i() {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericParameterUsedInConstraintOfOtherTypeNotChanged() { - let input = """ - func combineResults( - _: Potential, - _: Potential - ) -> Potential where - Success == (Result, Result), - Failure == Never - {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericParameterInheritedFromContextNotRemoved() { - let input = """ - func assign( - on _: DispatchQueue, - to _: AssignTarget, - at _: ReferenceWritableKeyPath - ) where Value: Equatable {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericParameterUsedInBodyNotRemoved() { - let input = """ - func foo(_ value: T) { - typealias TTT = T - let casted = value as TTT - print(casted) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericParameterUsedAsClosureParameterNotRemoved() { - let input = """ - func foo(_: (Foo) -> Void) {} - func bar(_: (Foo) throws -> Void) {} - func baz(_: (Foo) throws(Bar) -> Void) {} - func baaz(_: (Foo) async -> Void) {} - func qux(_: (Foo) async throws -> Void) {} - func quux(_: (Foo) async throws(Bar) -> Void) {} - func qaax(_: ([Foo]) -> Void) {} - func qaax(_: ((Foo, Bar)) -> Void) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testFinalGenericParamRemovedProperlyWithoutHangingComma() { - let input = """ - func foo( - bar _: (Bar) -> Void, - baaz _: Baaz - ) {} - """ - - let output = """ - func foo( - bar _: (Bar) -> Void, - baaz _: some Any - ) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testAddsParensAroundTypeIfNecessary() { - let input = """ - func foo(_: Foo.Type) {} - func bar(_: Foo?) {} - """ - - let output = """ - func foo(_: (some Any).Type) {} - func bar(_: (some Any)?) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testHandlesSingleExactTypeGenericConstraint() { - let input = """ - func foo(with _: T) -> Foo where T == Dependencies {} - """ - - let output = """ - func foo(with _: Dependencies) -> Foo {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testGenericConstraintThatIsGeneric() { - let input = """ - class Foo {} - func foo>(_: T) {} - class Bar {} - func bar>(_: T) {} - """ - - let output = """ - class Foo {} - func foo(_: some Foo) {} - class Bar {} - func bar(_: some Bar) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testDoesntChangeTypeWithConstraintThatReferencesItself() { - // This is a weird one but in the actual code this comes from `ViewModelContext` is both defined - // on the parent type of this declaration (where it has additional important constraints), - // and again in the method itself. Changing this to an opaque parameter breaks the build, because - // it loses the generic constraints applied by the parent type. - let input = """ - func makeSections>(_: ViewModelContext) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParametersDoesntleaveTrailingComma() { - let input = "func f(x: U) -> T where T: A, U: B {}" - let output = "func f(x: some B) -> T where T: A {}" - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, - options: options, exclude: ["unusedArguments"]) - } - - // MARK: docComments - - func testConvertCommentsToDocComments() { - let input = """ - // Multi-line comment before class with - // attribute between comment and declaration - @objc - class Foo { - // Single line comment before property - let foo = Foo() - - // Single line comment before property with property wrapper - @State - let bar = Bar() - - // Single line comment - func foo() {} - - /* Single line block comment before method */ - func baaz() {} - - /* - Multi-line block comment before method with attribute. - - This comment has a blank line in it. - */ - @nonobjc - func baaz() {} - } - - // Enum with a case - enum Quux { - // Documentation on an enum case - case quux - } - - extension Collection where Element: Foo { - // Property in extension with where clause - var foo: Foo { - first! - } - } - """ - - let output = """ - /// Multi-line comment before class with - /// attribute between comment and declaration - @objc - class Foo { - /// Single line comment before property - let foo = Foo() - - /// Single line comment before property with property wrapper - @State - let bar = Bar() - - /// Single line comment - func foo() {} - - /** Single line block comment before method */ - func baaz() {} - - /** - Multi-line block comment before method with attribute. - - This comment has a blank line in it. - */ - @nonobjc - func baaz() {} - } - - /// Enum with a case - enum Quux { - /// Documentation on an enum case - case quux - } - - extension Collection where Element: Foo { - /// Property in extension with where clause - var foo: Foo { - first! - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.docComments, - exclude: ["spaceInsideComments", "propertyType"]) - } - - func testConvertDocCommentsToComments() { - let input = """ - /// Comment not associated with class - - class Foo { - /** Comment not associated with function */ - - func bar() { - /// Comment inside function declaration. - /// This one is multi-line. - - /// This comment is inside a function and precedes a declaration, - /// but we don't want to use doc comments inside property or function - /// scopes since users typically don't think of these as doc comments, - /// and this also breaks a common pattern where comments introduce - /// an entire following block of code (not just the property) - let bar: Bar? = Bar() - print(bar) - } - - var baaz: Baaz { - /// Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - var quux: Quux { - didSet { - /// Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - let output = """ - // Comment not associated with class - - class Foo { - /* Comment not associated with function */ - - func bar() { - // Comment inside function declaration. - // This one is multi-line. - - // This comment is inside a function and precedes a declaration, - // but we don't want to use doc comments inside property or function - // scopes since users typically don't think of these as doc comments, - // and this also breaks a common pattern where comments introduce - // an entire following block of code (not just the property) - let bar: Bar? = Bar() - print(bar) - } - - var baaz: Baaz { - // Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - var quux: Quux { - didSet { - // Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.docComments, - exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) - } - - func testPreservesDocComments() { - let input = """ - /// Comment not associated with class - - class Foo { - /** Comment not associated with function */ - - // Documentation for function - func bar() { - /// Comment inside function declaration. - /// This one is multi-line. - - /// This comment is inside a function and precedes a declaration. - /// Since the option to preserve doc comments is enabled, - /// it should be left as-is. - let bar: Bar? = Bar() - print(bar) - } - - // Documentation for property - var baaz: Baaz { - /// Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - // Documentation for function - var quux: Quux { - didSet { - /// Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - let output = """ - /// Comment not associated with class - - class Foo { - /** Comment not associated with function */ - - /// Documentation for function - func bar() { - /// Comment inside function declaration. - /// This one is multi-line. - - /// This comment is inside a function and precedes a declaration. - /// Since the option to preserve doc comments is enabled, - /// it should be left as-is. - let bar: Bar? = Bar() - print(bar) - } - - /// Documentation for property - var baaz: Baaz { - /// Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - /// Documentation for function - var quux: Quux { - didSet { - /// Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - let options = FormatOptions(preserveDocComments: true) - testFormatting(for: input, output, rule: FormatRules.docComments, options: options, exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) - } - - func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { - let input = """ - // Names of the planets - struct PlanetNames { - // Inner planets - let mercury = "Mercury" - let venus = "Venus" - let earth = "Earth" - let mars = "Mars" - - // Inner planets - let jupiter = "Jupiter" - let saturn = "Saturn" - let uranus = "Uranus" - let neptune = "Neptune" - - /// Dwarf planets - let pluto = "Pluto" - let ceres = "Ceres" - } - """ - - let output = """ - /// Names of the planets - struct PlanetNames { - // Inner planets - let mercury = "Mercury" - let venus = "Venus" - let earth = "Earth" - let mars = "Mars" - - // Inner planets - let jupiter = "Jupiter" - let saturn = "Saturn" - let uranus = "Uranus" - let neptune = "Neptune" - - /// Dwarf planets - let pluto = "Pluto" - let ceres = "Ceres" - } - """ - - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testConvertsCommentsToDocCommentsInConsecutiveDeclarations() { - let input = """ - // Names of the planets - enum PlanetNames { - // Mercuy - case mercury - // Venus - case venus - // Earth - case earth - // Mars - case mars - - // Jupiter - case jupiter - - // Saturn - case saturn - - // Uranus - case uranus - - // Neptune - case neptune - } - """ - - let output = """ - /// Names of the planets - enum PlanetNames { - /// Mercuy - case mercury - /// Venus - case venus - /// Earth - case earth - /// Mars - case mars - - /// Jupiter - case jupiter - - /// Saturn - case saturn - - /// Uranus - case uranus - - /// Neptune - case neptune - } - """ - - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testDoesntConvertCommentBeforeConsecutiveEnumCasesToDocComment() { - let input = """ - // Names of the planets - enum PlanetNames { - // Inner planets - case mercury - case venus - case earth - case mars - - // Inner planets - case jupiter - case saturn - case uranus - case neptune - - // Dwarf planets - case pluto - case ceres - } - """ - - let output = """ - /// Names of the planets - enum PlanetNames { - // Inner planets - case mercury - case venus - case earth - case mars - - // Inner planets - case jupiter - case saturn - case uranus - case neptune - - // Dwarf planets - case pluto - case ceres - } - """ - - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testDoesntConvertAnnotationCommentsToDocComments() { - let input = """ - // swiftformat:disable some_swift_format_rule - let testSwiftLint: Foo - - // swiftlint:disable some_swift_lint_rule - let testSwiftLint: Foo - - // sourcery:directive - let testSourcery: Foo - """ - - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testDoesntConvertTODOCommentsToDocComments() { - let input = """ - // TODO: Clean up this mess - func doSomething() {} - """ - - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testDoesntConvertCommentAfterTODOToDocComments() { - let input = """ - // TODO: Clean up this mess - // because it's bothering me - func doSomething() {} - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testDoesntConvertCommentBeforeTODOToDocComments() { - let input = """ - // Something, something - // TODO: Clean up this mess - func doSomething() {} - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testConvertNoteCommentsToDocComments() { - let input = """ - // Does something - // Note: not really - func doSomething() {} - """ - let output = """ - /// Does something - /// Note: not really - func doSomething() {} - """ - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testConvertURLCommentsToDocComments() { - let input = """ - // Does something - // http://example.com - func doSomething() {} - """ - let output = """ - /// Does something - /// http://example.com - func doSomething() {} - """ - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testMultilineDocCommentReplaced() { - let input = """ - // A class - // With some other details - class Foo {} - """ - let output = """ - /// A class - /// With some other details - class Foo {} - """ - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testCommentWithBlankLineNotReplaced() { - let input = """ - // A class - // With some other details - - class Foo {} - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testDocCommentsAssociatedTypeNotReplaced() { - let input = """ - /// An interesting comment about Foo. - associatedtype Foo - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testNonDocCommentsAssociatedTypeReplaced() { - let input = """ - // An interesting comment about Foo. - associatedtype Foo - """ - let output = """ - /// An interesting comment about Foo. - associatedtype Foo - """ - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testConditionalDeclarationCommentNotReplaced() { - let input = """ - if let foo = bar, - // baz - let baz = bar - {} - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testCommentInsideSwitchCaseNotReplaced() { - let input = """ - switch foo { - case .bar: - // bar - let bar = baz() - - default: - // baz - let baz = quux() - } - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testDocCommentInsideIfdef() { - let input = """ - #if DEBUG - // return 3 - func returnNumber() { 3 } - #endif - """ - let output = """ - #if DEBUG - /// return 3 - func returnNumber() { 3 } - #endif - """ - testFormatting(for: input, output, rule: FormatRules.docComments) - } - - func testDocCommentInsideIfdefElse() { - let input = """ - #if DEBUG - #elseif PROD - /// return 2 - func returnNumber() { 2 } - #else - /// return 3 - func returnNumber() { 3 } - #endif - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - func testDocCommentForMacro() { - let input = """ - /// Adds a static `logger` member to the type. - @attached(member, names: named(logger)) public macro StaticLogger( - subsystem: String? = nil, - category: String? = nil - ) = #externalMacro(module: "StaticLoggerMacros", type: "StaticLogger") - """ - testFormatting(for: input, rule: FormatRules.docComments) - } - - // MARK: - conditionalAssignment - - func testDoesntConvertIfStatementAssignmentSwift5_8() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testConvertsIfStatementAssignment() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let output = """ - let foo: Foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) - } - - func testConvertsSimpleSwitchStatementAssignment() { - let input = """ - let foo: Foo - switch condition { - case true: - foo = Foo("foo") - case false: - foo = Foo("bar") - } - """ - let output = """ - let foo: Foo = switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) - } - - func testConvertsTrivialSwitchStatementAssignment() { - let input = """ - let foo: Foo - switch enumWithOnceCase(let value) { - case singleCase: - foo = value - } - """ - let output = """ - let foo: Foo = switch enumWithOnceCase(let value) { - case singleCase: - value - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - func testConvertsNestedIfAndStatementAssignments() { - let input = """ - let foo: Foo - switch condition { - case true: - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - - case false: - switch condition { - case true: - foo = Foo("baaz") - - case false: - if condition { - foo = Foo("quux") - } else { - foo = Foo("quack") - } - } - } - """ - let output = """ - let foo: Foo = switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - switch condition { - case true: - Foo("baaz") - - case false: - if condition { - Foo("quux") - } else { - Foo("quack") - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) - } - - func testConvertsIfStatementAssignmentPreservingComment() { - let input = """ - let foo: Foo - // This is a comment between the property and condition - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let output = """ - let foo: Foo - // This is a comment between the property and condition - = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["indent", "redundantType", "wrapMultilineConditionalAssignment"]) - } - - func testDoesntConvertsIfStatementAssigningMultipleProperties() { - let input = """ - let foo: Foo - let bar: Bar - if condition { - foo = Foo("foo") - bar = Bar("foo") - } else { - foo = Foo("bar") - bar = Bar("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertsIfStatementAssigningDifferentProperties() { - let input = """ - var foo: Foo? - var bar: Bar? - if condition { - foo = Foo("foo") - } else { - bar = Bar("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertNonExhaustiveIfStatementAssignment1() { - let input = """ - var foo: Foo? - if condition { - foo = Foo("foo") - } else if someOtherCondition { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertNonExhaustiveIfStatementAssignment2() { - let input = """ - var foo: Foo? - if condition { - if condition { - foo = Foo("foo") - } - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment1() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - print("Multi-statement") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment2() { - let input = """ - let foo: Foo - switch condition { - case true: - foo = Foo("foo") - print("Multi-statement") - - case false: - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment3() { - let input = """ - let foo: Foo - if condition { - if condition { - foo = Foo("bar") - } else { - foo = Foo("baaz") - } - print("Multi-statement") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment4() { - let input = """ - let foo: Foo - switch condition { - case true: - if condition { - foo = Foo("bar") - } else { - foo = Foo("baaz") - } - print("Multi-statement") - - case false: - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithStringLiteral() { - let input = """ - let text: String - if conditionOne { - text = "Hello World!" - doSomeStuffHere() - } else { - text = "Goodbye!" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithCollectionLiteral() { - let input = """ - let text: [String] - if conditionOne { - text = [] - doSomeStuffHere() - } else { - text = ["Goodbye!"] - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithIntLiteral() { - let input = """ - let number: Int? - if conditionOne { - number = 5 - doSomeStuffHere() - } else { - number = 10 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithNilLiteral() { - let input = """ - let number: Int? - if conditionOne { - number = nil - doSomeStuffHere() - } else { - number = 10 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithOtherProperty() { - let input = """ - let number: Int? - if conditionOne { - number = someOtherProperty - doSomeStuffHere() - } else { - number = 10 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertConditionalCastInSwift5_9() { - // The following code doesn't compile in Swift 5.9 due to this issue: - // https://github.com/apple/swift/issues/68764 - // - // let result = if condition { - // foo as? String - // } else { - // "bar" - // } - // - let input = """ - let result1: String? - if condition { - result1 = foo as? String - } else { - result1 = "bar" - } - - let result2: String? - switch condition { - case true: - result2 = foo as? String - case false: - result2 = "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testAllowsAsWithinInnerScope() { - let input = """ - let result: String? - switch condition { - case true: - result = method(string: foo as? String) - case false: - result = "bar" - } - """ - - let output = """ - let result: String? = switch condition { - case true: - method(string: foo as? String) - case false: - "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - // TODO: update branches parser to handle this case properly - func testIgnoreSwitchWithConditionalCompilation() { - let input = """ - func foo() -> String? { - let result: String? - switch condition { - #if os(macOS) - case .foo: - result = method(string: foo as? String) - #endif - case .bar: - return nil - } - return result - } - """ - - let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - // TODO: update branches parser to handle this scenario properly - func testIgnoreSwitchWithConditionalCompilation2() { - let input = """ - func foo() -> String? { - let result: String? - switch condition { - case .foo: - result = method(string: foo as? String) - #if os(macOS) - case .bar: - return nil - #endif - } - return result - } - """ - - let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testConvertsConditionalCastInSwift5_10() { - let input = """ - let result1: String? - if condition { - result1 = foo as? String - } else { - result1 = "bar" - } - - let result2: String? - switch condition { - case true: - result2 = foo as? String - case false: - result2 = "bar" - } - """ - - let output = """ - let result1: String? = if condition { - foo as? String - } else { - "bar" - } - - let result2: String? = switch condition { - case true: - foo as? String - case false: - "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) - } - - func testConvertsSwitchWithDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - default: - foo = Foo("default") - } - """ - - let output = """ - let foo: Foo = switch condition { - case .foo: - Foo("foo") - case .bar: - Foo("bar") - default: - Foo("default") - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) - } - - func testConvertsSwitchWithUnknownDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - @unknown default: - foo = Foo("default") - } - """ - - let output = """ - let foo: Foo = switch condition { - case .foo: - Foo("foo") - case .bar: - Foo("bar") - @unknown default: - Foo("default") - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) - } - - func testPreservesSwitchWithReturnInDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - default: - return - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testPreservesSwitchWithReturnInUnknownDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - @unknown default: - return - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testDoesntConvertIfStatementWithForLoopInBranch() { - let input = """ - var foo: Foo? - if condition { - foo = Foo("foo") - for foo in foos { - print(foo) - } - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) - } - - func testConvertsIfStatementNotFollowingPropertyDefinition() { - let input = """ - if condition { - property = Foo("foo") - } else { - property = Foo("bar") - } - """ - - let output = """ - property = - if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testPreservesIfStatementNotFollowingPropertyDefinitionWithInvalidBranch() { - let input = """ - if condition { - property = Foo("foo") - } else { - property = Foo("bar") - print("A second expression on this branch") - } - - if condition { - property = Foo("foo") - } else { - if otherCondition { - property = Foo("foo") - } - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testPreservesNonExhaustiveIfStatementNotFollowingPropertyDefinition() { - let input = """ - if condition { - property = Foo("foo") - } - - if condition { - property = Foo("foo") - } else if otherCondition { - property = Foo("foo") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testConvertsSwitchStatementNotFollowingPropertyDefinition() { - let input = """ - switch condition { - case true: - property = Foo("foo") - case false: - property = Foo("bar") - } - """ - - let output = """ - property = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testConvertsSwitchStatementWithComplexLValueNotFollowingPropertyDefinition() { - let input = """ - switch condition { - case true: - property?.foo!.bar["baaz"] = Foo("foo") - case false: - property?.foo!.bar["baaz"] = Foo("bar") - } - """ - - let output = """ - property?.foo!.bar["baaz"] = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testDoesntMergePropertyWithUnrelatedCondition() { - let input = """ - let differentProperty: Foo - switch condition { - case true: - property = Foo("foo") - case false: - property = Foo("bar") - } - """ - - let output = """ - let differentProperty: Foo - property = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testConvertsNestedIfSwitchStatementNotFollowingPropertyDefinition() { - let input = """ - switch firstCondition { - case true: - if secondCondition { - property = Foo("foo") - } else { - property = Foo("bar") - } - - case false: - if thirdCondition { - property = Foo("baaz") - } else { - property = Foo("quux") - } - } - """ - - let output = """ - property = - switch firstCondition { - case true: - if secondCondition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - if thirdCondition { - Foo("baaz") - } else { - Foo("quux") - } - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testPreservesSwitchConditionWithIneligibleBranch() { - let input = """ - switch firstCondition { - case true: - // Even though this condition is eligible to be converted, - // we leave it as-is because it's nested in an ineligible condition. - if secondCondition { - property = Foo("foo") - } else { - property = Foo("bar") - } - - case false: - if thirdCondition { - property = Foo("baaz") - } else { - property = Foo("quux") - print("A second expression on this branch") - } - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - func testPreservesIfConditionWithIneligibleBranch() { - let input = """ - if firstCondition { - // Even though this condition is eligible to be converted, - // we leave it as-is because it's nested in an ineligible condition. - if secondCondition { - property = Foo("foo") - } else { - property = Foo("bar") - } - } else { - if thirdCondition { - property = Foo("baaz") - } else { - property = Foo("quux") - print("A second expression on this branch") - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) - } - - // MARK: - preferForLoop - - func testConvertSimpleForEachToForLoop() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { string in - print(string) - } - - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { (string: String) in - print(string) - } - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for string in placeholderStrings { - print(string) - } - - let placeholderStrings = ["foo", "bar", "baaz"] - for string in placeholderStrings { - print(string) - } - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testConvertAnonymousForEachToForLoop() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - print($0) - } - - potatoes.forEach({ $0.bake() }) - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for placeholderString in placeholderStrings { - print(placeholderString) - } - - potatoes.forEach({ $0.bake() }) - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop, exclude: ["trailingClosures"]) - } - - func testNoConvertAnonymousForEachToForLoop() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - print($0) - } - - potatoes.forEach({ $0.bake() }) - """ - - let options = FormatOptions(preserveAnonymousForEach: true, preserveSingleLineForEach: false) - testFormatting(for: input, rule: FormatRules.preferForLoop, options: options, exclude: ["trailingClosures"]) - } - - func testConvertSingleLineForEachToForLoop() { - let input = "potatoes.forEach({ item in item.bake() })" - let output = "for item in potatoes { item.bake() }" - - let options = FormatOptions(preserveSingleLineForEach: false) - testFormatting(for: input, output, rule: FormatRules.preferForLoop, options: options, - exclude: ["wrapLoopBodies"]) - } - - func testConvertSingleLineAnonymousForEachToForLoop() { - let input = "potatoes.forEach({ $0.bake() })" - let output = "for potato in potatoes { potato.bake() }" - - let options = FormatOptions(preserveSingleLineForEach: false) - testFormatting(for: input, output, rule: FormatRules.preferForLoop, options: options, - exclude: ["wrapLoopBodies"]) - } - - func testConvertNestedForEach() { - let input = """ - let nestedArrays = [[1, 2], [3, 4]] - nestedArrays.forEach { - $0.forEach { - $0.forEach { - print($0) - } - } - } - """ - - let output = """ - let nestedArrays = [[1, 2], [3, 4]] - for nestedArray in nestedArrays { - for item in nestedArray { - for item in item { - print(item) - } - } - } - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testDefaultNameAlreadyUsedInLoopBody() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - let placeholderString = $0.uppercased() - print(placeholderString, $0) - } - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for item in placeholderStrings { - let placeholderString = item.uppercased() - print(placeholderString, item) - } - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testIgnoreLoopsWithCaptureListForNow() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { [someCapturedValue = fooBar] in - print($0, someCapturedValue) - } - """ - testFormatting(for: input, rule: FormatRules.preferForLoop) - } - - func testRemoveAllPrefixFromLoopIdentifier() { - let input = """ - allWindows.forEach { - print($0) - } - """ - - let output = """ - for window in allWindows { - print(window) - } - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testConvertsReturnToContinue() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - func capitalize(_ value: String) -> String { - return value.uppercased() - } - - if $0 == "foo" { - return - } else { - print(capitalize($0)) - } - } - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for placeholderString in placeholderStrings { - func capitalize(_ value: String) -> String { - return value.uppercased() - } - - if placeholderString == "foo" { - continue - } else { - print(capitalize(placeholderString)) - } - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testHandlesForEachOnChainedProperties() { - let input = """ - let bar = foo.bar - bar.baaz.quux.strings.forEach { - print($0) - } - """ - - let output = """ - let bar = foo.bar - for string in bar.baaz.quux.strings { - print(string) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testHandlesForEachOnFunctionCallResult() { - let input = """ - let bar = foo.bar - foo.item().bar[2].baazValues(option: true).forEach { - print($0) - } - """ - - let output = """ - let bar = foo.bar - for baazValue in foo.item().bar[2].baazValues(option: true) { - print(baazValue) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testHandlesForEachOnSubscriptResult() { - let input = """ - let bar = foo.bar - foo.item().bar[2].dictionary["myValue"].forEach { - print($0) - } - """ - - let output = """ - let bar = foo.bar - for item in foo.item().bar[2].dictionary["myValue"] { - print(item) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testHandlesForEachOnArrayLiteral() { - let input = """ - let quux = foo.bar.baaz.quux - ["foo", "bar", "baaz", quux].forEach { - print($0) - } - """ - - let output = """ - let quux = foo.bar.baaz.quux - for item in ["foo", "bar", "baaz", quux] { - print(item) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testHandlesForEachOnCurriedFunctionWithSubscript() { - let input = """ - let quux = foo.bar.baaz.quux - foo(bar)(baaz)["item"].forEach { - print($0) - } - """ - - let output = """ - let quux = foo.bar.baaz.quux - for item in foo(bar)(baaz)["item"] { - print(item) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testHandlesForEachOnArrayLiteralInParens() { - let input = """ - let quux = foo.bar.baaz.quux - (["foo", "bar", "baaz", quux]).forEach { - print($0) - } - """ - - let output = """ - let quux = foo.bar.baaz.quux - for item in (["foo", "bar", "baaz", quux]) { - print(item) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop, exclude: ["redundantParens"]) - } - - func testPreservesForEachAfterMultilineChain() { - let input = """ - placeholderStrings - .filter { $0.style == .fooBar } - .map { $0.uppercased() } - .forEach { print($0) } - - placeholderStrings - .filter({ $0.style == .fooBar }) - .map({ $0.uppercased() }) - .forEach({ print($0) }) - """ - testFormatting(for: input, rule: FormatRules.preferForLoop, exclude: ["trailingClosures"]) - } - - func testPreservesChainWithClosure() { - let input = """ - // Converting this to a for loop would result in unusual looking syntax like - // `for string in strings.map { $0.uppercased() } { print($0) }` - // which causes a warning to be emitted: "trailing closure in this context is - // confusable with the body of the statement; pass as a parenthesized argument - // to silence this warning". - strings.map { $0.uppercased() }.forEach { print($0) } - """ - testFormatting(for: input, rule: FormatRules.preferForLoop) - } - - func testForLoopVariableNotUsedIfClashesWithKeyword() { - let input = """ - Foo.allCases.forEach { - print($0) - } - """ - let output = """ - for item in Foo.allCases { - print(item) - } - """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testTryNotRemovedInThrowingForEach() { - let input = """ - try list().forEach { - print($0) - } - """ - testFormatting(for: input, rule: FormatRules.preferForLoop) - } - - func testOptionalTryNotRemovedInThrowingForEach() { - let input = """ - try? list().forEach { - print($0) - } - """ - testFormatting(for: input, rule: FormatRules.preferForLoop) - } - - func testAwaitNotRemovedInAsyncForEach() { - let input = """ - await list().forEach { - print($0) - } - """ - testFormatting(for: input, rule: FormatRules.preferForLoop) - } - - func testForEachOverDictionary() { - let input = """ - let dict = ["a": "b"] - - dict.forEach { (header: (key: String, value: String)) in - print(header.key) - print(header.value) - } - """ - - let output = """ - let dict = ["a": "b"] - - for header in dict { - print(header.key) - print(header.value) - } - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop) - } - - func testConvertsForEachWithGuardElseReturn() { - let input = """ - strings.forEach { string in - guard !string.isEmpty else { return } - print(string) - } - """ - - let output = """ - for string in strings { - guard !string.isEmpty else { continue } - print(string) - } - """ - - testFormatting(for: input, output, rule: FormatRules.preferForLoop, - exclude: ["wrapConditionalBodies"]) - } - - // MARK: propertyType - - func testConvertsExplicitTypeToInferredType() { - let input = """ - let foo: Foo = .init() - let bar: Bar = .staticBar - let baaz: Baaz = .Example.default - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - """ - - let output = """ - let foo = Foo() - let bar = Bar.staticBar - let baaz = Baaz.Example.default - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) - } - - func testConvertsInferredTypeToExplicitType() { - let input = """ - let foo = Foo() - let bar = Bar.staticBar - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - """ - - let output = """ - let foo: Foo = .init() - let bar: Bar = .staticBar - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) - } - - func testConvertsTypeMembersToExplicitType() { - let input = """ - struct Foo { - let foo = Foo() - let bar = Bar.staticBar - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - } - """ - - let output = """ - struct Foo { - let foo: Foo = .init() - let bar: Bar = .staticBar - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) - } - - func testConvertsLocalsToImplicitType() { - let input = """ - struct Foo { - let foo = Foo() - - func bar() { - let bar: Bar = .staticBar - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - } - } - """ - - let output = """ - struct Foo { - let foo: Foo = .init() - - func bar() { - let bar = Bar.staticBar - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) - } - - func testPreservesInferredTypeFollowingTypeWithDots() { - let input = """ - let baaz = Baaz.Example.default - let color = Color.Theme.default - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesExplicitTypeIfNoRHS() { - let input = """ - let foo: Foo - let bar: Bar - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesImplicitTypeIfNoRHSType() { - let input = """ - let foo = foo() - let bar = bar - let int = 24 - let array = ["string"] - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesImplicitForVoidAndTuples() { - let input = """ - let foo = Void() - let foo = (foo: "foo", bar: "bar").foo - let foo = ["bar", "baz"].quux(quuz) - let foo = [bar].first - let foo = [bar, baaz].first - let foo = ["foo": "bar"].first - let foo = [foo: bar].first - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options, exclude: ["void"]) - } - - func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { - let input = """ - let foo: Foo = localFoo - let bar: Bar = localBar - let int: Int64 = 1234 - let number: CGFloat = 12.345 - let array: [String] = [] - let dictionary: [String: Int] = [:] - let tuple: (String, Int) = ("foo", 123) - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options, exclude: ["redundantType"]) - } - - func testCompatibleWithRedundantTypeInferred() { - let input = """ - let foo: Foo = Foo() - """ - - let output = """ - let foo = Foo() - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType], options: options) - } - - func testCompatibleWithRedundantTypeExplicit() { - let input = """ - let foo: Foo = Foo() - """ - - let output = """ - let foo: Foo = .init() - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType], options: options) - } - - func testCompatibleWithRedundantTypeInferLocalsOnly() { - let input = """ - let foo: Foo = Foo.init() - let foo: Foo = .init() - - func bar() { - let baaz: Baaz = Baaz.init() - let baaz: Baaz = .init() - } - """ - - let output = """ - let foo: Foo = .init() - let foo: Foo = .init() - - func bar() { - let baaz = Baaz() - let baaz = Baaz() - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType, FormatRules.redundantInit], options: options) - } - - func testPropertyTypeWithIfExpressionDisabledByDefault() { - let input = """ - let foo: SomeTypeWithALongGenrericName = - if condition { - .init(bar) - } else { - .init(baaz) - } - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPropertyTypeWithIfExpression() { - let input = """ - let foo: Foo = - if condition { - .init(bar) - } else { - .init(baaz) - } - """ - - let output = """ - let foo = - if condition { - Foo(bar) - } else { - Foo(baaz) - } - """ - - let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) - } - - func testPropertyTypeWithSwitchExpression() { - let input = """ - let foo: Foo = - switch condition { - case true: - .init(bar) - case false: - .init(baaz) - } - """ - - let output = """ - let foo = - switch condition { - case true: - Foo(bar) - case false: - Foo(baaz) - } - """ - - let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) - } - - func testPreservesNonMatchingIfExpression() { - let input = """ - let foo: Foo = - if condition { - .init(bar) - } else { - [] // e.g. using ExpressibleByArrayLiteral - } - """ - - let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesExplicitOptionalType() { - // `let foo = Foo?.foo` doesn't work if `.foo` is defined on `Foo` but not `Foo?` - let input = """ - let optionalFoo1: Foo? = .foo - let optionalFoo2: Foo? = Foo.foo - let optionalFoo3: Foo! = .foo - let optionalFoo4: Foo! = Foo.foo - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesTypeWithSeparateDeclarationAndProperty() { - let input = """ - var foo: Foo! - foo = Foo(afterDelay: { - print(foo) - }) - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesTypeWithExistentialAny() { - let input = """ - protocol ShapeStyle {} - struct MyShapeStyle: ShapeStyle {} - - extension ShapeStyle where Self == MyShapeStyle { - static var myShape: MyShapeStyle { MyShapeStyle() } - } - - /// This compiles - let myShape1: any ShapeStyle = .myShape - - // This would fail with "error: static member 'myShape' cannot be used on protocol metatype '(any ShapeStyle).Type'" - // let myShape2 = (any ShapeStyle).myShape - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesExplicitRightHandSideWithOperator() { - let input = """ - let value: ClosedRange = .zero ... 10 - let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge - let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() - let dynamicTypeSizeRange: ClosedRange = .convertFromLiteral(.large ... .xxxLarge) - """ - - let output = """ - let value: ClosedRange = .zero ... 10 - let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge - let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() - let dynamicTypeSizeRange = ClosedRange.convertFromLiteral(.large ... .xxxLarge) - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) - } - - func testPreservesInferredRightHandSideWithOperators() { - let input = """ - let foo = Foo().bar - let foo = Foo.bar.baaz.quux - let foo = Foo.bar ... baaz - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPreservesUserProvidedSymbolTypes() { - let input = """ - class Foo { - let foo = Foo() - let bar = Bar() - - func bar() { - let foo: Foo = .foo - let bar: Bar = .bar - let baaz: Baaz = .baaz - let quux: Quux = .quux - } - } - """ - - let output = """ - class Foo { - let foo = Foo() - let bar: Bar = .init() - - func bar() { - let foo: Foo = .foo - let bar = Bar.bar - let baaz: Baaz = .baaz - let quux: Quux = .quux - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["Foo", "Baaz", "quux"]) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) - } - - func testPreserveInitIfExplicitlyExcluded() { - let input = """ - class Foo { - let foo = Foo() - let bar = Bar.init() - let baaz = Baaz.baaz() - - func bar() { - let foo: Foo = .init() - let bar: Bar = .init() - let baaz: Baaz = .baaz() - } - } - """ - - let output = """ - class Foo { - let foo = Foo() - let bar = Bar.init() - let baaz: Baaz = .baaz() - - func bar() { - let foo: Foo = .init() - let bar: Bar = .init() - let baaz = Baaz.baaz() - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options, exclude: ["redundantInit"]) - } - - func testClosureBodyIsConsideredLocal() { - let input = """ - foo { - let bar = Bar() - let baaz: Baaz = .init() - } - - foo(bar: bar, baaz: baaz, quux: { - let bar = Bar() - let baaz: Baaz = .init() - }) - - foo { - let bar = Bar() - let baaz: Baaz = .init() - } bar: { - let bar = Bar() - let baaz: Baaz = .init() - } - - class Foo { - let foo = Foo.bar { - let baaz = Baaz() - let baaz: Baaz = .init() - } - } - """ - - let output = """ - foo { - let bar = Bar() - let baaz = Baaz() - } - - foo(bar: bar, baaz: baaz, quux: { - let bar = Bar() - let baaz = Baaz() - }) - - foo { - let bar = Bar() - let baaz = Baaz() - } bar: { - let bar = Bar() - let baaz = Baaz() - } - - class Foo { - let foo: Foo = .bar { - let baaz = Baaz() - let baaz = Baaz() - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) - } - - func testIfGuardConditionsPreserved() { - let input = """ - if let foo = Foo(bar) { - let foo = Foo(bar) - } else if let foo = Foo(bar) { - let foo = Foo(bar) - } else { - let foo = Foo(bar) - } - - guard let foo = Foo(bar) else { - return - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - func testPropertyObserversConsideredLocal() { - let input = """ - class Foo { - var foo: Foo { - get { - let foo = Foo(bar) - } - set { - let foo = Foo(bar) - } - willSet { - let foo = Foo(bar) - } - didSet { - let foo = Foo(bar) - } - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) - } - - // MARK: - docCommentsBeforeAttributes - - func testDocCommentsBeforeAttributes() { - let input = """ - @MainActor - /// Doc comment on this type declaration - public struct Baaz { - @available(*, deprecated) - /// Doc comment on this property declaration. - /// This comment spans multiple lines. - private var bar: Bar - - @FooBarMacro(arg1: true, arg2: .baaz) - /** - * Doc comment on this function declaration - */ - func foo() {} - } - """ - - let output = """ - /// Doc comment on this type declaration - @MainActor - public struct Baaz { - /// Doc comment on this property declaration. - /// This comment spans multiple lines. - @available(*, deprecated) - private var bar: Bar - - /** - * Doc comment on this function declaration - */ - @FooBarMacro(arg1: true, arg2: .baaz) - func foo() {} - } - """ - - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes) - } - - func testDocCommentsBeforeMultipleAttributes() { - let input = """ - @MainActor @Macro(argument: true) @available(*, deprecated) - /// Doc comment on this function declaration after several attributes - public func foo() {} - - @MainActor - @Macro(argument: true) - @available(*, deprecated) - /// Doc comment on this function declaration after several attributes - public func bar() {} - """ - - let output = """ - /// Doc comment on this function declaration after several attributes - @MainActor @Macro(argument: true) @available(*, deprecated) - public func foo() {} - - /// Doc comment on this function declaration after several attributes - @MainActor - @Macro(argument: true) - @available(*, deprecated) - public func bar() {} - """ - - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes) - } - - func testUpdatesCommentsAfterMark() { - let input = """ - import FooBarKit - - // MARK: - Foo - - @MainActor - /// Doc comment on this type declaration. - enum Foo { - - // MARK: Public - - @MainActor - /// Doc comment on this function declaration. - public func foo() {} - - // MARK: Private - - // TODO: This function also has a TODO comment. - @MainActor - /// Doc comment on this function declaration. - private func bar() {} - - } - """ - - let output = """ - import FooBarKit - - // MARK: - Foo - - /// Doc comment on this type declaration. - @MainActor - enum Foo { - - // MARK: Public - - /// Doc comment on this function declaration. - @MainActor - public func foo() {} - - // MARK: Private - - // TODO: This function also has a TODO comment. - /// Doc comment on this function declaration. - @MainActor - private func bar() {} - - } - """ - - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - func testPreservesCommentsBetweenAttributes() { - let input = """ - @MainActor - /// Doc comment between attributes - @available(*, deprecated) - /// Doc comment before declaration - func bar() {} - - @MainActor /// Doc comment after main actor attribute - @available(*, deprecated) /// Doc comment after deprecation attribute - /// Doc comment before declaration - func bar() {} - """ - - let output = """ - /// Doc comment before declaration - @MainActor - /// Doc comment between attributes - @available(*, deprecated) - func bar() {} - - /// Doc comment before declaration - @MainActor /// Doc comment after main actor attribute - @available(*, deprecated) /// Doc comment after deprecation attribute - func bar() {} - """ - - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) - } - - func testPreservesCommentOnSameLineAsAttribute() { - let input = """ - @MainActor /// Doc comment trailing attributes - func foo() {} - """ - - testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) - } - - func testPreservesRegularComments() { - let input = """ - @MainActor - // Comment after attribute - func foo() {} - """ - - testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) - } - - func testCombinesWithDocCommentsRule() { - let input = """ - @MainActor - // Comment after attribute - func foo() {} - """ - - let output = """ - /// Comment after attribute - @MainActor - func foo() {} - """ - - testFormatting(for: input, [output], rules: [FormatRules.docComments, FormatRules.docCommentsBeforeAttributes]) - } -} diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift deleted file mode 100644 index 52bfb0e2..00000000 --- a/Tests/RulesTests+Wrapping.swift +++ /dev/null @@ -1,5481 +0,0 @@ -// -// RulesTests+Wrapping.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class WrappingTests: RulesTests { - // MARK: - elseOnSameLine - - func testElseOnSameLine() { - let input = "if true {\n 1\n}\nelse { 2 }" - let output = "if true {\n 1\n} else { 2 }" - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testElseOnSameLineOnlyAppliedToDanglingBrace() { - let input = "if true { 1 }\nelse { 2 }" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testGuardNotAffectedByElseOnSameLine() { - let input = "guard true\nelse { return }" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testElseOnSameLineDoesntEatPreviousStatement() { - let input = "if true {}\nguard true else { return }" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testElseNotOnSameLineForAllman() { - let input = "if true\n{\n 1\n} else { 2 }" - let output = "if true\n{\n 1\n}\nelse { 2 }" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) - } - - func testElseOnNextLineOption() { - let input = "if true {\n 1\n} else { 2 }" - let output = "if true {\n 1\n}\nelse { 2 }" - let options = FormatOptions(elseOnNextLine: true) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) - } - - func testGuardNotAffectedByElseOnSameLineForAllman() { - let input = "guard true else { return }" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) - } - - func testRepeatWhileNotOnSameLineForAllman() { - let input = "repeat\n{\n foo\n} while x" - let output = "repeat\n{\n foo\n}\nwhile x" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, options: options) - } - - func testWhileNotAffectedByElseOnSameLineIfNotRepeatWhile() { - let input = "func foo(x) {}\n\nwhile true {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine) - } - - func testCommentsNotDiscardedByElseOnSameLineRule() { - let input = "if true {\n 1\n}\n\n// comment\nelse {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine) - } - - func testElseOnSameLineInferenceEdgeCase() { - let input = """ - func foo() { - if let foo == bar { - // ... - } else { - // ... - } - - if let foo == bar, - let baz = quux - { - print() - } - - if let foo == bar, - let baz = quux - { - print() - } - - if let foo == bar, - let baz = quux - { - print() - } - - if let foo == bar, - let baz = quux - { - print() - } - } - """ - let options = FormatOptions(elseOnNextLine: false) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, options: options, - exclude: ["braces"]) - } - - // guardelse = auto - - func testSingleLineGuardElseNotWrappedByDefault() { - let input = "guard foo = bar else {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testSingleLineGuardElseNotUnwrappedByDefault() { - let input = "guard foo = bar\nelse {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testSingleLineGuardElseWrappedByDefaultIfBracesOnNextLine() { - let input = "guard foo = bar else\n{}" - let output = "guard foo = bar\nelse {}" - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - exclude: ["wrapConditionalBodies"]) - } - - func testMultilineGuardElseNotWrappedByDefault() { - let input = """ - guard let foo = bar, - bar > 5 else { - return - } - """ - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - exclude: ["wrapMultilineStatementBraces"]) - } - - func testMultilineGuardElseWrappedByDefaultIfBracesOnNextLine() { - let input = """ - guard let foo = bar, - bar > 5 else - { - return - } - """ - let output = """ - guard let foo = bar, - bar > 5 - else { - return - } - """ - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine) - } - - func testWrappedMultilineGuardElseCorrectlyIndented() { - let input = """ - func foo() { - guard let foo = bar, - bar > 5 else - { - return - } - } - """ - let output = """ - func foo() { - guard let foo = bar, - bar > 5 - else { - return - } - } - """ - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine) - } - - // guardelse = nextLine - - func testSingleLineGuardElseNotWrapped() { - let input = "guard foo = bar else {}" - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) - } - - func testSingleLineGuardElseNotUnwrapped() { - let input = "guard foo = bar\nelse {}" - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) - } - - func testSingleLineGuardElseWrappedIfBracesOnNextLine() { - let input = "guard foo = bar else\n{}" - let output = "guard foo = bar\nelse {}" - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) - } - - func testMultilineGuardElseWrapped() { - let input = """ - guard let foo = bar, - bar > 5 else { - return - } - """ - let output = """ - guard let foo = bar, - bar > 5 - else { - return - } - """ - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testMultilineGuardElseEndingInParen() { - let input = """ - guard let foo = bar, - let baz = quux() else - { - return - } - """ - let output = """ - guard let foo = bar, - let baz = quux() - else { - return - } - """ - let options = FormatOptions(guardElsePosition: .auto) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options) - } - - // guardelse = sameLine - - func testMultilineGuardElseUnwrapped() { - let input = """ - guard let foo = bar, - bar > 5 - else { - return - } - """ - let output = """ - guard let foo = bar, - bar > 5 else { - return - } - """ - let options = FormatOptions(guardElsePosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testGuardElseUnwrappedIfBracesOnNextLine() { - let input = "guard foo = bar\nelse {}" - let output = "guard foo = bar else {}" - let options = FormatOptions(guardElsePosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, - options: options) - } - - func testPreserveBlankLineBeforeElse() { - let input = """ - if foo { - print("foo") - } - - else if bar { - print("bar") - } - - else { - print("baaz") - } - """ - - testFormatting(for: input, rule: FormatRules.elseOnSameLine) - } - - func testPreserveBlankLineBeforeElseOnSameLine() { - let input = """ - if foo { - print("foo") - } - - else if bar { - print("bar") - } - - else { - print("baaz") - } - """ - - let options = FormatOptions(elseOnNextLine: false) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, options: options) - } - - func testPreserveBlankLineBeforeElseWithComments() { - let input = """ - if foo { - print("foo") - } - // Comment before else if - else if bar { - print("bar") - } - - // Comment before else - else { - print("baaz") - } - """ - - testFormatting(for: input, rule: FormatRules.elseOnSameLine) - } - - func testPreserveBlankLineBeforeElseDoesntAffectOtherCases() { - let input = """ - if foo { - print("foo") - } - else { - print("bar") - } - - guard foo else { - return - } - - guard - let foo, - let bar, - lat baaz else - { - return - } - """ - - let output = """ - if foo { - print("foo") - } else { - print("bar") - } - - guard foo else { - return - } - - guard - let foo, - let bar, - lat baaz - else { - return - } - """ - - let options = FormatOptions(elseOnNextLine: false, guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, options: options) - } - - // MARK: - wrapConditionalBodies - - func testGuardReturnWraps() { - let input = "guard let foo = bar else { return }" - let output = """ - guard let foo = bar else { - return - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testEmptyGuardReturnWithSpaceDoesNothing() { - let input = "guard let foo = bar else { }" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, - exclude: ["emptyBraces"]) - } - - func testEmptyGuardReturnWithoutSpaceDoesNothing() { - let input = "guard let foo = bar else {}" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, - exclude: ["emptyBraces"]) - } - - func testGuardReturnWithValueWraps() { - let input = "guard let foo = bar else { return baz }" - let output = """ - guard let foo = bar else { - return baz - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardBodyWithClosingBraceAlreadyOnNewlineWraps() { - let input = """ - guard foo else { return - } - """ - let output = """ - guard foo else { - return - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardContinueWithNoSpacesToCleanupWraps() { - let input = "guard let foo = bar else {continue}" - let output = """ - guard let foo = bar else { - continue - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardReturnWrapsSemicolonDelimitedStatements() { - let input = "guard let foo = bar else { var baz = 0; let boo = 1; fatalError() }" - let output = """ - guard let foo = bar else { - var baz = 0; let boo = 1; fatalError() - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardReturnWrapsSemicolonDelimitedStatementsWithNoSpaces() { - let input = "guard let foo = bar else {var baz=0;let boo=1;fatalError()}" - let output = """ - guard let foo = bar else { - var baz=0;let boo=1;fatalError() - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies, - exclude: ["spaceAroundOperators"]) - } - - func testGuardReturnOnNewlineUnchanged() { - let input = """ - guard let foo = bar else { - return - } - """ - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardCommentSameLineUnchanged() { - let input = """ - guard let foo = bar else { // Test comment - return - } - """ - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardMultilineCommentSameLineUnchanged() { - let input = "guard let foo = bar else { /* Test comment */ return }" - let output = """ - guard let foo = bar else { /* Test comment */ - return - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardTwoMultilineCommentsSameLine() { - let input = "guard let foo = bar else { /* Test comment 1 */ return /* Test comment 2 */ }" - let output = """ - guard let foo = bar else { /* Test comment 1 */ - return /* Test comment 2 */ - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testNestedGuardElseIfStatementsPutOnNewline() { - let input = "guard let foo = bar else { if qux { return quux } else { return quuz } }" - let output = """ - guard let foo = bar else { - if qux { - return quux - } else { - return quuz - } - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testNestedGuardElseGuardStatementPutOnNewline() { - let input = "guard let foo = bar else { guard qux else { return quux } }" - let output = """ - guard let foo = bar else { - guard qux else { - return quux - } - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testGuardWithClosureOnlyWrapsElseBody() { - let input = "guard foo { $0.bar } else { return true }" - let output = """ - guard foo { $0.bar } else { - return true - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testIfElseReturnsWrap() { - let input = "if foo { return bar } else if baz { return qux } else { return quux }" - let output = """ - if foo { - return bar - } else if baz { - return qux - } else { - return quux - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testIfElseBodiesWrap() { - let input = "if foo { bar } else if baz { qux } else { quux }" - let output = """ - if foo { - bar - } else if baz { - qux - } else { - quux - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testIfElsesWithClosuresDontWrapClosures() { - let input = "if foo { $0.bar } { baz } else if qux { $0.quux } { quuz } else { corge }" - let output = """ - if foo { $0.bar } { - baz - } else if qux { $0.quux } { - quuz - } else { - corge - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - func testEmptyIfElseBodiesWithSpaceDoNothing() { - let input = "if foo { } else if baz { } else { }" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, - exclude: ["emptyBraces"]) - } - - func testEmptyIfElseBodiesWithoutSpaceDoNothing() { - let input = "if foo {} else if baz {} else {}" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, - exclude: ["emptyBraces"]) - } - - func testGuardElseBraceStartingOnDifferentLine() { - let input = """ - guard foo else - { return bar } - """ - let output = """ - guard foo else - { - return bar - } - """ - - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies, - exclude: ["braces", "indent", "elseOnSameLine"]) - } - - func testIfElseBracesStartingOnDifferentLines() { - let input = """ - if foo - { return bar } - else if baz - { return qux } - else - { return quux } - """ - let output = """ - if foo - { - return bar - } - else if baz - { - return qux - } - else - { - return quux - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies, - exclude: ["braces", "indent", "elseOnSameLine"]) - } - - func testInsideStringLiteralDoesNothing() { - let input = """ - "\\(list.map { if $0 % 2 == 0 { return 0 } else { return 1 } })" - """ - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies) - } - - func testInsideMultilineStringLiteral() { - let input = """ - let foo = \""" - \\(list.map { if $0 % 2 == 0 { return 0 } else { return 1 } }) - \""" - """ - let output = """ - let foo = \""" - \\(list.map { if $0 % 2 == 0 { - return 0 - } else { - return 1 - } }) - \""" - """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) - } - - // MARK: - wrapLoopBodies - - func testWrapForLoop() { - let input = "for foo in bar { print(foo) }" - let output = """ - for foo in bar { - print(foo) - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapLoopBodies) - } - - func testWrapWhileLoop() { - let input = "while let foo = bar.next() { print(foo) }" - let output = """ - while let foo = bar.next() { - print(foo) - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapLoopBodies) - } - - func testWrapRepeatWhileLoop() { - let input = "repeat { print(foo) } while condition()" - let output = """ - repeat { - print(foo) - } while condition() - """ - testFormatting(for: input, output, rule: FormatRules.wrapLoopBodies) - } - - // MARK: - wrap - - func testWrapIfStatement() { - let input = """ - if let foo = foo, let bar = bar, let baz = baz {} - """ - let output = """ - if let foo = foo, - let bar = bar, - let baz = baz {} - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapIfElseStatement() { - let input = """ - if let foo = foo {} else if let bar = bar {} - """ - let output = """ - if let foo = foo {} - else if let bar = - bar {} - """ - let output2 = """ - if let foo = foo {} - else if let bar = - bar {} - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) - } - - func testWrapGuardStatement() { - let input = """ - guard let foo = foo, let bar = bar else { - break - } - """ - let output = """ - guard let foo = foo, - let bar = bar - else { - break - } - """ - let output2 = """ - guard let foo = foo, - let bar = bar - else { - break - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapClosure() { - let input = """ - let foo = { () -> Bool in true } - """ - let output = """ - let foo = - { () -> Bool in - true } - """ - let output2 = """ - let foo = - { () -> Bool in - true - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) - } - - func testWrapClosure2() { - let input = """ - let foo = { bar, _ in bar } - """ - let output = """ - let foo = - { bar, _ in - bar } - """ - let output2 = """ - let foo = - { bar, _ in - bar - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) - } - - func testWrapClosureWithAllmanBraces() { - let input = """ - let foo = { bar, _ in bar } - """ - let output = """ - let foo = - { bar, _ in - bar } - """ - let output2 = """ - let foo = - { bar, _ in - bar - } - """ - let options = FormatOptions(allmanBraces: true, maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) - } - - func testWrapClosure3() { - let input = "let foo = bar { $0.baz }" - let output = """ - let foo = bar { - $0.baz } - """ - let output2 = """ - let foo = bar { - $0.baz - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth() { - let input = """ - func testFunc() -> ReturnType { - doSomething() - doSomething() - } - """ - let output = """ - func testFunc() - -> ReturnType { - doSomething() - doSomething() - } - """ - let options = FormatOptions(maxWidth: 25) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() { - let input = """ - func testFunc() -> ReturnType { - doSomething() - doSomething() - } - """ - let output = """ - func testFunc() - -> ReturnType { - doSomething() - doSomething() - } - """ - let output2 = """ - func testFunc() - -> ReturnType { - doSomething() - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 25) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth2() { - let input = """ - func testFunc() -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output = """ - func testFunc() - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() { - let input = """ - func testFunc() throws -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output = """ - func testFunc() throws - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output2 = """ - func testFunc() throws - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() { - let input = """ - func testFunc() throws(Foo) -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output = """ - func testFunc() throws(Foo) - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output2 = """ - func testFunc() throws(Foo) - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth3() { - let input = """ - func testFunc() -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc() - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() { - let input = """ - func testFunc() -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc() - -> (Bool, String) -> String? { - doSomething() - } - """ - let output2 = """ - func testFunc() - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth4() { - let input = """ - func testFunc(_: () -> Void) -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() { - let input = """ - func testFunc(_: () -> Void) -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) - -> (Bool, String) -> String? { - doSomething() - } - """ - let output2 = """ - func testFunc(_: () -> Void) - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapChainedFunctionAfterSubscriptCollection() { - let input = """ - let foo = bar["baz"].quuz() - """ - let output = """ - let foo = bar["baz"] - .quuz() - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapChainedFunctionInSubscriptCollection() { - let input = """ - let foo = bar[baz.quuz()] - """ - let output = """ - let foo = - bar[baz.quuz()] - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapThrowingFunctionIfReturnTypeExceedsMaxWidth() { - let input = """ - func testFunc(_: () -> Void) throws -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) throws - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() { - let input = """ - func testFunc(_: () -> Void) throws(Foo) -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) throws(Foo) - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) - } - - func testNoWrapInterpolatedStringLiteral() { - let input = """ - "a very long \\(string) literal" - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testNoWrapAtUnspacedOperator() { - let input = "let foo = bar+baz+quux" - let output = "let foo =\n bar+baz+quux" - let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, - exclude: ["spaceAroundOperators"]) - } - - func testNoWrapAtUnspacedEquals() { - let input = "let foo=bar+baz+quux" - let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, rule: FormatRules.wrap, options: options, - exclude: ["spaceAroundOperators"]) - } - - func testNoWrapSingleParameter() { - let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" - let output = """ - let fooBar = try unkeyedContainer - .decode(FooBar.self) - """ - let options = FormatOptions(maxWidth: 50) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapSingleParameter() { - let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" - let output = """ - let fooBar = try unkeyedContainer.decode( - FooBar.self - ) - """ - let options = FormatOptions(maxWidth: 50, noWrapOperators: [".", "="]) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapFunctionArrow() { - let input = "func foo() -> Int {}" - let output = """ - func foo() - -> Int {} - """ - let options = FormatOptions(maxWidth: 14) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testNoWrapFunctionArrow() { - let input = "func foo() -> Int {}" - let output = """ - func foo( - ) -> Int {} - """ - let options = FormatOptions(maxWidth: 14, noWrapOperators: ["->"]) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testNoCrashWrap() { - let input = """ - struct Foo { - func bar(a: Set, c: D) {} - } - """ - let output = """ - struct Foo { - func bar( - a: Set< - B - >, - c: D - ) {} - } - """ - let options = FormatOptions(maxWidth: 10) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, - exclude: ["unusedArguments"]) - } - - func testNoCrashWrap2() { - let input = """ - struct Test { - func webView(_: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - authenticationChallengeProcessor.process(challenge: challenge, completionHandler: completionHandler) - } - } - """ - let output = """ - struct Test { - func webView( - _: WKWebView, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping (URLSession.AuthChallengeDisposition, - URLCredential?) -> Void - ) { - authenticationChallengeProcessor.process( - challenge: challenge, - completionHandler: completionHandler - ) - } - } - """ - let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, - exclude: ["indent", "wrapArguments"]) - } - - func testNoCrashWrap3() throws { - let input = """ - override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { - let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext - context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size - return context - } - """ - let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 100) - let rules = [FormatRules.wrap, FormatRules.wrapArguments] - XCTAssertNoThrow(try format(input, rules: rules, options: options)) - } - - func testWrapColorLiteral() throws { - let input = """ - button.setTitleColor(#colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1), for: .normal) - """ - let options = FormatOptions(maxWidth: 80, assetLiteralWidth: .visualWidth) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testWrapImageLiteral() { - let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" - let options = FormatOptions(maxWidth: 40, assetLiteralWidth: .visualWidth) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testNoWrapBeforeFirstArgumentInSingleLineStringInterpolation() { - let input = """ - "a very long string literal with \\(interpolation) inside" - """ - let options = FormatOptions(maxWidth: 40) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testWrapBeforeFirstArgumentInMultineStringInterpolation() { - let input = """ - \""" - a very long string literal with \\(interpolation) inside - \""" - """ - let output = """ - \""" - a very long string literal with \\( - interpolation - ) inside - \""" - """ - let options = FormatOptions(maxWidth: 40) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - // ternary expressions - - func testWrapSimpleTernaryOperator() { - let input = """ - let foo = fooCondition ? longValueThatContainsFoo : longValueThatContainsBar - """ - - let output = """ - let foo = fooCondition - ? longValueThatContainsFoo - : longValueThatContainsBar - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testRewrapsSimpleTernaryOperator() { - let input = """ - let foo = fooCondition ? longValueThatContainsFoo : - longValueThatContainsBar - """ - - let output = """ - let foo = fooCondition - ? longValueThatContainsFoo - : longValueThatContainsBar - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapComplexTernaryOperator() { - let input = """ - let foo = fooCondition ? Foo(property: value) : barContainer.getBar(using: barProvider) - """ - - let output = """ - let foo = fooCondition - ? Foo(property: value) - : barContainer.getBar(using: barProvider) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testRewrapsComplexTernaryOperator() { - let input = """ - let foo = fooCondition ? Foo(property: value) : - barContainer.getBar(using: barProvider) - """ - - let output = """ - let foo = fooCondition - ? Foo(property: value) - : barContainer.getBar(using: barProvider) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrappedTernaryOperatorIndentsChainedCalls() { - let input = """ - let ternary = condition - ? values - .map { $0.bar } - .filter { $0.hasFoo } - .last - : other.values - .compactMap { $0 } - .first? - .with(property: updatedValue) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, rule: FormatRules.indent, options: options) - } - - func testWrapsSimpleNestedTernaryOperator() { - let input = """ - let foo = fooCondition ? (barCondition ? a : b) : (baazCondition ? c : d) - """ - - let output = """ - let foo = fooCondition - ? (barCondition ? a : b) - : (baazCondition ? c : d) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapsDoubleNestedTernaryOperation() { - let input = """ - let foo = fooCondition ? barCondition ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult - """ - - let output = """ - let foo = fooCondition - ? barCondition - ? longTrueBarResult - : longFalseBarResult - : baazCondition - ? longTrueBaazResult - : longFalseBaazResult - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testWrapsTripleNestedTernaryOperation() { - let input = """ - let foo = fooCondition ? barCondition ? quuxCondition ? longTrueQuuxResult : longFalseQuuxResult : barCondition2 ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult - """ - - let output = """ - let foo = fooCondition - ? barCondition - ? quuxCondition - ? longTrueQuuxResult - : longFalseQuuxResult - : barCondition2 - ? longTrueBarResult - : longFalseBarResult - : baazCondition - ? longTrueBaazResult - : longFalseBaazResult - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testNoWrapTernaryWrappedWithinChildExpression() { - let input = """ - func foo() { - return _skipString(string) ? .token( - string, Location(source: input, range: startIndex ..< index) - ) : nil - } - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testNoWrapTernaryWrappedWithinChildExpression2() { - let input = """ - let types: [PolygonType] = plane.isEqual(to: plane) ? [] : vertices.map { - let t = plane.normal.dot($0.position) - plane.w - let type: PolygonType = (t < -epsilon) ? .back : (t > epsilon) ? .front : .coplanar - polygonType = PolygonType(rawValue: polygonType.rawValue | type.rawValue)! - return type - } - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testNoWrapTernaryInsideStringLiteral() { - let input = """ - "\\(true ? "Some string literal" : "Some other string")" - """ - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) - testFormatting(for: input, rule: FormatRules.wrap, options: options) - } - - func testWrapTernaryInsideMultilineStringLiteral() { - let input = """ - let foo = \""" - \\(true ? "Some string literal" : "Some other string")" - \""" - """ - let output = """ - let foo = \""" - \\(true - ? "Some string literal" - : "Some other string")" - \""" - """ - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) - } - - func testErrorNotReportedOnBlankLineAfterWrap() throws { - let input = """ - [ - abagdiasiudbaisndoanosdasdasdasdasdnaosnooanso(), - - bar(), - ] - """ - let options = FormatOptions(truncateBlankLines: false, maxWidth: 40) - let changes = try lint(input, rules: [FormatRules.wrap, FormatRules.indent], options: options) - XCTAssertEqual(changes, [.init(line: 2, rule: FormatRules.wrap, filePath: nil)]) - } - - // MARK: - wrapArguments - - func testIndentFirstElementWhenApplyingWrap() { - let input = """ - let foo = Set([ - Thing(), - Thing(), - ]) - """ - let output = """ - let foo = Set([ - Thing(), - Thing(), - ]) - """ - testFormatting(for: input, output, rule: FormatRules.wrapArguments, exclude: ["propertyType"]) - } - - func testWrapArgumentsDoesntIndentTrailingComment() { - let input = """ - foo( // foo - bar: Int - ) - """ - let output = """ - foo( // foo - bar: Int - ) - """ - testFormatting(for: input, output, rule: FormatRules.wrapArguments) - } - - func testWrapArgumentsDoesntIndentClosingBracket() { - let input = """ - [ - "foo": [ - ], - ] - """ - testFormatting(for: input, rule: FormatRules.wrapArguments) - } - - func testWrapParametersDoesNotAffectFunctionDeclaration() { - let input = "foo(\n bar _: Int,\n baz _: String\n)" - let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersClosureAfterParameterListDoesNotWrapClosureArguments() { - let input = """ - func foo() {} - bar = (baz: 5, quux: 7, - quuz: 10) - """ - let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersNotSetWrapArgumentsAfterFirstDefaultsToAfterFirst() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersNotSetWrapArgumentsBeforeFirstDefaultsToBeforeFirst() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersNotSetWrapArgumentsPreserveDefaultsToPreserve() { - let input = "func foo(\n bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnSameLine() { - let input = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnNextLine() { - let input = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnSameLineAndForce() { - let input = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnNextLineAndForce() { - let input = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersFunctionCallClosingParenOnNextLineAndForce() { - let input = """ - foo( - bar: 42, - baz: "foo" - ) - """ - let output = """ - foo( - bar: 42, - baz: "foo") - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testIndentMultilineStringWhenWrappingArguments() { - let input = """ - foobar(foo: \"\"" - baz - \"\"", - bar: \"\"" - baz - \"\"") - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testHandleXcodeTokenApplyingWrap() { - let input = """ - test(image: \u{003c}#T##UIImage#>, name: "Name") - """ - - let output = """ - test( - image: \u{003c}#T##UIImage#>, - name: "Name" - ) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testIssue1530() { - let input = """ - extension DRAutoWeatherReadRequestResponse { - static let mock = DRAutoWeatherReadRequestResponse( - offlineFirstWeather: DRAutoWeatherReadRequestResponse.DROfflineFirstWeather( - daily: .mockWeatherID, hourly: [] - ) - ) - } - """ - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) - } - - // MARK: wrapParameters - - // MARK: preserve - - func testAfterFirstPreserved() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testAfterFirstPreservedIndentFixed() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testAfterFirstPreservedNewlineRemoved() { - let input = "func foo(bar _: Int,\n baz _: String\n) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testBeforeFirstPreserved() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testBeforeFirstPreservedIndentFixed() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testBeforeFirstPreservedNewlineAdded() { - let input = "func foo(\n bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersAfterMultilineComment() { - let input = """ - /** - Some function comment. - */ - func barFunc( - _ firstParam: FirstParamType, - secondParam: SecondParamType - ) - """ - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: afterFirst - - func testBeforeFirstConvertedToAfterFirst() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapInnerArguments() { - let input = "func foo(\n bar _: Int,\n baz _: foo(bar, baz)\n) {}" - let output = "func foo(bar _: Int,\n baz _: foo(bar, baz)) {}" - let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: afterFirst, maxWidth - - func testWrapAfterFirstIfMaxLengthExceeded() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let output = """ - func foo(bar: Int, - baz: String) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) - } - - func testWrapAfterFirstIfMaxLengthExceeded2() { - let input = """ - func foo(bar: Int, baz: String, quux: Bool) -> Bool {} - """ - let output = """ - func foo(bar: Int, - baz: String, - quux: Bool) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) - } - - func testWrapAfterFirstIfMaxLengthExceeded3() { - let input = """ - func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} - """ - let output = """ - func foo(bar: Int, baz: String, - aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) - } - - func testWrapAfterFirstIfMaxLengthExceeded3WithWrap() { - let input = """ - func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} - """ - let output = """ - func foo(bar: Int, baz: String, - aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) - -> Bool {} - """ - let output2 = """ - func foo(bar: Int, baz: String, - aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) - -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) - testFormatting(for: input, [output, output2], - rules: [FormatRules.wrapArguments, FormatRules.wrap], - options: options, exclude: ["unusedArguments"]) - } - - func testWrapAfterFirstIfMaxLengthExceeded4WithWrap() { - let input = """ - func foo(bar: String, baz: String, quux: Bool) -> Bool {} - """ - let output = """ - func foo(bar: String, - baz: String, - quux: Bool) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], - options: options, exclude: ["unusedArguments"]) - } - - func testWrapAfterFirstIfMaxLengthExceededInClassScopeWithWrap() { - let input = """ - class TestClass { - func foo(bar: String, baz: String, quux: Bool) -> Bool {} - } - """ - let output = """ - class TestClass { - func foo(bar: String, - baz: String, - quux: Bool) - -> Bool {} - } - """ - let output2 = """ - class TestClass { - func foo(bar: String, - baz: String, - quux: Bool) - -> Bool {} - } - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) - testFormatting(for: input, [output, output2], - rules: [FormatRules.wrapArguments, FormatRules.wrap], - options: options, exclude: ["unusedArguments"]) - } - - func testWrapParametersListInClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: (Int, - Int, - String) -> Int = { _, _, _ in - 0 - } - """ - let output2 = """ - var mathFunction: (Int, - Int, - String) - -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 30) - testFormatting(for: input, [output, output2], - rules: [FormatRules.wrapArguments], - options: options) - } - - func testWrapParametersAfterFirstIfMaxLengthExceededInReturnType() { - let input = """ - func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} - """ - let output2 = """ - func foo(bar: Int, baz: String, - quux: Bool) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 50) - testFormatting(for: input, [input, output2], rules: [FormatRules.wrapArguments], - options: options, exclude: ["unusedArguments"]) - } - - func testWrapParametersAfterFirstWithSeparatedArgumentLabels() { - let input = """ - func foo(with - bar: Int, and - baz: String, and - quux: Bool - ) -> LongReturnType {} - """ - let output = """ - func foo(with bar: Int, - and baz: String, - and quux: Bool) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, - options: options, exclude: ["unusedArguments"]) - } - - // MARK: beforeFirst - - func testWrapAfterFirstConvertedToWrapBefore() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testLinebreakInsertedAtEndOfWrappedFunction() { - let input = "func foo(\n bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testAfterFirstConvertedToBeforeFirst() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapParametersListBeforeFirstInClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInThrowingClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) throws -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) throws -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInTypedThrowingClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) throws(Foo) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) throws(Foo) -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInRethrowingClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) rethrows -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) rethrows -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options, - exclude: ["unusedArguments"]) - } - - func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParams() { - let input = """ - func foo(bar: Int, baz: (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: Int, baz: ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options, - exclude: ["unusedArguments"]) - } - - func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParamsAfterWrappedClosure() { - let input = """ - func foo(bar: Int, baz: (Int, - Bool, String) -> Int, quux: String) -> Int {} - """ - let output = """ - func foo(bar: Int, baz: ( - Int, - Bool, - String - ) -> Int, quux: String) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options, - exclude: ["unusedArguments"]) - } - - func testWrapParametersListBeforeFirstInEscapingClosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: @escaping (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: @escaping ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options, - exclude: ["unusedArguments"]) - } - - func testWrapParametersListBeforeFirstInNoEscapeClosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: @noescape (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: @noescape ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options, - exclude: ["unusedArguments"]) - } - - func testWrapParametersListBeforeFirstInEscapingAutoclosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: @escaping @autoclosure (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: @escaping @autoclosure ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], - options: options, - exclude: ["unusedArguments"]) - } - - // MARK: beforeFirst, maxWidth - - func testWrapBeforeFirstIfMaxLengthExceeded() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let output = """ - func foo( - bar: Int, - baz: String - ) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments"]) - } - - func testNoWrapBeforeFirstIfMaxLengthNotExceeded() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 42) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments"]) - } - - func testNoWrapGenericsIfClosingBracketWithinMaxWidth() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let output = """ - func foo( - bar: Int, - baz: String - ) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments"]) - } - - func testWrapAlreadyWrappedArgumentsIfMaxLengthExceeded() { - let input = """ - func foo( - bar: Int, baz: String, quux: Bool - ) -> Bool {} - """ - let output = """ - func foo( - bar: Int, baz: String, - quux: Bool - ) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 26) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments"]) - } - - func testWrapParametersBeforeFirstIfMaxLengthExceededInReturnType() { - let input = """ - func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} - """ - let output2 = """ - func foo( - bar: Int, - baz: String, - quux: Bool - ) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 50) - testFormatting(for: input, [input, output2], rules: [FormatRules.wrapArguments], - options: options, exclude: ["unusedArguments"]) - } - - func testWrapParametersBeforeFirstWithSeparatedArgumentLabels() { - let input = """ - func foo(with - bar: Int, and - baz: String - ) -> LongReturnType {} - """ - let output = """ - func foo( - with bar: Int, - and baz: String - ) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, - options: options, exclude: ["unusedArguments"]) - } - - func testWrapParametersListBeforeFirstInClosureTypeWithMaxWidth() { - let input = """ - var mathFunction: (Int, Int, String) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments], - options: options) - } - - func testNoWrapBeforeFirstMaxWidthNotExceededWithLineBreakSinceLastEndOfArgumentScope() { - let input = """ - class Foo { - func foo() { - bar() - } - - func bar(foo: String, bar: Int) { - quux() - } - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 37) - testFormatting(for: input, rule: FormatRules.wrapArguments, - options: options, exclude: ["unusedArguments"]) - } - - func testNoWrapSubscriptWithSingleElement() { - let input = "guard let foo = bar[0] {}" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 20) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["wrap"]) - } - - func testNoWrapArrayWithSingleElement() { - let input = "let foo = [0]" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 11) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["wrap"]) - } - - func testNoWrapDictionaryWithSingleElement() { - let input = "let foo = [bar: baz]" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 15) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["wrap"]) - } - - func testNoWrapImageLiteral() { - let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["wrap"]) - } - - func testNoWrapColorLiteral() { - let input = """ - if let color = #colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1) {} - """ - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["wrap"]) - } - - func testWrapArgumentsNoIndentBlankLines() { - let input = """ - let foo = [ - - bar, - - ] - """ - let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["wrap", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) - } - - // MARK: closingParenPosition = true - - func testParenOnSameLineWhenWrapAfterFirstConvertedToWrapBefore() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testParenOnSameLineWhenWrapBeforeFirstUnchanged() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(\n bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testParenOnSameLineWhenWrapBeforeFirstPreserved() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(\n bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: indent with tabs - - func testTabIndentWrappedFunctionWithSmartTabs() { - let input = """ - func foo(bar: Int, - baz: Int) {} - """ - let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments"]) - } - - func testTabIndentWrappedFunctionWithoutSmartTabs() { - let input = """ - func foo(bar: Int, - baz: Int) {} - """ - let output = """ - func foo(bar: Int, - \t\t\t\t baz: Int) {} - """ - let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, - tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments"]) - } - - // MARK: - wrapArguments --wrapArguments - - func testWrapArgumentsDoesNotAffectFunctionDeclaration() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapArgumentsDoesNotAffectInit() { - let input = "init(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapArgumentsDoesNotAffectSubscript() { - let input = "subscript(\n bar _: Int,\n baz _: String\n) -> Int {}" - let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: afterFirst - - func testWrapArgumentsConvertBeforeFirstToAfterFirst() { - let input = """ - foo( - bar _: Int, - baz _: String - ) - """ - let output = """ - foo(bar _: Int, - baz _: String) - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testCorrectWrapIndentForNestedArguments() { - let input = "foo(\nbar: (\nx: 0,\ny: 0\n),\nbaz: (\nx: 0,\ny: 0\n)\n)" - let output = "foo(bar: (x: 0,\n y: 0),\n baz: (x: 0,\n y: 0))" - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testNoRemoveLinebreakAfterCommentInArguments() { - let input = "a(b // comment\n)" - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoRemoveLinebreakAfterCommentInArguments2() { - let input = """ - foo(bar: bar - // , - // baz: baz - ) {} - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["indent"]) - } - - func testConsecutiveCodeCommentsNotIndented() { - let input = """ - foo(bar: bar, - // bar, - // baz, - quux) - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: afterFirst maxWidth - - func testWrapArgumentsAfterFirst() { - let input = """ - foo(bar: Int, baz: String, quux: Bool) - """ - let output = """ - foo(bar: Int, - baz: String, - quux: Bool) - """ - let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) - } - - // MARK: beforeFirst - - func testClosureInsideParensNotWrappedOntoNextLine() { - let input = "foo({\n bar()\n})" - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["trailingClosures"]) - } - - func testNoMangleCommentedLinesWhenWrappingArguments() { - let input = """ - foo(bar: bar - // , - // baz: baz - ) {} - """ - let output = """ - foo( - bar: bar - // , - // baz: baz - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testNoMangleCommentedLinesWhenWrappingArgumentsWithNoCommas() { - let input = """ - foo(bar: bar - // baz: baz - ) {} - """ - let output = """ - foo( - bar: bar - // baz: baz - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: preserve - - func testWrapArgumentsDoesNotAffectLessThanOperator() { - let input = """ - func foo() { - guard foo < bar.count else { return nil } - } - """ - let options = FormatOptions(wrapArguments: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, - options: options, exclude: ["wrapConditionalBodies"]) - } - - // MARK: - --wrapArguments, --wrapParameter - - // MARK: beforeFirst - - func testNoMistakeTernaryExpressionForArguments() { - let input = """ - (foo ? - bar : - baz) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["redundantParens"]) - } - - // MARK: beforeFirst, maxWidth : string interpolation - - func testNoWrapBeforeFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(interpolation) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 40) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapBeforeFirstArgumentInStringInterpolation2() { - let input = """ - "a very long string literal with \\(interpolation) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 50) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapBeforeFirstArgumentInStringInterpolation3() { - let input = """ - "a very long string literal with \\(interpolated, variables) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 40) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapBeforeNestedFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(foo(interpolated)) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 45) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapBeforeNestedFirstArgumentInStringInterpolation2() { - let input = """ - "a very long string literal with \\(foo(interpolated, variables)) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 45) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapProtocolFuncParametersBeforeFirst() { - let input = """ - protocol Foo { - public func stringify(_ value: T, label: String) -> (T, String) - } - """ - let output = """ - protocol Foo { - public func stringify( - _ value: T, - label: String - ) -> (T, String) - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, - options: options) - } - - // MARK: afterFirst maxWidth : string interpolation - - func testNoWrapAfterFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(interpolated) inside" - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapParameters: .afterFirst, - maxWidth: 46) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapAfterFirstArgumentInStringInterpolation2() { - let input = """ - "a very long string literal with \\(interpolated, variables) inside" - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapParameters: .afterFirst, - maxWidth: 50) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testNoWrapAfterNestedFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(foo(interpolated, variables)) inside" - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapParameters: .afterFirst, - maxWidth: 55) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // macros - - func testWrapMacroParametersBeforeFirst() { - let input = """ - @freestanding(expression) - public macro stringify(_ value: T, label: String) -> (T, String) - """ - let output = """ - @freestanding(expression) - public macro stringify( - _ value: T, - label: String - ) -> (T, String) - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, - options: options) - } - - // MARK: - wrapArguments --wrapCollections - - // MARK: beforeFirst - - func testNoDoubleSpaceAddedToWrappedArray() { - let input = "[ foo,\n bar ]" - let output = "[\n foo,\n bar\n]" - let options = FormatOptions(trailingCommas: false, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.spaceInsideBrackets], - options: options) - } - - func testTrailingCommasAddedToWrappedArray() { - let input = "[foo,\n bar]" - let output = "[\n foo,\n bar,\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - func testTrailingCommasAddedToWrappedNestedDictionary() { - let input = "[foo: [bar: baz,\n bar2: baz2]]" - let output = "[foo: [\n bar: baz,\n bar2: baz2,\n]]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - func testTrailingCommasAddedToSingleLineNestedDictionary() { - let input = "[\n foo: [bar: baz, bar2: baz2]]" - let output = "[\n foo: [bar: baz, bar2: baz2],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - func testTrailingCommasAddedToWrappedNestedDictionaries() { - let input = "[foo: [bar: baz,\n bar2: baz2],\n foo2: [bar: baz,\n bar2: baz2]]" - let output = "[\n foo: [\n bar: baz,\n bar2: baz2,\n ],\n foo2: [\n bar: baz,\n bar2: baz2,\n ],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - func testSpaceAroundEnumValuesInArray() { - let input = "[\n .foo,\n .bar, .baz,\n]" - let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: beforeFirst maxWidth - - func testWrapCollectionOnOneLineBeforeFirstWidthExceededInChainedFunctionCallAfterCollection() { - let input = """ - let foo = ["bar", "baz"].quux(quuz) - """ - let output2 = """ - let foo = ["bar", "baz"] - .quux(quuz) - """ - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 26) - testFormatting(for: input, [input, output2], - rules: [FormatRules.wrapArguments], options: options) - } - - // MARK: afterFirst - - func testTrailingCommaRemovedInWrappedArray() { - let input = "[\n .foo,\n .bar,\n .baz,\n]" - let output = "[.foo,\n .bar,\n .baz]" - let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testNoRemoveLinebreakAfterCommentInElements() { - let input = "[a, // comment\n]" - let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapCollectionsConsecutiveCodeCommentsNotIndented() { - let input = """ - let a = [foo, - // bar, - // baz, - quux] - """ - let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapCollectionsConsecutiveCodeCommentsNotIndentedInWrapBeforeFirst() { - let input = """ - let a = [ - foo, - // bar, - // baz, - quux, - ] - """ - let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: preserve - - func testNoBeforeFirstPreservedAndTrailingCommaIgnoredInMultilineNestedDictionary() { - let input = "[foo: [bar: baz,\n bar2: baz2]]" - let output = "[foo: [bar: baz,\n bar2: baz2]]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionary() { - let input = "[\n foo: [bar: baz, bar2: baz2]]" - let output = "[\n foo: [bar: baz, bar2: baz2],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionaryWithOneNestedItem() { - let input = "[\n foo: [bar: baz]]" - let output = "[\n foo: [bar: baz],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], - options: options) - } - - // MARK: - wrapArguments --wrapCollections & --wrapArguments - - // MARK: beforeFirst maxWidth - - func testWrapArgumentsBeforeFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { - let input = """ - foo(bar: ["baz", "quux"], quuz: corge) - """ - let output = """ - foo( - bar: ["baz", "quux"], - quuz: corge - ) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapCollections: .beforeFirst, - maxWidth: 26) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], options: options) - } - - // MARK: afterFirst maxWidth - - func testWrapArgumentsAfterFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { - let input = """ - foo(bar: ["baz", "quux"], quuz: corge) - """ - let output = """ - foo(bar: ["baz", "quux"], - quuz: corge) - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapCollections: .beforeFirst, - maxWidth: 26) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], options: options) - } - - // MARK: - wrapArguments Multiple Wraps On Same Line - - func testWrapAfterFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { - let input = """ - foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) - """ - let output = """ - foo.bar(baz: [qux, quux]) - .quuz([corge: grault], - garply: waldo) - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapCollections: .afterFirst, - maxWidth: 28) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], options: options) - } - - func testWrapAfterFirstWrapCollectionsBeforeFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { - let input = """ - foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) - """ - let output = """ - foo.bar(baz: [qux, quux]) - .quuz([corge: grault], - garply: waldo) - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapCollections: .beforeFirst, - maxWidth: 28) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], options: options) - } - - func testNoMangleNestedFunctionCalls() { - let input = """ - points.append(.curve( - quadraticBezier(p0.position.x, Double(p1.x), Double(p2.x), t), - quadraticBezier(p0.position.y, Double(p1.y), Double(p2.y), t) - )) - """ - let output = """ - points.append(.curve( - quadraticBezier( - p0.position.x, - Double(p1.x), - Double(p2.x), - t - ), - quadraticBezier( - p0.position.y, - Double(p1.y), - Double(p2.y), - t - ) - )) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 40) - testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], options: options) - } - - func testWrapArguments_typealias_beforeFirst() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 40) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_multipleTypealiases_beforeFirst() { - let input = """ - enum Namespace { - typealias DependenciesA = FooProviding & BarProviding - typealias DependenciesB = BaazProviding & QuuxProviding - } - """ - - let output = """ - enum Namespace { - typealias DependenciesA - = FooProviding - & BarProviding - typealias DependenciesB - = BaazProviding - & QuuxProviding - } - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 45) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_afterFirst() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 40) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_multipleTypealiases_afterFirst() { - let input = """ - enum Namespace { - typealias DependenciesA = FooProviding & BarProviding - typealias DependenciesB = BaazProviding & QuuxProviding - } - """ - - let output = """ - enum Namespace { - typealias DependenciesA = FooProviding - & BarProviding - typealias DependenciesB = BaazProviding - & QuuxProviding - } - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 45) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & BaazProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 100) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & - BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently2() { - let input = """ - enum Namespace { - typealias Dependencies = FooProviding & BarProviding - & BaazProviding & QuuxProviding - } - """ - - let output = """ - enum Namespace { - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - } - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently3() { - let input = """ - typealias Dependencies - = FooProviding & BarProviding & - BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently4() { - let input = """ - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistentlyWithComment() { - let input = """ - typealias Dependencies = FooProviding & BarProviding // trailing comment 1 - // Inline Comment 1 - & BaazProviding & QuuxProviding // trailing comment 2 - """ - - let output = """ - typealias Dependencies - = FooProviding - & BarProviding // trailing comment 1 - // Inline Comment 1 - & BaazProviding - & QuuxProviding // trailing comment 2 - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_singleTypePreserved() { - let input = """ - typealias Dependencies = FooProviding - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 10) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["wrap"]) - } - - func testWrapArguments_typealias_preservesCommentsBetweenTypes() { - let input = """ - typealias Dependencies - // We use `FooProviding` because `FooFeature` depends on `Foo` - = FooProviding - // We use `BarProviding` because `BarFeature` depends on `Bar` - & BarProviding - // We use `BaazProviding` because `BaazFeature` depends on `Baaz` - & BaazProviding - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_preservesCommentsAfterTypes() { - let input = """ - typealias Dependencies - = FooProviding // We use `FooProviding` because `FooFeature` depends on `Foo` - & BarProviding // We use `BarProviding` because `BarFeature` depends on `Bar` - & BaazProviding // We use `BaazProviding` because `BaazFeature` depends on `Baaz` - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - func testWrapArguments_typealias_withAssociatedType() { - let input = """ - typealias Collections = Collection & Collection & Collection & Collection - """ - - let output = """ - typealias Collections - = Collection - & Collection - & Collection - & Collection - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 50) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) - } - - // MARK: - -return wrap-if-multiline - - func testWrapReturnOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapReturnAndEffectOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) async -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) - async -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testDoesntWrapReturnAndEffectOnSingleLineFunctionDeclaration() { - let input = """ - func singleLineFunction() async throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testDoesntWrapReturnAndTypedEffectOnSingleLineFunctionDeclaration() { - let input = """ - func singleLineFunction() async throws(Foo) -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapEffectOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) async throws - -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) - async throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testUnwrapEffectOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) - async throws -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) async throws - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .never - ) - - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapArgumentsDoesntBreakFunctionDeclaration_issue_1776() { - let input = """ - struct OpenAPIController: RouteCollection { - let info = InfoObject(title: "Swagger {{cookiecutter.service_name}} - OpenAPI", - description: "{{cookiecutter.description}}", - contact: .init(email: "{{cookiecutter.email}}"), - version: Version(0, 0, 1)) - func boot(routes: RoutesBuilder) throws { - routes.get("swagger", "swagger.json") { - $0.application.routes.openAPI(info: info) - } - .excludeFromOpenAPI() - } - } - """ - - let options = FormatOptions(wrapEffects: .never) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) - } - - func testWrapEffectsNeverPreservesComments() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) - // Comment here between the parameters and effects - async throws -> String {} - """ - - let options = FormatOptions(closingParenPosition: .sameLine, wrapEffects: .never) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testWrapReturnOnMultilineFunctionDeclarationWithAfterFirst() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) -> String {} - """ - - let output = """ - func multilineFunction(foo _: String, - bar _: String) - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting( - for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["indent"] - ) - } - - func testWrapReturnOnMultilineThrowingFunctionDeclarationWithAfterFirst() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) throws -> String {} - """ - - let output = """ - func multilineFunction(foo _: String, - bar _: String) throws - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting( - for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["indent"] - ) - } - - func testWrapReturnAndEffectOnMultilineThrowingFunctionDeclarationWithAfterFirst() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) throws -> String {} - """ - - let output = """ - func multilineFunction(foo _: String, - bar _: String) - throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting( - for: input, output, rule: FormatRules.wrapArguments, options: options, - exclude: ["indent"] - ) - } - - func testDoesntWrapReturnOnMultilineThrowingFunction() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) - throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting( - for: input, rule: FormatRules.wrapArguments, options: options, - exclude: ["indent"] - ) - } - - func testDoesntWrapReturnOnSingleLineFunctionDeclaration() { - let input = """ - func multilineFunction(foo _: String, bar _: String) -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineArray() { - let input = """ - final class Foo { - private static let array = [ - "one", - ] - - private func singleLine() -> String {} - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineMethodCall() { - let input = """ - public final class Foo { - public var multiLineMethodCall = Foo.multiLineMethodCall( - bar: bar, - baz: baz) - - func singleLine() -> String { - return "method body" - } - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) - } - - func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) -> String - {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) - } - - // MARK: wrapMultilineStatementBraces - - func testMultilineIfBraceOnNextLine() { - let input = """ - if firstConditional, - array.contains(where: { secondConditional }) { - print("statement body") - } - """ - let output = """ - if firstConditional, - array.contains(where: { secondConditional }) - { - print("statement body") - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineFuncBraceOnNextLine() { - let input = """ - func method( - foo: Int, - bar: Int) { - print("function body") - } - """ - let output = """ - func method( - foo: Int, - bar: Int) - { - print("function body") - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, - exclude: ["wrapArguments", "unusedArguments"]) - } - - func testMultilineInitBraceOnNextLine() { - let input = """ - init(foo: Int, - bar: Int) { - print("function body") - } - """ - let output = """ - init(foo: Int, - bar: Int) - { - print("function body") - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, - exclude: ["wrapArguments", "unusedArguments"]) - } - - func testMultilineForLoopBraceOnNextLine() { - let input = """ - for foo in - [1, 2] { - print(foo) - } - """ - let output = """ - for foo in - [1, 2] - { - print(foo) - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineForLoopBraceOnNextLine2() { - let input = """ - for foo in [ - 1, - 2, - ] { - print(foo) - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineForWhereLoopBraceOnNextLine() { - let input = """ - for foo in bar - where foo != baz { - print(foo) - } - """ - let output = """ - for foo in bar - where foo != baz - { - print(foo) - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineGuardBraceOnNextLine() { - let input = """ - guard firstConditional, - array.contains(where: { secondConditional }) else { - print("statement body") - } - """ - let output = """ - guard firstConditional, - array.contains(where: { secondConditional }) else - { - print("statement body") - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, - exclude: ["braces", "elseOnSameLine"]) - } - - func testInnerMultilineIfBraceOnNextLine() { - let input = """ - if outerConditional { - if firstConditional, - array.contains(where: { secondConditional }) { - print("statement body") - } - } - """ - let output = """ - if outerConditional { - if firstConditional, - array.contains(where: { secondConditional }) - { - print("statement body") - } - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineIfBraceOnSameLine() { - let input = """ - if let object = Object([ - foo, - bar, - ]) { - print("statement body") - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, exclude: ["propertyType"]) - } - - func testSingleLineIfBraceOnSameLine() { - let input = """ - if firstConditional { - print("statement body") - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testSingleLineGuardBrace() { - let input = """ - guard firstConditional else { - print("statement body") - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testGuardElseOnOwnLineBraceNotWrapped() { - let input = """ - guard let foo = bar, - bar == baz - else { - print("statement body") - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineGuardClosingBraceOnSameLine() { - let input = """ - guard let foo = bar, - let baz = quux else { return } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, - exclude: ["wrapConditionalBodies"]) - } - - func testMultilineGuardBraceOnSameLineAsElse() { - let input = """ - guard let foo = bar, - let baz = quux - else { - return - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineClassBrace() { - let input = """ - class Foo: BarProtocol, - BazProtocol - { - init() {} - } - """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) - } - - func testMultilineClassBraceNotAppliedForXcodeIndentationMode() { - let input = """ - class Foo: BarProtocol, - BazProtocol { - init() {} - } - """ - let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, options: options) - } - - func testMultilineBraceAppliedToTrailingClosure_wrapBeforeFirst() { - let input = """ - UIView.animate( - duration: 10, - options: []) { - print() - } - """ - - let output = """ - UIView.animate( - duration: 10, - options: []) - { - print() - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, - options: options, exclude: ["indent"]) - } - - func testMultilineBraceAppliedToTrailingClosure2_wrapBeforeFirst() { - let input = """ - moveGradient( - to: defaultPosition, - isTouchDown: false, - animated: animated) { - self.isTouchDown = false - } - """ - - let output = """ - moveGradient( - to: defaultPosition, - isTouchDown: false, - animated: animated) - { - self.isTouchDown = false - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.indent, FormatRules.braces, - ], options: options) - } - - func testMultilineBraceAppliedToGetterBody_wrapBeforeFirst() { - let input = """ - var items = Adaptive.adaptive( - compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) { - didSet { updateAccessoryViewSpacing() } - } - """ - - let output = """ - var items = Adaptive.adaptive( - compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) - { - didSet { updateAccessoryViewSpacing() } - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.indent, - ], options: options, exclude: ["propertyType"]) - } - - func testMultilineBraceAppliedToTrailingClosure_wrapAfterFirst() { - let input = """ - UIView.animate(duration: 10, - options: []) { - print() - } - """ - - let output = """ - UIView.animate(duration: 10, - options: []) - { - print() - } - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, - options: options, exclude: ["indent"]) - } - - func testMultilineBraceAppliedToGetterBody_wrapAfterFirst() { - let input = """ - var items = Adaptive.adaptive(compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) - { - didSet { updateAccessoryViewSpacing() } - } - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options, exclude: ["propertyType"]) - } - - func testMultilineBraceAppliedToSubscriptBody() { - let input = """ - public subscript( - key: Foo) - -> ServerDrivenLayoutContentPresenter? - { - get { foo[key] } - set { foo[key] = newValue } - } - """ - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, - options: options, exclude: ["trailingClosures"]) - } - - func testWrapsMultilineStatementConsistently() { - let input = """ - func aFunc( - one _: Int, - two _: Int) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int) - -> String - { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithEffects() { - let input = """ - func aFunc( - one _: Int, - two _: Int) async throws -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int) - async throws -> String - { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithArrayReturnType() { - let input = """ - public func aFunc( - one _: Int, - two _: Int) -> [String] { - ["one"] - } - """ - - let output = """ - public func aFunc( - one _: Int, - two _: Int) - -> [String] - { - ["one"] - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithComplexGenericReturnType() { - let input = """ - public func aFunc( - one _: Int, - two _: Int) throws -> some Collection { - ["one"] - } - """ - - let output = """ - public func aFunc( - one _: Int, - two _: Int) - throws -> some Collection - { - ["one"] - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithTuple() { - let input = """ - public func aFunc( - one: Int, - two: Int) -> (one: String, two: String) { - (one: String(one), two: String(two)) - } - """ - - let output = """ - public func aFunc( - one: Int, - two: Int) - -> (one: String, two: String) - { - (one: String(one), two: String(two)) - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently2() { - let input = """ - func aFunc( - one _: Int, - two _: Int) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int - ) -> String { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .balanced - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently2_withEffects() { - let input = """ - func aFunc( - one _: Int, - two _: Int) async throws -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int - ) async throws -> String { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .balanced, - wrapEffects: .never - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently2_withTypedEffects() { - let input = """ - func aFunc( - one _: Int, - two _: Int) async throws(Foo) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int - ) async throws(Foo) -> String { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .balanced, - wrapEffects: .never - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently3() { - let input = """ - func aFunc( - one _: Int, - two _: Int - ) -> String { - "one" - } - """ - - let options = FormatOptions( - // wrapMultilineStatementBraces: true, - wrapArguments: .beforeFirst, - closingParenPosition: .balanced - ) - - testFormatting(for: input, [], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently4() { - let input = """ - func aFunc( - one _: Int, - two _: Int - ) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int) -> String - { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, - ], options: options) - } - - func testWrapMultilineStatementConsistently5() { - let input = """ - foo( - one: 1, - two: 2).bar({ _ in - "one" - }) - """ - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, - options: options, exclude: ["trailingClosures"]) - } - - func testOpenBraceAfterEqualsInGuardNotWrapped() { - let input = """ - guard - let foo = foo, - let bar: String = { - nil - }() - else { return } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rules: [FormatRules.wrapMultilineStatementBraces, FormatRules.wrap], - options: options, exclude: ["indent", "redundantClosure", "wrapConditionalBodies"]) - } - - func testWrapMultilineStatementBraceAfterWhereClauseWithTuple() { - let input = """ - extension Foo { - public func testWithWhereClause( - a: A, - b: B) - -> Outcome where - Outcome == (A, B) - { - return (a, b) - } - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rules: [FormatRules.wrapMultilineStatementBraces, FormatRules.braces], options: options) - } - - // MARK: wrapConditions before-first - - func testWrapConditionsBeforeFirstPreservesMultilineStatements() { - let input = """ - if - let unwrappedFoo = Foo( - bar: bar, - baz: baz), - unwrappedFoo.elements - .compactMap({ $0 }) - .filter({ - if $0.matchesCondition { - return true - } else { - return false - } - }).isEmpty, - let bar = unwrappedFoo.bar, - let baz = unwrappedFoo.bar? - .first(where: { $0.isBaz }), - let unwrappedFoo2 = Foo( - bar: bar2, - baz: baz2), - let quux = baz.quux - {} - """ - testFormatting( - for: input, rules: [FormatRules.wrapArguments, FormatRules.indent], - options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), - exclude: ["propertyType"] - ) - } - - func testWrapConditionsBeforeFirst() { - let input = """ - if let foo = foo, - let bar = bar, - foo == bar {} - - else if foo != bar, - let quux = quux {} - - if let baz = baz {} - - guard baz.filter({ $0 == foo }), - let bar = bar else {} - - while let foo = foo, - let bar = bar {} - """ - let output = """ - if - let foo = foo, - let bar = bar, - foo == bar {} - - else if - foo != bar, - let quux = quux {} - - if let baz = baz {} - - guard - baz.filter({ $0 == foo }), - let bar = bar else {} - - while - let foo = foo, - let bar = bar {} - """ - testFormatting( - for: input, output, rule: FormatRules.wrapArguments, - options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: ["wrapConditionalBodies"] - ) - } - - func testWrapConditionsBeforeFirstWhereShouldPreserveExisting() { - let input = """ - else {} - - else - {} - - if foo == bar - {} - - guard let foo = bar else - {} - - guard let foo = bar - else {} - """ - testFormatting( - for: input, rule: FormatRules.wrapArguments, - options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: ["elseOnSameLine", "wrapConditionalBodies"] - ) - } - - func testWrapConditionsAfterFirst() { - let input = """ - if - let foo = foo, - let bar = bar, - foo == bar {} - - else if - foo != bar, - let quux = quux {} - - else {} - - if let baz = baz {} - - guard - baz.filter({ $0 == foo }), - let bar = bar else {} - - while - let foo = foo, - let bar = bar {} - """ - let output = """ - if let foo = foo, - let bar = bar, - foo == bar {} - - else if foo != bar, - let quux = quux {} - - else {} - - if let baz = baz {} - - guard baz.filter({ $0 == foo }), - let bar = bar else {} - - while let foo = foo, - let bar = bar {} - """ - testFormatting( - for: input, output, rule: FormatRules.wrapArguments, - options: FormatOptions(indent: " ", wrapConditions: .afterFirst), - exclude: ["wrapConditionalBodies"] - ) - } - - func testWrapConditionsAfterFirstWhenFirstLineIsComment() { - let input = """ - guard - // Apply this rule to any function-like declaration - ["func", "init", "subscript"].contains(keyword.string), - // Opaque generic parameter syntax is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - // Validate that this is a generic method using angle bracket syntax, - // and find the indices for all of the key tokens - let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), - let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), - let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), - let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - genericSignatureStartIndex < paramListStartIndex, - genericSignatureEndIndex < paramListStartIndex, - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), - let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) - else { return } - """ - let output = """ - guard // Apply this rule to any function-like declaration - ["func", "init", "subscript"].contains(keyword.string), - // Opaque generic parameter syntax is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - // Validate that this is a generic method using angle bracket syntax, - // and find the indices for all of the key tokens - let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), - let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), - let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), - let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - genericSignatureStartIndex < paramListStartIndex, - genericSignatureEndIndex < paramListStartIndex, - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), - let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) - else { return } - """ - testFormatting( - for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.indent], - options: FormatOptions(wrapConditions: .afterFirst), - exclude: ["wrapConditionalBodies"] - ) - } - - // MARK: - wrapAttributes - - func testPreserveWrappedFuncAttributeByDefault() { - let input = """ - @objc - func foo() {} - """ - testFormatting(for: input, rule: FormatRules.wrapAttributes) - } - - func testPreserveUnwrappedFuncAttributeByDefault() { - let input = """ - @objc func foo() {} - """ - testFormatting(for: input, rule: FormatRules.wrapAttributes) - } - - func testWrapFuncAttribute() { - let input = """ - @available(iOS 14.0, *) func foo() {} - """ - let output = """ - @available(iOS 14.0, *) - func foo() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapInitAttribute() { - let input = """ - @available(iOS 14.0, *) init() {} - """ - let output = """ - @available(iOS 14.0, *) - init() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testMultipleAttributesNotSeparated() { - let input = """ - @objc @IBAction func foo {} - """ - let output = """ - @objc @IBAction - func foo {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, - options: options, exclude: ["redundantObjc"]) - } - - func testFuncAttributeStaysWrapped() { - let input = """ - @available(iOS 14.0, *) - func foo() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testUnwrapFuncAttribute() { - let input = """ - @available(iOS 14.0, *) - func foo() {} - """ - let output = """ - @available(iOS 14.0, *) func foo() {} - """ - let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testUnwrapFuncAttribute2() { - let input = """ - class MyClass: NSObject { - @objc - func myFunction() { - print("Testing") - } - } - """ - let output = """ - class MyClass: NSObject { - @objc func myFunction() { - print("Testing") - } - } - """ - let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testFuncAttributeStaysUnwrapped() { - let input = """ - @objc func foo() {} - """ - let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testVarAttributeIsNotWrapped() { - let input = """ - @IBOutlet var foo: UIView? - - @available(iOS 14.0, *) - func foo() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapTypeAttribute() { - let input = """ - @available(iOS 14.0, *) class Foo {} - """ - let output = """ - @available(iOS 14.0, *) - class Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting( - for: input, - output, - rule: FormatRules.wrapAttributes, - options: options - ) - } - - func testWrapExtensionAttribute() { - let input = """ - @available(iOS 14.0, *) extension Foo {} - """ - let output = """ - @available(iOS 14.0, *) - extension Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting( - for: input, - output, - rule: FormatRules.wrapAttributes, - options: options - ) - } - - func testTypeAttributeStaysWrapped() { - let input = """ - @available(iOS 14.0, *) - struct Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testUnwrapTypeAttribute() { - let input = """ - @available(iOS 14.0, *) - enum Foo {} - """ - let output = """ - @available(iOS 14.0, *) enum Foo {} - """ - let options = FormatOptions(typeAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testTypeAttributeStaysUnwrapped() { - let input = """ - @objc class Foo {} - """ - let options = FormatOptions(typeAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testTestableImportIsNotWrapped() { - let input = """ - @testable import Framework - - @available(iOS 14.0, *) - class Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testModifiersDontAffectAttributeWrapping() { - let input = """ - @objc override public func foo {} - """ - let output = """ - @objc - override public func foo {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testClassFuncAttributeTreatedAsFunction() { - let input = """ - @objc class func foo {} - """ - let output = """ - @objc - class func foo {} - """ - let options = FormatOptions(funcAttributes: .prevLine, fragment: true) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testClassFuncAttributeNotTreatedAsType() { - let input = """ - @objc class func foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine, fragment: true) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testClassAttributeNotMistakenForClassLet() { - let input = """ - @objc final class MyClass: NSObject {} - let myClass = MyClass() - """ - let output = """ - @objc - final class MyClass: NSObject {} - let myClass = MyClass() - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testClassImportAttributeNotTreatedAsType() { - let input = """ - @testable import class Framework.Foo - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapPrivateSetComputedVarAttributes() { - let input = """ - @objc private(set) dynamic var foo = Foo() - """ - let output = """ - @objc - private(set) dynamic var foo = Foo() - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testWrapPrivateSetVarAttributes() { - let input = """ - @objc private(set) dynamic var foo = Foo() - """ - let output = """ - @objc - private(set) dynamic var foo = Foo() - """ - let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testDontWrapPrivateSetVarAttributes() { - let input = """ - @objc - private(set) dynamic var foo = Foo() - """ - let output = """ - @objc private(set) dynamic var foo = Foo() - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testWrapConvenienceInitAttribute() { - let input = """ - @objc public convenience init() {} - """ - let output = """ - @objc - public convenience init() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapPropertyWrapperAttributeVarAttributes() { - let input = """ - @OuterType.Wrapper var foo: Int - """ - let output = """ - @OuterType.Wrapper - var foo: Int - """ - let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapPropertyWrapperAttribute() { - let input = """ - @OuterType.Wrapper var foo: Int - """ - let output = """ - @OuterType.Wrapper - var foo: Int - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testDontWrapPropertyWrapperAttribute() { - let input = """ - @OuterType.Wrapper - var foo: Int - """ - let output = """ - @OuterType.Wrapper var foo: Int - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapGenericPropertyWrapperAttribute() { - let input = """ - @OuterType.Generic var foo: WrappedType - """ - let output = """ - @OuterType.Generic - var foo: WrappedType - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapGenericPropertyWrapperAttribute2() { - let input = """ - @OuterType.Generic.Foo var foo: WrappedType - """ - let output = """ - @OuterType.Generic.Foo - var foo: WrappedType - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testAttributeOnComputedProperty() { - let input = """ - extension SectionContainer: ContentProviding where Section: ContentProviding { - @_disfavoredOverload - public var content: Section.Content { - section.content - } - } - """ - - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapAvailableAttributeUnderMaxWidth() { - let input = """ - @available(*, unavailable, message: "This property is deprecated.") - var foo: WrappedType - """ - let output = """ - @available(*, unavailable, message: "This property is deprecated.") var foo: WrappedType - """ - let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testDoesntWrapAvailableAttributeWithLongMessage() { - // Unwrapping this attribute would just cause it to wrap in a different way: - // - // @available( - // *, - // unavailable, - // message: "This property is deprecated. It has a really long message." - // ) var foo: WrappedType - // - // so instead leave it un-wrapped to preserve the existing formatting. - let input = """ - @available(*, unavailable, message: "This property is deprecated. It has a really long message.") - var foo: WrappedType - """ - let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testDoesntWrapComplexAttribute() { - let input = """ - @Option( - name: ["myArgument"], - help: "Long help text for my example arg from Swift argument parser") - var foo: WrappedType - """ - let options = FormatOptions(closingParenPosition: .sameLine, varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testDoesntWrapComplexMultilineAttribute() { - let input = """ - @available(*, deprecated, message: "Deprecated!") - var foo: WrappedType - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapsComplexAttribute() { - let input = """ - @available(*, deprecated, message: "Deprecated!") var foo: WrappedType - """ - - let output = """ - @available(*, deprecated, message: "Deprecated!") - var foo: WrappedType - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapAttributesIndentsLineCorrectly() { - let input = """ - class Foo { - @objc var foo = Foo() - } - """ - let output = """ - class Foo { - @objc - var foo = Foo() - } - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testComplexAttributesException() { - let input = """ - @Environment(\\.myEnvironmentVar) var foo: Foo - - @SomeCustomAttr(argument: true) var foo: Foo - - @available(*, deprecated) var foo: Foo - """ - - let output = """ - @Environment(\\.myEnvironmentVar) var foo: Foo - - @SomeCustomAttr(argument: true) var foo: Foo - - @available(*, deprecated) - var foo: Foo - """ - - let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine, complexAttributesExceptions: ["@SomeCustomAttr"]) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) - } - - func testMixedComplexAndSimpleAttributes() { - let input = """ - /// Simple attributes stay on a single line: - @State private var warpDriveEnabled: Bool - - @ObservedObject private var lifeSupportService: LifeSupportService - - @Environment(\\.controlPanelStyle) private var controlPanelStyle - - @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration - - /// Complex attributes are wrapped: - @AppStorage("ControlPanelState", store: myCustomUserDefaults) private var controlPanelState: ControlPanelState - - @Tweak(name: "Aspect ratio") private var aspectRatio = AspectRatio.stretch - - @available(*, unavailable) var saturn5Builder: Saturn5Builder - - @available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder - """ - - let output = """ - /// Simple attributes stay on a single line: - @State private var warpDriveEnabled: Bool - - @ObservedObject private var lifeSupportService: LifeSupportService - - @Environment(\\.controlPanelStyle) private var controlPanelStyle - - @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration - - /// Complex attributes are wrapped: - @AppStorage("ControlPanelState", store: myCustomUserDefaults) - private var controlPanelState: ControlPanelState - - @Tweak(name: "Aspect ratio") - private var aspectRatio = AspectRatio.stretch - - @available(*, unavailable) - var saturn5Builder: Saturn5Builder - - @available(*, unavailable, message: "No longer in production") - var saturn5Builder: Saturn5Builder - """ - - let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testEscapingClosureNotMistakenForComplexAttribute() { - let input = """ - func foo(_ fooClosure: @escaping () throws -> Void) { - try fooClosure() - } - """ - - let options = FormatOptions(complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testEscapingTypedThrowClosureNotMistakenForComplexAttribute() { - let input = """ - func foo(_ fooClosure: @escaping () throws(Foo) -> Void) { - try fooClosure() - } - """ - - let options = FormatOptions(complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapOrDontWrapMultipleDeclarationsInClass() { - let input = """ - class Foo { - @objc - var foo = Foo() - - @available(*, unavailable) - var bar: Bar - - @available(*, unavailable) - var myComputedFoo: String { - "myComputedFoo" - } - - @Environment(\\.myEnvironmentVar) - var foo - - @State - var myStoredFoo: String = "myStoredFoo" { - didSet { - print(newValue) - } - } - } - """ - let output = """ - class Foo { - @objc var foo = Foo() - - @available(*, unavailable) - var bar: Bar - - @available(*, unavailable) - var myComputedFoo: String { - "myComputedFoo" - } - - @Environment(\\.myEnvironmentVar) var foo - - @State var myStoredFoo: String = "myStoredFoo" { - didSet { - print(newValue) - } - } - } - """ - let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) - } - - func testWrapOrDontAttributesInSwiftUIView() { - let input = """ - struct MyView: View { - @State var textContent: String - - var body: some View { - childView - } - - @ViewBuilder - var childView: some View { - Text(verbatim: textContent) - } - } - """ - - let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testWrapAttributesInSwiftUIView() { - let input = """ - struct MyView: View { - @State var textContent: String - @Environment(\\.myEnvironmentVar) var environmentVar - - var body: some View { - childView - } - - @ViewBuilder var childView: some View { - Text(verbatim: textContent) - } - } - """ - - let options = FormatOptions(varAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - func testInlineMainActorAttributeNotWrapped() { - let input = """ - var foo: @MainActor (Foo) -> Void - var bar: @MainActor (Bar) -> Void - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) - } - - // MARK: wrapEnumCases - - func testMultilineEnumCases() { - let input = """ - enum Enum1: Int { - case a = 0, p = 2, c, d - case e, k - case m(String, String) - } - """ - let output = """ - enum Enum1: Int { - case a = 0 - case p = 2 - case c - case d - case e - case k - case m(String, String) - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) - } - - func testMultilineEnumCasesWithNestedEnumsDoesNothing() { - let input = """ - public enum SearchTerm: Decodable, Equatable { - case term(name: String) - case category(category: Category) - - enum CodingKeys: String, CodingKey { - case name - case type - case categoryID = "category_id" - case attributes - } - } - """ - testFormatting(for: input, rule: FormatRules.wrapEnumCases) - } - - func testEnumCaseSplitOverMultipleLines() { - let input = """ - enum Foo { - case bar( - x: String, - y: Int - ), baz - } - """ - let output = """ - enum Foo { - case bar( - x: String, - y: Int - ) - case baz - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) - } - - func testEnumCasesAlreadyWrappedOntoMultipleLines() { - let input = """ - enum Foo { - case bar, - baz, - quux - } - """ - let output = """ - enum Foo { - case bar - case baz - case quux - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) - } - - func testEnumCasesIfValuesWithoutValuesDoesNothing() { - let input = """ - enum Foo { - case bar, baz, quux - } - """ - testFormatting(for: input, rule: FormatRules.wrapEnumCases, - options: FormatOptions(wrapEnumCases: .withValues)) - } - - func testEnumCasesIfValuesWithRawValuesAndNestedEnum() { - let input = """ - enum Foo { - case bar = 1, baz, quux - - enum Foo2 { - case bar, baz, quux - } - } - """ - let output = """ - enum Foo { - case bar = 1 - case baz - case quux - - enum Foo2 { - case bar, baz, quux - } - } - """ - testFormatting( - for: input, - output, - rule: FormatRules.wrapEnumCases, - options: FormatOptions(wrapEnumCases: .withValues) - ) - } - - func testEnumCasesIfValuesWithAssociatedValues() { - let input = """ - enum Foo { - case bar(a: Int), baz, quux - } - """ - let output = """ - enum Foo { - case bar(a: Int) - case baz - case quux - } - """ - testFormatting( - for: input, - output, - rule: FormatRules.wrapEnumCases, - options: FormatOptions(wrapEnumCases: .withValues) - ) - } - - func testEnumCasesWithCommentsAlreadyWrappedOntoMultipleLines() { - let input = """ - enum Foo { - case bar, // bar - baz, // baz - quux // quux - } - """ - let output = """ - enum Foo { - case bar // bar - case baz // baz - case quux // quux - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) - } - - func testNoWrapEnumStatementAllOnOneLine() { - let input = "enum Foo { bar, baz }" - testFormatting(for: input, rule: FormatRules.wrapEnumCases) - } - - func testNoConfuseIfCaseWithEnum() { - let input = """ - enum Foo { - case foo - case bar(value: [Int]) - } - - func baz() { - if case .foo = foo, - case .bar(let value) = bar, - value.isEmpty - { - print("") - } - } - """ - testFormatting(for: input, rule: FormatRules.wrapEnumCases, - exclude: ["hoistPatternLet"]) - } - - func testNoMangleUnindentedEnumCases() { - let input = """ - enum Foo { - case foo, bar - } - """ - let output = """ - enum Foo { - case foo - case bar - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases, exclude: ["indent"]) - } - - func testNoMangleEnumCaseOnOpeningLine() { - let input = """ - enum SortOrder { case - asc(String), desc(String) - } - """ - // TODO: improve formatting here - let output = """ - enum SortOrder { case - asc(String) - case desc(String) - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases, exclude: ["indent"]) - } - - func testNoWrapSingleLineEnumCases() { - let input = "enum Foo { case foo, bar }" - testFormatting(for: input, rule: FormatRules.wrapEnumCases) - } - - // MARK: wrapSwitchCases - - func testMultilineSwitchCases() { - let input = """ - func foo() { - switch bar { - case .a(_), .b, "c": - print("") - case .d: - print("") - } - } - """ - let output = """ - func foo() { - switch bar { - case .a(_), - .b, - "c": - print("") - case .d: - print("") - } - } - """ - testFormatting(for: input, output, rule: FormatRules.wrapSwitchCases) - } - - func testIfAfterSwitchCaseNotWrapped() { - let input = """ - switch foo { - case "foo": - print("") - default: - print("") - } - if let foo = bar, foo != .baz { - throw error - } - """ - testFormatting(for: input, rule: FormatRules.wrapSwitchCases) - } - - // MARK: wrapSingleLineComments - - func testWrapSingleLineComment() { - let input = """ - // a b cde fgh - """ - let output = """ - // a b - // cde - // fgh - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 6)) - } - - func testWrapSingleLineCommentThatOverflowsByOneCharacter() { - let input = """ - // a b cde fg h - """ - let output = """ - // a b cde fg - // h - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 14)) - } - - func testNoWrapSingleLineCommentThatExactlyFits() { - let input = """ - // a b cde fg h - """ - - testFormatting(for: input, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 15)) - } - - func testWrapSingleLineCommentWithNoLeadingSpace() { - let input = """ - //a b cde fgh - """ - let output = """ - //a b - //cde - //fgh - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 6), - exclude: ["spaceInsideComments"]) - } - - func testWrapDocComment() { - let input = """ - /// a b cde fgh - """ - let output = """ - /// a b - /// cde - /// fgh - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 7), exclude: ["docComments"]) - } - - func testWrapDocLineCommentWithNoLeadingSpace() { - let input = """ - ///a b cde fgh - """ - let output = """ - ///a b - ///cde - ///fgh - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 6), - exclude: ["spaceInsideComments", "docComments"]) - } - - func testWrapSingleLineCommentWithIndent() { - let input = """ - func f() { - // a b cde fgh - let x = 1 - } - """ - let output = """ - func f() { - // a b cde - // fgh - let x = 1 - } - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 14), exclude: ["docComments"]) - } - - func testWrapSingleLineCommentAfterCode() { - let input = """ - func f() { - foo.bar() // this comment is much much much too long - } - """ - let output = """ - func f() { - foo.bar() // this comment - // is much much much too - // long - } - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 29), exclude: ["wrap"]) - } - - func testWrapDocCommentWithLongURL() { - let input = """ - /// See [Link](https://www.domain.com/pathextension/pathextension/pathextension/pathextension/pathextension/pathextension). - """ - - testFormatting(for: input, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 100), exclude: ["docComments"]) - } - - func testWrapDocCommentWithLongURL2() { - let input = """ - /// Link to SDK documentation - https://docs.adyen.com/checkout/3d-secure/native-3ds2/api-integration#collect-the-3d-secure-2-device-fingerprint-from-an-ios-app - """ - - testFormatting(for: input, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 80)) - } - - func testWrapDocCommentWithMultipleLongURLs() { - let input = """ - /// Link to http://a-very-long-url-that-wont-fit-on-one-line, http://another-very-long-url-that-wont-fit-on-one-line - """ - let output = """ - /// Link to http://a-very-long-url-that-wont-fit-on-one-line, - /// http://another-very-long-url-that-wont-fit-on-one-line - """ - - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, - options: FormatOptions(maxWidth: 40), exclude: ["docComments"]) - } - - // MARK: - wrapMultilineConditionalAssignment - - func testWrapIfExpressionAssignment() { - let input = """ - let foo = if let bar { - bar - } else { - baaz - } - """ - - let output = """ - let foo = - if let bar { - bar - } else { - baaz - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) - } - - func testUnwrapsAssignmentOperatorInIfExpressionAssignment() { - let input = """ - let foo - = if let bar { - bar - } else { - baaz - } - """ - - let output = """ - let foo = - if let bar { - bar - } else { - baaz - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) - } - - func testUnwrapsAssignmentOperatorInIfExpressionFollowingComment() { - let input = """ - let foo - // In order to unwrap the `=` here it has to move it to - // before the comment, rather than simply unwrapping it. - = if let bar { - bar - } else { - baaz - } - """ - - let output = """ - let foo = - // In order to unwrap the `=` here it has to move it to - // before the comment, rather than simply unwrapping it. - if let bar { - bar - } else { - baaz - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) - } - - func testWrapIfAssignmentWithoutIntroducer() { - let input = """ - property = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - - let output = """ - property = - if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) - } - - func testWrapSwitchAssignmentWithoutIntroducer() { - let input = """ - property = switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let output = """ - property = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) - } - - func testWrapSwitchAssignmentWithComplexLValue() { - let input = """ - property?.foo!.bar["baaz"] = switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let output = """ - property?.foo!.bar["baaz"] = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) - } -} diff --git a/Tests/SwiftFormatTests.swift b/Tests/SwiftFormatTests.swift index c3a7c479..7d8635d0 100644 --- a/Tests/SwiftFormatTests.swift +++ b/Tests/SwiftFormatTests.swift @@ -67,7 +67,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 73) + XCTAssertGreaterThanOrEqual(files.count, 180) } func testInputFilesMatchOutputFilesForSameOutput() { @@ -78,7 +78,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 73) + XCTAssertGreaterThanOrEqual(files.count, 180) } func testInputFileNotEnumeratedWhenExcluded() { @@ -92,8 +92,16 @@ class SwiftFormatTests: XCTestCase { XCTAssertEqual(inputURL, outputURL) return { files.append(inputURL) } } + + var allFiles = [URL]() + let allFilesInputURL = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent() + _ = enumerateFiles(withInputURL: allFilesInputURL, outputURL: allFilesInputURL) { inputURL, outputURL, _ in + XCTAssertEqual(inputURL, outputURL) + return { allFiles.append(inputURL) } + } + XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 46) + XCTAssertLessThan(files.count, allFiles.count) } // MARK: format function @@ -119,16 +127,16 @@ class SwiftFormatTests: XCTestCase { func testLintWithDefaultRules() { let input = "foo () " XCTAssertEqual(try lint(input), [ - .init(line: 1, rule: FormatRules.linebreakAtEndOfFile, filePath: nil), - .init(line: 1, rule: FormatRules.spaceAroundParens, filePath: nil), - .init(line: 1, rule: FormatRules.trailingSpace, filePath: nil), + .init(line: 1, rule: .linebreakAtEndOfFile, filePath: nil), + .init(line: 1, rule: .spaceAroundParens, filePath: nil), + .init(line: 1, rule: .trailingSpace, filePath: nil), ]) } func testLintConsecutiveBlankLinesAtEndOfFile() { let input = "foo\n\n" XCTAssertEqual(try lint(input), [ - .init(line: 2, rule: FormatRules.consecutiveBlankLines, filePath: nil), + .init(line: 2, rule: .consecutiveBlankLines, filePath: nil), ]) } @@ -277,7 +285,7 @@ class SwiftFormatTests: XCTestCase { let offset2 = SourceOffset(line: 2, column: 1) let offset3 = SourceOffset(line: 5, column: 1) let offset4 = SourceOffset(line: 6, column: 1) - let output = try format(input, rules: [FormatRules.consecutiveBlankLines]) + let output = try format(input, rules: [.consecutiveBlankLines]) let expected3 = SourceOffset(line: 4, column: 1) let expected4 = SourceOffset(line: 5, column: 1) XCTAssertEqual(newOffset(for: offset1, in: output, tabWidth: 1), offset1) @@ -319,6 +327,6 @@ class SwiftFormatTests: XCTestCase { func testLinebreakInferredForBlankLinesBetweenScopes() { let input = "class Foo {\r func bar() {\r }\r func baz() {\r }\r}" let output = "class Foo {\r func bar() {\r }\r\r func baz() {\r }\r}" - XCTAssertEqual(try format(input, rules: [FormatRules.blankLinesBetweenScopes]), output) + XCTAssertEqual(try format(input, rules: [.blankLinesBetweenScopes]), output) } } diff --git a/Tests/RulesTests.swift b/Tests/XCTestCase+testFormatting.swift similarity index 89% rename from Tests/RulesTests.swift rename to Tests/XCTestCase+testFormatting.swift index 3844b5d4..5d3070e9 100644 --- a/Tests/RulesTests.swift +++ b/Tests/XCTestCase+testFormatting.swift @@ -1,5 +1,5 @@ // -// RulesTests.swift +// XCTestCase+testFormatting.swift // SwiftFormat // // Created by Nick Lockwood on 12/08/2016. @@ -32,11 +32,9 @@ import XCTest @testable import SwiftFormat -class RulesTests: XCTestCase { - // MARK: - shared test infra - +extension XCTestCase { func testFormatting(for input: String, _ output: String? = nil, rule: FormatRule, - options: FormatOptions = .default, exclude: [String] = [], + options: FormatOptions = .default, exclude: [FormatRule] = [], file: StaticString = #file, line: UInt = #line) { testFormatting(for: input, output.map { [$0] } ?? [], rules: [rule], @@ -44,9 +42,16 @@ class RulesTests: XCTestCase { } func testFormatting(for input: String, _ outputs: [String] = [], rules: [FormatRule], - options: FormatOptions = .default, exclude: [String] = [], + options: FormatOptions = .default, exclude: [FormatRule] = [], file: StaticString = #file, line: UInt = #line) { + // Always make sure the rule registry is up-to-date before running the tests + do { + try FormatRules.generateRuleRegistryIfNecessary() + } catch { + XCTFail("Encountered error generating rule registry: \(error.localizedDescription)", file: file, line: line) + } + var options = options if options.timeout == FormatOptions.default.timeout { // Make breakpoint debugging easier by increasing timeout @@ -68,9 +73,9 @@ class RulesTests: XCTestCase { precondition(input != outputs.first || input != outputs.last, "Redundant output parameter") precondition((0 ... 2).contains(outputs.count), "Only 0, 1 or 2 output parameters permitted") - precondition(Set(exclude).intersection(rules.map { $0.name }).isEmpty, "Cannot exclude rule under test") + precondition(Set(exclude.map(\.name)).intersection(rules.map(\.name)).isEmpty, "Cannot exclude rule under test") let output = outputs.first ?? input, output2 = outputs.last ?? input - let exclude = exclude + FormatRules.deprecated + let exclude = exclude.map(\.name) + FormatRules.deprecated + (rules.first?.name == "linebreakAtEndOfFile" ? [] : ["linebreakAtEndOfFile"]) + (rules.first?.name == "organizeDeclarations" ? [] : ["organizeDeclarations"]) + (rules.first?.name == "extensionAccessControl" ? [] : ["extensionAccessControl"])