├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── rule-request.md ├── .gitignore ├── .gitmodules ├── .sourcery ├── AutomaticRuleTests.stencil ├── LinuxMain.stencil └── MasterRuleList.stencil ├── .swift-version ├── .swiftlint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.private ├── Cartfile.resolved ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── README_CN.md ├── README_KR.md ├── Releasing.md ├── Rules.md ├── Source ├── SwiftLintFramework │ ├── Extensions │ │ ├── Array+SwiftLint.swift │ │ ├── CharacterSet+SwiftLint.swift │ │ ├── Configuration+Cache.swift │ │ ├── Configuration+IndentationStyle.swift │ │ ├── Configuration+LintableFiles.swift │ │ ├── Configuration+Merging.swift │ │ ├── Configuration+Parsing.swift │ │ ├── Dictionary+SwiftLint.swift │ │ ├── File+Cache.swift │ │ ├── File+SwiftLint.swift │ │ ├── FileManager+SwiftLint.swift │ │ ├── NSRange+SwiftLint.swift │ │ ├── NSRegularExpression+SwiftLint.swift │ │ ├── QueuedPrint.swift │ │ ├── Request+DisableSourceKit.swift │ │ ├── String+SwiftLint.swift │ │ ├── String+XML.swift │ │ ├── Structure+SwiftLint.swift │ │ ├── SwiftDeclarationAttributeKind+Swiftlint.swift │ │ ├── SwiftDeclarationKind+SwiftLint.swift │ │ ├── SwiftExpressionKind.swift │ │ ├── SyntaxKind+SwiftLint.swift │ │ └── SyntaxMap+SwiftLint.swift │ ├── Helpers │ │ ├── Glob.swift │ │ ├── NamespaceCollector.swift │ │ └── RegexHelpers.swift │ ├── Models │ │ ├── AccessControlLevel.swift │ │ ├── Command.swift │ │ ├── Configuration.swift │ │ ├── ConfigurationError.swift │ │ ├── Correction.swift │ │ ├── Linter.swift │ │ ├── LinterCache.swift │ │ ├── Location.swift │ │ ├── MasterRuleList.swift │ │ ├── Region.swift │ │ ├── RuleDescription.swift │ │ ├── RuleIdentifier.swift │ │ ├── RuleKind.swift │ │ ├── RuleList+Documentation.swift │ │ ├── RuleList.swift │ │ ├── RuleParameter.swift │ │ ├── StyleViolation.swift │ │ ├── SwiftVersion.swift │ │ ├── Version.swift │ │ ├── ViolationSeverity.swift │ │ └── YamlParser.swift │ ├── Protocols │ │ ├── ASTRule.swift │ │ ├── CacheDescriptionProvider.swift │ │ ├── CallPairRule.swift │ │ ├── Reporter.swift │ │ ├── Rule.swift │ │ └── RuleConfiguration.swift │ ├── Reporters │ │ ├── CSVReporter.swift │ │ ├── CheckstyleReporter.swift │ │ ├── EmojiReporter.swift │ │ ├── HTMLReporter.swift │ │ ├── JSONReporter.swift │ │ ├── JUnitReporter.swift │ │ ├── MarkdownReporter.swift │ │ ├── SonarQubeReporter.swift │ │ └── XcodeReporter.swift │ ├── Rules │ │ ├── Idiomatic │ │ │ ├── BlockBasedKVORule.swift │ │ │ ├── ConvenienceTypeRule.swift │ │ │ ├── DiscouragedObjectLiteralRule.swift │ │ │ ├── DiscouragedOptionalBooleanRule.swift │ │ │ ├── DiscouragedOptionalBooleanRuleExamples.swift │ │ │ ├── DiscouragedOptionalCollectionExamples.swift │ │ │ ├── DiscouragedOptionalCollectionRule.swift │ │ │ ├── DuplicateImportsRule.swift │ │ │ ├── DuplicateImportsRuleExamples.swift │ │ │ ├── ExplicitACLRule.swift │ │ │ ├── ExplicitEnumRawValueRule.swift │ │ │ ├── ExplicitInitRule.swift │ │ │ ├── ExplicitTopLevelACLRule.swift │ │ │ ├── ExplicitTypeInterfaceRule.swift │ │ │ ├── ExtensionAccessModifierRule.swift │ │ │ ├── FallthroughRule.swift │ │ │ ├── FatalErrorMessageRule.swift │ │ │ ├── FileNameRule.swift │ │ │ ├── ForWhereRule.swift │ │ │ ├── ForceCastRule.swift │ │ │ ├── ForceTryRule.swift │ │ │ ├── ForceUnwrappingRule.swift │ │ │ ├── FunctionDefaultParameterAtEndRule.swift │ │ │ ├── GenericTypeNameRule.swift │ │ │ ├── ImplicitlyUnwrappedOptionalRule.swift │ │ │ ├── IsDisjointRule.swift │ │ │ ├── JoinedDefaultParameterRule.swift │ │ │ ├── LegacyCGGeometryFunctionsRule.swift │ │ │ ├── LegacyConstantRule.swift │ │ │ ├── LegacyConstantRuleExamples.swift │ │ │ ├── LegacyConstructorRule.swift │ │ │ ├── LegacyHashingRule.swift │ │ │ ├── LegacyNSGeometryFunctionsRule.swift │ │ │ ├── LegacyRandomRule.swift │ │ │ ├── NimbleOperatorRule.swift │ │ │ ├── NoExtensionAccessModifierRule.swift │ │ │ ├── NoFallthroughOnlyRule.swift │ │ │ ├── NoGroupingExtensionRule.swift │ │ │ ├── ObjectLiteralRule.swift │ │ │ ├── PatternMatchingKeywordsRule.swift │ │ │ ├── PrivateOverFilePrivateRule.swift │ │ │ ├── RedundantNilCoalescingRule.swift │ │ │ ├── RedundantObjcAttributeRule.swift │ │ │ ├── RedundantObjcAttributeRuleExamples.swift │ │ │ ├── RedundantOptionalInitializationRule.swift │ │ │ ├── RedundantSetAccessControlRule.swift │ │ │ ├── RedundantStringEnumValueRule.swift │ │ │ ├── RedundantTypeAnnotationRule.swift │ │ │ ├── RedundantVoidReturnRule.swift │ │ │ ├── StaticOperatorRule.swift │ │ │ ├── StrictFilePrivateRule.swift │ │ │ ├── SyntacticSugarRule.swift │ │ │ ├── ToggleBoolRule.swift │ │ │ ├── TrailingSemicolonRule.swift │ │ │ ├── TypeNameRule.swift │ │ │ ├── TypeNameRuleExamples.swift │ │ │ ├── UnavailableFunctionRule.swift │ │ │ ├── UnneededBreakInSwitchRule.swift │ │ │ ├── UntypedErrorInCatchRule.swift │ │ │ ├── UnusedEnumeratedRule.swift │ │ │ ├── XCTFailMessageRule.swift │ │ │ ├── XCTSpecificMatcherRule.swift │ │ │ └── XCTSpecificMatcherRuleExamples.swift │ │ ├── Lint │ │ │ ├── AnyObjectProtocolRule.swift │ │ │ ├── ArrayInitRule.swift │ │ │ ├── ClassDelegateProtocolRule.swift │ │ │ ├── CompilerProtocolInitRule.swift │ │ │ ├── DeploymentTargetRule.swift │ │ │ ├── DiscardedNotificationCenterObserverRule.swift │ │ │ ├── DiscouragedDirectInitRule.swift │ │ │ ├── DynamicInlineRule.swift │ │ │ ├── EmptyXCTestMethodRule.swift │ │ │ ├── EmptyXCTestMethodRuleExamples.swift │ │ │ ├── IdenticalOperandsRule.swift │ │ │ ├── InertDeferRule.swift │ │ │ ├── LowerACLThanParentRule.swift │ │ │ ├── MarkRule.swift │ │ │ ├── MissingDocsRule.swift │ │ │ ├── NSLocalizedStringKeyRule.swift │ │ │ ├── NSLocalizedStringRequireBundleRule.swift │ │ │ ├── NSObjectPreferIsEqualRule.swift │ │ │ ├── NSObjectPreferIsEqualRuleExamples.swift │ │ │ ├── NotificationCenterDetachmentRule.swift │ │ │ ├── NotificationCenterDetachmentRuleExamples.swift │ │ │ ├── OverriddenSuperCallRule.swift │ │ │ ├── OverrideInExtensionRule.swift │ │ │ ├── PrivateActionRule.swift │ │ │ ├── PrivateOutletRule.swift │ │ │ ├── PrivateUnitTestRule.swift │ │ │ ├── ProhibitedInterfaceBuilderRule.swift │ │ │ ├── ProhibitedSuperRule.swift │ │ │ ├── QuickDiscouragedCallRule.swift │ │ │ ├── QuickDiscouragedCallRuleExamples.swift │ │ │ ├── QuickDiscouragedFocusedTestRule.swift │ │ │ ├── QuickDiscouragedFocusedTestRuleExamples.swift │ │ │ ├── QuickDiscouragedPendingTestRule.swift │ │ │ ├── QuickDiscouragedPendingTestRuleExamples.swift │ │ │ ├── RequiredDeinitRule.swift │ │ │ ├── RequiredEnumCaseRule.swift │ │ │ ├── StrongIBOutletRule.swift │ │ │ ├── SuperfluousDisableCommandRule.swift │ │ │ ├── TodoRule.swift │ │ │ ├── UnusedCaptureListRule.swift │ │ │ ├── UnusedClosureParameterRule.swift │ │ │ ├── UnusedControlFlowLabelRule.swift │ │ │ ├── UnusedImportRule.swift │ │ │ ├── UnusedPrivateDeclarationRule.swift │ │ │ ├── UnusedSetterValueRule.swift │ │ │ ├── ValidIBInspectableRule.swift │ │ │ ├── WeakComputedProperyRule.swift │ │ │ ├── WeakDelegateRule.swift │ │ │ └── YodaConditionRule.swift │ │ ├── Metrics │ │ │ ├── ClosureBodyLengthRule.swift │ │ │ ├── ClosureBodyLengthRuleExamples.swift │ │ │ ├── CyclomaticComplexityRule.swift │ │ │ ├── FileLengthRule.swift │ │ │ ├── FunctionBodyLengthRule.swift │ │ │ ├── FunctionParameterCountRule.swift │ │ │ ├── LargeTupleRule.swift │ │ │ ├── LineLengthRule.swift │ │ │ ├── NestingRule.swift │ │ │ └── TypeBodyLengthRule.swift │ │ ├── Performance │ │ │ ├── ContainsOverFirstNotNilRule.swift │ │ │ ├── EmptyCountRule.swift │ │ │ ├── EmptyStringRule.swift │ │ │ ├── FirstWhereRule.swift │ │ │ ├── LastWhereRule.swift │ │ │ ├── ReduceBooleanRule.swift │ │ │ ├── ReduceIntoRule.swift │ │ │ └── SortedFirstLastRule.swift │ │ ├── RuleConfigurations │ │ │ ├── AttributesConfiguration.swift │ │ │ ├── CollectionAlignmentConfiguration.swift │ │ │ ├── ColonConfiguration.swift │ │ │ ├── ConditionalReturnsOnNewlineConfiguration.swift │ │ │ ├── CyclomaticComplexityConfiguration.swift │ │ │ ├── DeploymentTargetConfiguration.swift │ │ │ ├── DiscouragedDirectInitConfiguration.swift │ │ │ ├── ExplicitTypeInterfaceConfiguration.swift │ │ │ ├── FileHeaderConfiguration.swift │ │ │ ├── FileLengthRuleConfiguration.swift │ │ │ ├── FileNameConfiguration.swift │ │ │ ├── FileTypesOrderConfiguration.swift │ │ │ ├── FunctionParameterCountConfiguration.swift │ │ │ ├── ImplicitlyUnwrappedOptionalConfiguration.swift │ │ │ ├── LineLengthConfiguration.swift │ │ │ ├── MissingDocsRuleConfiguration.swift │ │ │ ├── ModifierOrderConfiguration.swift │ │ │ ├── MultilineArgumentsConfiguration.swift │ │ │ ├── NameConfiguration.swift │ │ │ ├── NestingConfiguration.swift │ │ │ ├── NumberSeparatorConfiguration.swift │ │ │ ├── ObjectLiteralConfiguration.swift │ │ │ ├── OverridenSuperCallConfiguration.swift │ │ │ ├── PrefixedConstantRuleConfiguration.swift │ │ │ ├── PrivateOutletRuleConfiguration.swift │ │ │ ├── PrivateOverFilePrivateRuleConfiguration.swift │ │ │ ├── PrivateUnitTestConfiguration.swift │ │ │ ├── ProhibitedSuperConfiguration.swift │ │ │ ├── RegexConfiguration.swift │ │ │ ├── RequiredEnumCaseRuleConfiguration.swift │ │ │ ├── SeverityConfiguration.swift │ │ │ ├── SeverityLevelsConfiguration.swift │ │ │ ├── StatementModeConfiguration.swift │ │ │ ├── SwitchCaseAlignmentConfiguration.swift │ │ │ ├── TrailingClosureConfiguration.swift │ │ │ ├── TrailingCommaConfiguration.swift │ │ │ ├── TrailingWhitespaceConfiguration.swift │ │ │ ├── TypeContentsOrderConfiguration.swift │ │ │ ├── UnusedOptionalBindingConfiguration.swift │ │ │ └── VerticalWhitespaceConfiguration.swift │ │ └── Style │ │ │ ├── AttributesRule.swift │ │ │ ├── AttributesRuleExamples.swift │ │ │ ├── ClosingBraceRule.swift │ │ │ ├── ClosureEndIndentationRule.swift │ │ │ ├── ClosureEndIndentationRuleExamples.swift │ │ │ ├── ClosureParameterPositionRule.swift │ │ │ ├── ClosureSpacingRule.swift │ │ │ ├── CollectionAlignmentRule.swift │ │ │ ├── ColonRule+Dictionary.swift │ │ │ ├── ColonRule+FunctionCall.swift │ │ │ ├── ColonRule+Type.swift │ │ │ ├── ColonRule.swift │ │ │ ├── ColonRuleExamples.swift │ │ │ ├── CommaRule.swift │ │ │ ├── ConditionalReturnsOnNewlineRule.swift │ │ │ ├── ControlStatementRule.swift │ │ │ ├── CustomRules.swift │ │ │ ├── EmptyEnumArgumentsRule.swift │ │ │ ├── EmptyParametersRule.swift │ │ │ ├── EmptyParenthesesWithTrailingClosureRule.swift │ │ │ ├── ExplicitSelfRule.swift │ │ │ ├── FileHeaderRule.swift │ │ │ ├── FileTypesOrderRule.swift │ │ │ ├── FileTypesOrderRuleExamples.swift │ │ │ ├── IdentifierNameRule.swift │ │ │ ├── IdentifierNameRuleExamples.swift │ │ │ ├── ImplicitGetterRule.swift │ │ │ ├── ImplicitReturnRule.swift │ │ │ ├── LeadingWhitespaceRule.swift │ │ │ ├── LetVarWhitespaceRule.swift │ │ │ ├── LiteralExpressionEndIdentationRule.swift │ │ │ ├── ModifierOrderRule.swift │ │ │ ├── ModifierOrderRuleExamples.swift │ │ │ ├── MultilineArgumentsBracketsRule.swift │ │ │ ├── MultilineArgumentsRule.swift │ │ │ ├── MultilineArgumentsRuleExamples.swift │ │ │ ├── MultilineFunctionChainsRule.swift │ │ │ ├── MultilineLiteralBracketsRule.swift │ │ │ ├── MultilineParametersBracketsRule.swift │ │ │ ├── MultilineParametersRule.swift │ │ │ ├── MultilineParametersRuleExamples.swift │ │ │ ├── MultipleClosuresWithTrailingClosureRule.swift │ │ │ ├── NumberSeparatorRule.swift │ │ │ ├── NumberSeparatorRuleExamples.swift │ │ │ ├── OpeningBraceRule.swift │ │ │ ├── OperatorFunctionWhitespaceRule.swift │ │ │ ├── OperatorUsageWhitespaceRule.swift │ │ │ ├── PrefixedTopLevelConstantRule.swift │ │ │ ├── ProtocolPropertyAccessorsOrderRule.swift │ │ │ ├── RedundantDiscardableLetRule.swift │ │ │ ├── ReturnArrowWhitespaceRule.swift │ │ │ ├── ShorthandOperatorRule.swift │ │ │ ├── SingleTestClassRule.swift │ │ │ ├── SortedImportsRule.swift │ │ │ ├── StatementPositionRule.swift │ │ │ ├── SwitchCaseAlignmentRule.swift │ │ │ ├── SwitchCaseOnNewlineRule.swift │ │ │ ├── TrailingClosureRule.swift │ │ │ ├── TrailingCommaRule.swift │ │ │ ├── TrailingNewlineRule.swift │ │ │ ├── TrailingWhitespaceRule.swift │ │ │ ├── TypeContentsOrderRule.swift │ │ │ ├── TypeContentsOrderRuleExamples.swift │ │ │ ├── UnneededParenthesesInClosureArgumentRule.swift │ │ │ ├── UnusedOptionalBindingRule.swift │ │ │ ├── VerticalParameterAlignmentOnCallRule.swift │ │ │ ├── VerticalParameterAlignmentRule.swift │ │ │ ├── VerticalWhitespaceBetweenCasesRule.swift │ │ │ ├── VerticalWhitespaceClosingBracesRule.swift │ │ │ ├── VerticalWhitespaceOpeningBracesRule.swift │ │ │ ├── VerticalWhitespaceRule.swift │ │ │ └── VoidReturnRule.swift │ └── Supporting Files │ │ └── Info.plist └── swiftlint │ ├── Commands │ ├── AnalyzeCommand.swift │ ├── AutoCorrectCommand.swift │ ├── GenerateDocsCommand.swift │ ├── LintCommand.swift │ ├── RulesCommand.swift │ └── VersionCommand.swift │ ├── Extensions │ ├── Configuration+CommandLine.swift │ └── Reporter+CommandLine.swift │ ├── Helpers │ ├── Benchmark.swift │ ├── CommonOptions.swift │ ├── CompilerArgumentsExtractor.swift │ ├── LintOrAnalyzeCommand.swift │ └── LintableFilesVisitor.swift │ ├── Supporting Files │ └── Info.plist │ └── main.swift ├── SwiftLint.podspec ├── SwiftLint.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── SwiftLintFramework.xcscheme │ └── swiftlint.xcscheme ├── SwiftLint.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftLintFramework.podspec ├── Tests ├── LinuxMain.swift └── SwiftLintFrameworkTests │ ├── AttributesRuleTests.swift │ ├── AutomaticRuleTests.generated.swift │ ├── CollectionAlignmentRuleTests.swift │ ├── ColonRuleTests.swift │ ├── CommandTests.swift │ ├── CompilerProtocolInitRuleTests.swift │ ├── ConditionalReturnsOnNewlineRuleTests.swift │ ├── ConfigurationAliasesTests.swift │ ├── ConfigurationTests+Nested.swift │ ├── ConfigurationTests+ProjectMock.swift │ ├── ConfigurationTests.swift │ ├── ContainsOverFirstNotNilRuleTests.swift │ ├── CustomRulesTests.swift │ ├── CyclomaticComplexityConfigurationTests.swift │ ├── CyclomaticComplexityRuleTests.swift │ ├── DeploymentTargetConfigurationTests.swift │ ├── DeploymentTargetRuleTests.swift │ ├── DisableAllTests.swift │ ├── DiscouragedDirectInitRuleTests.swift │ ├── DiscouragedObjectLiteralRuleTests.swift │ ├── DocumentationTests.swift │ ├── ExplicitTypeInterfaceConfigurationTests.swift │ ├── ExplicitTypeInterfaceRuleTests.swift │ ├── ExtendedNSStringTests.swift │ ├── FileHeaderRuleTests.swift │ ├── FileLengthRuleTests.swift │ ├── FileNameRuleTests.swift │ ├── FileTypesOrderRuleTests.swift │ ├── FunctionBodyLengthRuleTests.swift │ ├── FunctionParameterCountRuleTests.swift │ ├── GenericTypeNameRuleTests.swift │ ├── GlobTests.swift │ ├── IdentifierNameRuleTests.swift │ ├── ImplicitlyUnwrappedOptionalConfigurationTests.swift │ ├── ImplicitlyUnwrappedOptionalRuleTests.swift │ ├── IntegrationTests.swift │ ├── LineLengthConfigurationTests.swift │ ├── LineLengthRuleTests.swift │ ├── LinterCacheTests.swift │ ├── MissingDocsRuleConfigurationTests.swift │ ├── ModifierOrderTests.swift │ ├── MultilineArgumentsRuleTests.swift │ ├── NumberSeparatorRuleTests.swift │ ├── ObjectLiteralRuleTests.swift │ ├── PrefixedTopLevelConstantRuleTests.swift │ ├── PrivateOutletRuleTests.swift │ ├── PrivateOverFilePrivateRuleTests.swift │ ├── RegionTests.swift │ ├── ReporterTests.swift │ ├── RequiredEnumCaseRuleTestCase.swift │ ├── Resources │ ├── CannedCSVReporterOutput.csv │ ├── CannedCheckstyleReporterOutput.xml │ ├── CannedEmojiReporterOutput.txt │ ├── CannedHTMLReporterOutput.html │ ├── CannedJSONReporterOutput.json │ ├── CannedJunitReporterOutput.xml │ ├── CannedMarkdownReporterOutput.md │ ├── CannedSonarQubeReporterOutput.json │ ├── CannedXcodeReporterOutput.txt │ ├── FileHeaderRuleFixtures │ │ ├── FileNameCaseMismatch.swift │ │ ├── FileNameMatchingComplex.swift │ │ ├── FileNameMatchingSimple.swift │ │ ├── FileNameMismatch.swift │ │ └── FileNameMissing.swift │ ├── FileNameRuleFixtures │ │ ├── BoolExtension.swift │ │ ├── BoolExtensionTests.swift │ │ ├── BoolExtensions.swift │ │ ├── ExtensionBool+SwiftLint.swift │ │ ├── ExtensionBool.swift │ │ ├── ExtensionsBool.swift │ │ ├── LinuxMain.swift │ │ ├── MyClass.swift │ │ ├── MyStruct.swift │ │ ├── MyStructf.swift │ │ ├── NSString+Extension.swift │ │ ├── Notification.Name+Extension.swift │ │ ├── SLBoolExtension.swift │ │ └── main.swift │ ├── ProjectMock │ │ ├── .swiftlint.yml │ │ ├── Directory.swift │ │ │ └── DirectoryLevel1.swift │ │ ├── Level0.swift │ │ ├── Level1 │ │ │ ├── Level1.swift │ │ │ └── Level2 │ │ │ │ ├── .swiftlint.yml │ │ │ │ ├── Level2.swift │ │ │ │ ├── Level3 │ │ │ │ ├── .swiftlint.yml │ │ │ │ └── Level3.swift │ │ │ │ ├── custom_rules.yml │ │ │ │ └── custom_rules_disabled.yml │ │ ├── custom.yml │ │ └── custom_rules.yml │ ├── test.txt │ └── test.yml │ ├── RuleConfigurationTests.swift │ ├── RuleDescription+Examples.swift │ ├── RuleTests.swift │ ├── RulesTests.swift │ ├── SourceKitCrashTests.swift │ ├── StatementPositionRuleTests.swift │ ├── Supporting Files │ └── Info.plist │ ├── SwitchCaseAlignmentRuleTests.swift │ ├── TestHelpers.swift │ ├── TodoRuleTests.swift │ ├── TrailingClosureConfigurationTests.swift │ ├── TrailingClosureRuleTests.swift │ ├── TrailingCommaRuleTests.swift │ ├── TrailingWhitespaceTests.swift │ ├── TypeContentsOrderRuleTests.swift │ ├── TypeNameRuleTests.swift │ ├── UnusedOptionalBindingRuleTests.swift │ ├── VerticalWhitespaceRuleTests.swift │ ├── XCTSpecificMatcherRuleTests.swift │ ├── XCTestCase+BundlePath.swift │ ├── YamlParserTests.swift │ └── YamlSwiftLintTests.swift ├── assets ├── custom-rule.png ├── presentation.jpg ├── realm.png ├── runscript.png └── screenshot.png ├── azure-pipelines.yml └── script ├── LICENSE.md ├── README.md ├── Version.swift.template ├── bootstrap ├── check-xcode-version ├── cibuild ├── extract-tool └── oss-check /.gitattributes: -------------------------------------------------------------------------------- 1 | CHANGELOG.md merge=union 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ### New Issue Checklist 8 | 9 | - [ ] Updated SwiftLint to the latest version 10 | - [ ] I searched for [existing GitHub issues](https://github.com/realm/SwiftLint/issues) 11 | 12 | ### Describe the bug 13 | 14 | A clear and concise description of what the bug is. 15 | 16 | ##### Complete output when running SwiftLint, including the stack trace and command used 17 | 18 | ```bash 19 | $ swiftlint lint 20 | ``` 21 | 22 | ### Environment 23 | 24 | * SwiftLint version (run `swiftlint version` to be sure)? 25 | * Installation method used (Homebrew, CocoaPods, building from source, etc)? 26 | * Paste your configuration file: 27 | 28 | ```yml 29 | # insert yaml contents here 30 | ``` 31 | 32 | * Are you using [nested configurations](https://github.com/realm/SwiftLint#nested-configurations)? 33 | If so, paste their relative paths and respective contents. 34 | * Which Xcode version are you using (check `xcode-select -p`)? 35 | * Do you have a sample that shows the issue? Run `echo "[string here]" | swiftlint lint --no-cache --use-stdin --enable-all-rules` 36 | to quickly test if your example is really demonstrating the issue. If your example is more 37 | complex, you can use `swiftlint lint --path [file here] --no-cache --enable-all-rules`. 38 | 39 | ```swift 40 | // This triggers a violation: 41 | let foo = try! bar() 42 | ``` 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/rule-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Rule request 3 | about: Share your idea for a new rule 4 | 5 | --- 6 | 7 | ### New Issue Checklist 8 | 9 | - [ ] Updated SwiftLint to the latest version 10 | - [ ] I searched for [existing GitHub issues](https://github.com/realm/SwiftLint/issues) 11 | 12 | ### New rule request 13 | 14 | Please describe the rule idea, format 15 | this issue's title as `Rule Request: [Rule Name]` and describe: 16 | 17 | 1. Why should this rule be added? Share links to existing discussion about what 18 | the community thinks about this. 19 | 2. Provide several examples of what _would_ and _wouldn't_ trigger violations. 20 | 3. Should the rule be configurable, if so what parameters should be configurable? 21 | 4. Should the rule be opt-in or enabled by default? Why? 22 | See [README.md](../README.md#opt-in-rules) for guidelines on when to mark a rule as opt-in. 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | #Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # SwiftLint 46 | 47 | SwiftLint.pkg 48 | SwiftLintFramework.framework.zip 49 | benchmark_* 50 | portable_swiftlint.zip 51 | osscheck/ 52 | 53 | # SPM 54 | .build 55 | Packages 56 | Package.pins 57 | 58 | # macOS 59 | .DS_Store 60 | 61 | # Bundler 62 | .bundle/ 63 | bundle/ 64 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/Result"] 2 | path = Carthage/Checkouts/Result 3 | url = https://github.com/antitypical/Result.git 4 | [submodule "Carthage/Checkouts/SWXMLHash"] 5 | path = Carthage/Checkouts/SWXMLHash 6 | url = https://github.com/drmohundro/SWXMLHash.git 7 | [submodule "Carthage/Checkouts/xcconfigs"] 8 | path = Carthage/Checkouts/xcconfigs 9 | url = https://github.com/jspahrsummers/xcconfigs.git 10 | [submodule "Carthage/Checkouts/Commandant"] 11 | path = Carthage/Checkouts/Commandant 12 | url = https://github.com/Carthage/Commandant.git 13 | [submodule "Carthage/Checkouts/SourceKitten"] 14 | path = Carthage/Checkouts/SourceKitten 15 | url = https://github.com/jpsim/SourceKitten.git 16 | [submodule "Carthage/Checkouts/SwiftyTextTable"] 17 | path = Carthage/Checkouts/SwiftyTextTable 18 | url = https://github.com/scottrhoyt/SwiftyTextTable.git 19 | [submodule "Carthage/Checkouts/Yams"] 20 | path = Carthage/Checkouts/Yams 21 | url = https://github.com/jpsim/Yams.git 22 | -------------------------------------------------------------------------------- /.sourcery/AutomaticRuleTests.stencil: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | // swiftlint:disable file_length single_test_class type_name 5 | 6 | {% for rule in types.structs|based:"AutomaticTestableRule" %} 7 | class {{ rule.name }}Tests: XCTestCase { 8 | func testWithDefaultConfiguration() { 9 | verifyRule({{ rule.name }}.description) 10 | } 11 | } 12 | {% if not forloop.last %} 13 | 14 | {% endif %}{% endfor %} -------------------------------------------------------------------------------- /.sourcery/LinuxMain.stencil: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFrameworkTests 2 | import XCTest 3 | 4 | // swiftlint:disable line_length file_length 5 | 6 | {% for type in types.classes|based:"XCTestCase" %} 7 | extension {{ type.name }} { 8 | static var allTests: [(String, ({{ type.name }}) -> () throws -> Void)] = [ 9 | {% for method in type.methods where method.parameters.count == 0 and method.shortName|hasPrefix:"test" and method|!annotated:"skipTestOnLinux" %} ("{{ method.shortName }}", {{ method.shortName }}){% if not forloop.last %},{% endif %} 10 | {% endfor %}] 11 | } 12 | 13 | {% endfor %} 14 | XCTMain([ 15 | {% for type in types.classes|based:"XCTestCase" %} testCase({{ type.name }}.allTests){% if not forloop.last %},{% endif %} 16 | {% endfor %}]) 17 | -------------------------------------------------------------------------------- /.sourcery/MasterRuleList.stencil: -------------------------------------------------------------------------------- 1 | public let masterRuleList = RuleList(rules: [ 2 | {% for rule in types.structs where rule.name|hasSuffix:"Rule" or rule.name|hasSuffix:"Rules" %} {{ rule.name }}.self{% if not forloop.last %},{% endif %} 3 | {% endfor %}]) 4 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2.1 2 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "jpsim/SourceKitten" ~> 0.22.0 2 | github "scottrhoyt/SwiftyTextTable" ~> 0.8.2 3 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Carthage/Commandant" ~> 0.15.0 2 | github "jpsim/Yams" ~> 1.0.1 3 | github "jspahrsummers/xcconfigs" ~> 0.12.0 4 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Carthage/Commandant" "0.15.0" 2 | github "antitypical/Result" "4.0.0" 3 | github "drmohundro/SWXMLHash" "4.7.6" 4 | github "jpsim/SourceKitten" "0.22.0" 5 | github "jpsim/Yams" "1.0.1" 6 | github "jspahrsummers/xcconfigs" "0.12" 7 | github "scottrhoyt/SwiftyTextTable" "0.8.2" 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | gem "danger" 5 | gem "cocoapods", "~> 1.6.0.beta" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Realm Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | #if canImport(CommonCrypto) 5 | private let addCryptoSwift = false 6 | #else 7 | private let addCryptoSwift = true 8 | #endif 9 | 10 | let package = Package( 11 | name: "SwiftLint", 12 | products: [ 13 | .executable(name: "swiftlint", targets: ["swiftlint"]), 14 | .library(name: "SwiftLintFramework", targets: ["SwiftLintFramework"]) 15 | ], 16 | dependencies: [ 17 | .package(url: "https://github.com/Carthage/Commandant.git", from: "0.15.0"), 18 | .package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.22.0"), 19 | .package(url: "https://github.com/jpsim/Yams.git", from: "1.0.1"), 20 | .package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.8.2"), 21 | ] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "0.13.0")] : []), 22 | targets: [ 23 | .target( 24 | name: "swiftlint", 25 | dependencies: [ 26 | "Commandant", 27 | "SwiftLintFramework", 28 | "SwiftyTextTable", 29 | ] 30 | ), 31 | .target( 32 | name: "SwiftLintFramework", 33 | dependencies: [ 34 | "SourceKittenFramework", 35 | "Yams", 36 | ] + (addCryptoSwift ? ["CryptoSwift"] : []) 37 | ), 38 | .testTarget( 39 | name: "SwiftLintFrameworkTests", 40 | dependencies: [ 41 | "SwiftLintFramework" 42 | ], 43 | exclude: [ 44 | "Resources", 45 | ] 46 | ) 47 | ] 48 | ) 49 | -------------------------------------------------------------------------------- /Releasing.md: -------------------------------------------------------------------------------- 1 | # Releasing SwiftLint 2 | 3 | For SwiftLint contributors, follow these steps to cut a release: 4 | 5 | 1. Come up with a witty washer- or dryer-themed release name. Past names include: 6 | * Tumble Dry 7 | * FabricSoftenerRule 8 | * Top Loading 9 | * Fresh Out Of The Dryer 10 | 2. Push new version: `make push_version "0.2.0: Tumble Dry"` 11 | 3. Make sure you have the latest stable Xcode version installed and 12 | `xcode-select`ed. 13 | 4. Create the pkg installer, framework zip, and portable zip: `make release` 14 | 5. Create a GitHub release: https://github.com/realm/SwiftLint/releases/new 15 | * Specify the tag you just pushed from the dropdown. 16 | * Set the release title to the new version number & release name. 17 | * Add the changelog section to the release description text box. 18 | * Upload the pkg installer, framework zip, and portable zip you just built 19 | to the GitHub release binaries. 20 | * Click "Publish release". 21 | 6. Publish to Homebrew and CocoaPods trunk: `make publish` 22 | 7. Celebrate. :tada: 23 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/CharacterSet+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // This is workaround for https://bugs.swift.org/browse/SR-5971 4 | // Can be removed when 5 | // https://github.com/apple/swift-corelibs-foundation/pull/1471 is merged 6 | internal extension CharacterSet { 7 | init(safeCharactersIn string: String) { 8 | #if os(Linux) 9 | self.init() 10 | insert(charactersIn: string) 11 | #else 12 | self.init(charactersIn: string) 13 | #endif 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/Configuration+IndentationStyle.swift: -------------------------------------------------------------------------------- 1 | public extension Configuration { 2 | enum IndentationStyle: Equatable { 3 | case tabs 4 | case spaces(count: Int) 5 | 6 | public static var `default` = spaces(count: 4) 7 | 8 | internal init?(_ object: Any?) { 9 | switch object { 10 | case let value as Int: self = .spaces(count: value) 11 | case let value as String where value == "tabs": self = .tabs 12 | default: return nil 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/Configuration+LintableFiles.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | extension Configuration { 5 | public func lintableFiles(inPath path: String, forceExclude: Bool) -> [File] { 6 | return lintablePaths(inPath: path, forceExclude: forceExclude).compactMap(File.init(pathDeferringReading:)) 7 | } 8 | 9 | internal func lintablePaths(inPath path: String, forceExclude: Bool, 10 | fileManager: LintableFileManager = FileManager.default) -> [String] { 11 | // If path is a file and we're not forcing excludes, skip filtering with excluded/included paths 12 | if path.isFile && !forceExclude { 13 | return [path] 14 | } 15 | let pathsForPath = included.isEmpty ? fileManager.filesToLint(inPath: path, rootDirectory: nil) : [] 16 | let includedPaths = included.parallelFlatMap { 17 | fileManager.filesToLint(inPath: $0, rootDirectory: self.rootPath) 18 | } 19 | return filterExcludedPaths(fileManager: fileManager, in: pathsForPath, includedPaths) 20 | } 21 | } 22 | 23 | extension Configuration { 24 | public func filterExcludedPaths(fileManager: LintableFileManager = FileManager.default, 25 | in paths: [String]...) -> [String] { 26 | #if os(Linux) 27 | let allPaths = paths.reduce([], +) 28 | let result = NSMutableOrderedSet(capacity: allPaths.count) 29 | result.addObjects(from: allPaths) 30 | #else 31 | let result = NSMutableOrderedSet(array: paths.reduce([], +)) 32 | #endif 33 | let excludedPaths = self.excludedPaths(fileManager: fileManager) 34 | result.minusSet(Set(excludedPaths)) 35 | // swiftlint:disable:next force_cast 36 | return result.map { $0 as! String } 37 | } 38 | 39 | internal func excludedPaths(fileManager: LintableFileManager) -> [String] { 40 | return excluded 41 | .flatMap(Glob.resolveGlob) 42 | .parallelFlatMap { 43 | fileManager.filesToLint(inPath: $0, rootDirectory: self.rootPath) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/FileManager+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol LintableFileManager { 4 | func filesToLint(inPath: String, rootDirectory: String?) -> [String] 5 | func modificationDate(forFileAtPath: String) -> Date? 6 | } 7 | 8 | extension FileManager: LintableFileManager { 9 | public func filesToLint(inPath path: String, rootDirectory: String? = nil) -> [String] { 10 | let rootPath = rootDirectory ?? currentDirectoryPath 11 | let absolutePath = path.bridge() 12 | .absolutePathRepresentation(rootDirectory: rootPath).bridge() 13 | .standardizingPath 14 | 15 | // if path is a file, it won't be returned in `enumerator(atPath:)` 16 | if absolutePath.bridge().isSwiftFile() && absolutePath.isFile { 17 | return [absolutePath] 18 | } 19 | 20 | #if os(Linux) 21 | return enumerator(atPath: absolutePath)?.compactMap { element -> String? in 22 | guard let element = element as? String, element.bridge().isSwiftFile() else { return nil } 23 | let absoluteElementPath = absolutePath.bridge().appendingPathComponent(element) 24 | return absoluteElementPath.isFile ? absoluteElementPath : nil 25 | } ?? [] 26 | #else 27 | return subpaths(atPath: absolutePath)?.parallelCompactMap { element -> String? in 28 | guard element.bridge().isSwiftFile() else { return nil } 29 | let absoluteElementPath = absolutePath.bridge().appendingPathComponent(element) 30 | return absoluteElementPath.isFile ? absoluteElementPath : nil 31 | } ?? [] 32 | #endif 33 | } 34 | 35 | public func modificationDate(forFileAtPath path: String) -> Date? { 36 | return (try? attributesOfItem(atPath: path))?[.modificationDate] as? Date 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/NSRange+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSRange { 4 | func intersects(_ range: NSRange) -> Bool { 5 | return NSIntersectionRange(self, range).length > 0 6 | } 7 | 8 | func intersects(_ ranges: [NSRange]) -> Bool { 9 | for range in ranges where intersects(range) { 10 | return true 11 | } 12 | return false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/NSRegularExpression+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private var regexCache = [RegexCacheKey: NSRegularExpression]() 4 | private let regexCacheLock = NSLock() 5 | 6 | private struct RegexCacheKey: Hashable { 7 | // Disable unused private declaration rule here because even though we don't use these properties 8 | // directly, we rely on them for their hashable and equatable behavior. 9 | // swiftlint:disable unused_private_declaration 10 | let pattern: String 11 | let options: NSRegularExpression.Options 12 | // swiftlint:enable unused_private_declaration 13 | } 14 | 15 | extension NSRegularExpression.Options: Hashable { 16 | public func hash(into hasher: inout Hasher) { 17 | hasher.combine(rawValue) 18 | } 19 | } 20 | 21 | extension NSRegularExpression { 22 | internal static func cached(pattern: String, options: Options? = nil) throws -> NSRegularExpression { 23 | let options = options ?? [.anchorsMatchLines, .dotMatchesLineSeparators] 24 | let key = RegexCacheKey(pattern: pattern, options: options) 25 | regexCacheLock.lock() 26 | defer { regexCacheLock.unlock() } 27 | if let result = regexCache[key] { 28 | return result 29 | } 30 | 31 | let result = try NSRegularExpression(pattern: pattern, options: options) 32 | regexCache[key] = result 33 | return result 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/QueuedPrint.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | import Foundation 3 | 4 | private let outputQueue: DispatchQueue = { 5 | let queue = DispatchQueue( 6 | label: "io.realm.swiftlint.outputQueue", 7 | qos: .userInteractive, 8 | target: .global(qos: .userInteractive) 9 | ) 10 | 11 | #if !os(Linux) 12 | atexit_b { 13 | queue.sync(flags: .barrier) {} 14 | } 15 | #endif 16 | 17 | return queue 18 | }() 19 | 20 | /** 21 | A thread-safe version of Swift's standard print(). 22 | 23 | - parameter object: Object to print. 24 | */ 25 | public func queuedPrint(_ object: T) { 26 | outputQueue.async { 27 | print(object) 28 | } 29 | } 30 | 31 | /** 32 | A thread-safe, newline-terminated version of fputs(..., stderr). 33 | 34 | - parameter string: String to print. 35 | */ 36 | public func queuedPrintError(_ string: String) { 37 | outputQueue.async { 38 | fflush(stdout) 39 | fputs(string + "\n", stderr) 40 | } 41 | } 42 | 43 | /** 44 | A thread-safe, newline-terminated version of fatalError that doesn't leak 45 | the source path from the compiled binary. 46 | */ 47 | public func queuedFatalError(_ string: String, file: StaticString = #file, line: UInt = #line) -> Never { 48 | outputQueue.sync { 49 | fflush(stdout) 50 | let file = "\(file)".bridge().lastPathComponent 51 | fputs("\(string): file \(file), line \(line)\n", stderr) 52 | } 53 | 54 | abort() 55 | } 56 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/Request+DisableSourceKit.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | extension Request { 5 | static let disableSourceKit = ProcessInfo.processInfo.environment["SWIFTLINT_DISABLE_SOURCEKIT"] != nil 6 | 7 | func sendIfNotDisabled() throws -> [String: SourceKitRepresentable] { 8 | guard !Request.disableSourceKit else { 9 | throw Request.Error.connectionInterrupted("SourceKit is disabled by `SWIFTLINT_DISABLE_SOURCEKIT`.") 10 | } 11 | return try send() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/String+XML.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | func escapedForXML() -> String { 3 | // & needs to go first, otherwise other replacements will be replaced again 4 | let htmlEscapes = [ 5 | ("&", "&"), 6 | ("\"", """), 7 | ("'", "'"), 8 | (">", ">"), 9 | ("<", "<") 10 | ] 11 | var newString = self 12 | for (key, value) in htmlEscapes { 13 | newString = newString.replacingOccurrences(of: key, with: value) 14 | } 15 | return newString 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/Structure+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | extension Structure { 5 | /// Returns array of tuples containing "key.kind" and "byteRange" from Structure 6 | /// that contains the byte offset. Returns all kinds if no parameter specified. 7 | /// 8 | /// - Parameter byteOffset: Int? 9 | internal func kinds(forByteOffset byteOffset: Int? = nil) -> [(kind: String, byteRange: NSRange)] { 10 | var results = [(kind: String, byteRange: NSRange)]() 11 | 12 | func parse(_ dictionary: [String: SourceKitRepresentable]) { 13 | guard let offset = dictionary.offset, 14 | let byteRange = dictionary.length.map({ NSRange(location: offset, length: $0) }) else { 15 | return 16 | } 17 | if let byteOffset = byteOffset, !NSLocationInRange(byteOffset, byteRange) { 18 | return 19 | } 20 | if let kind = dictionary.kind { 21 | results.append((kind: kind, byteRange: byteRange)) 22 | } 23 | dictionary.substructure.forEach(parse) 24 | } 25 | parse(dictionary) 26 | return results 27 | } 28 | 29 | internal func structures(forByteOffset byteOffset: Int) -> [[String: SourceKitRepresentable]] { 30 | var results = [[String: SourceKitRepresentable]]() 31 | 32 | func parse(_ dictionary: [String: SourceKitRepresentable]) { 33 | guard let offset = dictionary.offset, 34 | let byteRange = dictionary.length.map({ NSRange(location: offset, length: $0) }), 35 | NSLocationInRange(byteOffset, byteRange) else { 36 | return 37 | } 38 | 39 | results.append(dictionary) 40 | dictionary.substructure.forEach(parse) 41 | } 42 | parse(dictionary) 43 | return results 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/SwiftDeclarationKind+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | extension SwiftDeclarationKind { 4 | internal static let variableKinds: Set = [ 5 | .varClass, 6 | .varGlobal, 7 | .varInstance, 8 | .varLocal, 9 | .varParameter, 10 | .varStatic 11 | ] 12 | 13 | internal static let functionKinds: Set = [ 14 | .functionAccessorAddress, 15 | .functionAccessorDidset, 16 | .functionAccessorGetter, 17 | .functionAccessorMutableaddress, 18 | .functionAccessorSetter, 19 | .functionAccessorWillset, 20 | .functionConstructor, 21 | .functionDestructor, 22 | .functionFree, 23 | .functionMethodClass, 24 | .functionMethodInstance, 25 | .functionMethodStatic, 26 | .functionOperator, 27 | .functionSubscript 28 | ] 29 | 30 | internal static let typeKinds: Set = [ 31 | .class, 32 | .struct, 33 | .typealias, 34 | .associatedtype, 35 | .enum 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/SwiftExpressionKind.swift: -------------------------------------------------------------------------------- 1 | public enum SwiftExpressionKind: String { 2 | case call = "source.lang.swift.expr.call" 3 | case argument = "source.lang.swift.expr.argument" 4 | case array = "source.lang.swift.expr.array" 5 | case dictionary = "source.lang.swift.expr.dictionary" 6 | case objectLiteral = "source.lang.swift.expr.object_literal" 7 | case closure = "source.lang.swift.expr.closure" 8 | case tuple = "source.lang.swift.expr.tuple" 9 | } 10 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/SyntaxKind+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | extension SyntaxKind { 4 | init(shortName: Swift.String) throws { 5 | let prefix = "source.lang.swift.syntaxtype." 6 | guard let kind = SyntaxKind(rawValue: prefix + shortName.lowercased()) else { 7 | throw ConfigurationError.unknownConfiguration 8 | } 9 | self = kind 10 | } 11 | 12 | static let commentAndStringKinds: Set = commentKinds.union([.string]) 13 | 14 | static let commentKinds: Set = [.comment, .commentMark, .commentURL, 15 | .docComment, .docCommentField] 16 | 17 | static let allKinds: Set = [.argument, .attributeBuiltin, .attributeID, .buildconfigID, 18 | .buildconfigKeyword, .comment, .commentMark, .commentURL, 19 | .docComment, .docCommentField, .identifier, .keyword, .number, 20 | .objectLiteral, .parameter, .placeholder, .string, 21 | .stringInterpolationAnchor, .typeidentifier] 22 | } 23 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Extensions/SyntaxMap+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | extension SyntaxMap { 5 | /// Returns array of SyntaxTokens intersecting with byte range 6 | /// 7 | /// - Parameter byteRange: byte based NSRange 8 | internal func tokens(inByteRange byteRange: NSRange) -> [SyntaxToken] { 9 | func intersect(_ token: SyntaxToken) -> Bool { 10 | return NSRange(location: token.offset, length: token.length) 11 | .intersects(byteRange) 12 | } 13 | 14 | guard let startIndex = tokens.firstIndex(where: intersect) else { 15 | return [] 16 | } 17 | let tokensBeginningIntersect = tokens.lazy.suffix(from: startIndex) 18 | return Array(tokensBeginningIntersect.filter(intersect)) 19 | } 20 | 21 | internal func kinds(inByteRange byteRange: NSRange) -> [SyntaxKind] { 22 | return tokens(inByteRange: byteRange).kinds 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Helpers/Glob.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(Darwin) 4 | import Darwin 5 | 6 | private let globFunction = Darwin.glob 7 | #elseif canImport(Glibc) 8 | import Glibc 9 | 10 | private let globFunction = Glibc.glob 11 | #else 12 | #error("Unsupported platform") 13 | #endif 14 | 15 | struct Glob { 16 | static func resolveGlob(_ pattern: String) -> [String] { 17 | let globCharset = CharacterSet(charactersIn: "*?[]") 18 | guard pattern.rangeOfCharacter(from: globCharset) != nil else { 19 | return [pattern] 20 | } 21 | 22 | var globResult = glob_t() 23 | defer { globfree(&globResult) } 24 | 25 | let flags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK 26 | guard globFunction(pattern.cString(using: .utf8)!, flags, nil, &globResult) == 0 else { 27 | return [] 28 | } 29 | 30 | #if os(Linux) 31 | let matchCount = globResult.gl_pathc 32 | #else 33 | let matchCount = globResult.gl_matchc 34 | #endif 35 | 36 | return (0.., 31 | namespace: [String] = []) -> [Element] { 32 | return findAllElements(in: dictionary, of: types, namespace: namespace) 33 | } 34 | 35 | private func findAllElements(in dictionary: [String: SourceKitRepresentable], 36 | of types: Set, 37 | namespace: [String] = []) -> [Element] { 38 | return dictionary.substructure.flatMap { subDict -> [Element] in 39 | var elements: [Element] = [] 40 | guard let element = Element(dictionary: subDict, namespace: namespace) else { 41 | return elements 42 | } 43 | 44 | if types.contains(element.kind) { 45 | elements.append(element) 46 | } 47 | 48 | elements += findAllElements(in: subDict, of: types, namespace: [element.name]) 49 | 50 | return elements 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Helpers/RegexHelpers.swift: -------------------------------------------------------------------------------- 1 | struct RegexHelpers { 2 | // A single variable 3 | static let varName = "[a-zA-Z_][a-zA-Z0-9_]+" 4 | 5 | // A single variable in a group (capturable) 6 | static let varNameGroup = "\\s*(\(varName))\\s*" 7 | 8 | // Two variables (capturables) 9 | static let twoVars = "\(varNameGroup),\(varNameGroup)" 10 | 11 | // A number 12 | static let number = "[\\-0-9\\.]+" 13 | 14 | // A variable or a number (capturable) 15 | static let variableOrNumber = "\\s*(\(varName)|\(number))\\s*" 16 | 17 | // Two 'variable or number' 18 | static let twoVariableOrNumber = "\(variableOrNumber),\(variableOrNumber)" 19 | } 20 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/AccessControlLevel.swift: -------------------------------------------------------------------------------- 1 | public enum AccessControlLevel: String, CustomStringConvertible { 2 | case `private` = "source.lang.swift.accessibility.private" 3 | case `fileprivate` = "source.lang.swift.accessibility.fileprivate" 4 | case `internal` = "source.lang.swift.accessibility.internal" 5 | case `public` = "source.lang.swift.accessibility.public" 6 | case `open` = "source.lang.swift.accessibility.open" 7 | 8 | internal init?(description value: String) { 9 | switch value { 10 | case "private": self = .private 11 | case "fileprivate": self = .fileprivate 12 | case "internal": self = .internal 13 | case "public": self = .public 14 | case "open": self = .open 15 | default: return nil 16 | } 17 | } 18 | 19 | init?(identifier value: String) { 20 | self.init(rawValue: value) 21 | } 22 | 23 | public var description: String { 24 | switch self { 25 | case .private: return "private" 26 | case .fileprivate: return "fileprivate" 27 | case .internal: return "internal" 28 | case .public: return "public" 29 | case .open: return "open" 30 | } 31 | } 32 | 33 | // Returns true if is `private` or `fileprivate` 34 | var isPrivate: Bool { 35 | return self == .private || self == .fileprivate 36 | } 37 | } 38 | 39 | extension AccessControlLevel: Comparable { 40 | private var priority: Int { 41 | switch self { 42 | case .private: return 1 43 | case .fileprivate: return 2 44 | case .internal: return 3 45 | case .public: return 4 46 | case .open: return 5 47 | } 48 | } 49 | 50 | public static func < (lhs: AccessControlLevel, rhs: AccessControlLevel) -> Bool { 51 | return lhs.priority < rhs.priority 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/ConfigurationError.swift: -------------------------------------------------------------------------------- 1 | public enum ConfigurationError: Error { 2 | case unknownConfiguration 3 | } 4 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/Correction.swift: -------------------------------------------------------------------------------- 1 | public struct Correction: Equatable { 2 | public let ruleDescription: RuleDescription 3 | public let location: Location 4 | 5 | public var consoleDescription: String { 6 | return "\(location) Corrected \(ruleDescription.name)" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/Region.swift: -------------------------------------------------------------------------------- 1 | public struct Region: Equatable { 2 | public let start: Location 3 | public let end: Location 4 | public let disabledRuleIdentifiers: Set 5 | 6 | public init(start: Location, end: Location, disabledRuleIdentifiers: Set) { 7 | self.start = start 8 | self.end = end 9 | self.disabledRuleIdentifiers = disabledRuleIdentifiers 10 | } 11 | 12 | public func contains(_ location: Location) -> Bool { 13 | return start <= location && end >= location 14 | } 15 | 16 | public func isRuleEnabled(_ rule: Rule) -> Bool { 17 | return !isRuleDisabled(rule) 18 | } 19 | 20 | public func isRuleDisabled(_ rule: Rule) -> Bool { 21 | guard !disabledRuleIdentifiers.contains(.all) else { 22 | return true 23 | } 24 | 25 | let identifiersToCheck = type(of: rule).description.allIdentifiers 26 | let regionIdentifiers = Set(disabledRuleIdentifiers.map { $0.stringRepresentation }) 27 | return !regionIdentifiers.isDisjoint(with: identifiersToCheck) 28 | } 29 | 30 | public func deprecatedAliasesDisabling(rule: Rule) -> Set { 31 | let identifiers = type(of: rule).description.deprecatedAliases 32 | return Set(disabledRuleIdentifiers.map { $0.stringRepresentation }).intersection(identifiers) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/RuleDescription.swift: -------------------------------------------------------------------------------- 1 | public struct RuleDescription: Equatable { 2 | public let identifier: String 3 | public let name: String 4 | public let description: String 5 | public let kind: RuleKind 6 | public let nonTriggeringExamples: [String] 7 | public let triggeringExamples: [String] 8 | public let corrections: [String: String] 9 | public let deprecatedAliases: Set 10 | public let minSwiftVersion: SwiftVersion 11 | public let requiresFileOnDisk: Bool 12 | 13 | public var consoleDescription: String { return "\(name) (\(identifier)): \(description)" } 14 | 15 | public var allIdentifiers: [String] { 16 | return Array(deprecatedAliases) + [identifier] 17 | } 18 | 19 | public init(identifier: String, name: String, description: String, kind: RuleKind, 20 | minSwiftVersion: SwiftVersion = .three, 21 | nonTriggeringExamples: [String] = [], triggeringExamples: [String] = [], 22 | corrections: [String: String] = [:], 23 | deprecatedAliases: Set = [], 24 | requiresFileOnDisk: Bool = false) { 25 | self.identifier = identifier 26 | self.name = name 27 | self.description = description 28 | self.kind = kind 29 | self.nonTriggeringExamples = nonTriggeringExamples 30 | self.triggeringExamples = triggeringExamples 31 | self.corrections = corrections 32 | self.deprecatedAliases = deprecatedAliases 33 | self.minSwiftVersion = minSwiftVersion 34 | self.requiresFileOnDisk = requiresFileOnDisk 35 | } 36 | 37 | // MARK: Equatable 38 | 39 | public static func == (lhs: RuleDescription, rhs: RuleDescription) -> Bool { 40 | return lhs.identifier == rhs.identifier 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/RuleIdentifier.swift: -------------------------------------------------------------------------------- 1 | public enum RuleIdentifier: Hashable, ExpressibleByStringLiteral { 2 | case all 3 | case single(identifier: String) 4 | 5 | private static let allStringRepresentation = "all" 6 | 7 | public var stringRepresentation: String { 8 | switch self { 9 | case .all: 10 | return RuleIdentifier.allStringRepresentation 11 | 12 | case .single(let identifier): 13 | return identifier 14 | } 15 | } 16 | 17 | public init(_ value: String) { 18 | self = value == RuleIdentifier.allStringRepresentation ? .all : .single(identifier: value) 19 | } 20 | 21 | public init(stringLiteral value: String) { 22 | self = RuleIdentifier(value) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/RuleKind.swift: -------------------------------------------------------------------------------- 1 | public enum RuleKind: String { 2 | case lint 3 | case idiomatic 4 | case style 5 | case metrics 6 | case performance 7 | } 8 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/RuleParameter.swift: -------------------------------------------------------------------------------- 1 | public struct RuleParameter: Equatable { 2 | public let severity: ViolationSeverity 3 | public let value: T 4 | 5 | public init(severity: ViolationSeverity, value: T) { 6 | self.severity = severity 7 | self.value = value 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/StyleViolation.swift: -------------------------------------------------------------------------------- 1 | public struct StyleViolation: CustomStringConvertible, Equatable { 2 | public let ruleDescription: RuleDescription 3 | public let severity: ViolationSeverity 4 | public let location: Location 5 | public let reason: String 6 | public var description: String { 7 | return XcodeReporter.generateForSingleViolation(self) 8 | } 9 | 10 | public init(ruleDescription: RuleDescription, severity: ViolationSeverity = .warning, 11 | location: Location, reason: String? = nil) { 12 | self.ruleDescription = ruleDescription 13 | self.severity = severity 14 | self.location = location 15 | self.reason = reason ?? ruleDescription.description 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/Version.swift: -------------------------------------------------------------------------------- 1 | public struct Version { 2 | public let value: String 3 | 4 | public static let current = Version(value: "0.31.0") 5 | } 6 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/ViolationSeverity.swift: -------------------------------------------------------------------------------- 1 | public enum ViolationSeverity: String, Comparable { 2 | case warning 3 | case error 4 | 5 | // MARK: Comparable 6 | 7 | public static func < (lhs: ViolationSeverity, rhs: ViolationSeverity) -> Bool { 8 | return lhs == .warning && rhs == .error 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Models/YamlParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Yams 3 | 4 | // MARK: - YamlParsingError 5 | 6 | internal enum YamlParserError: Error, Equatable { 7 | case yamlParsing(String) 8 | } 9 | 10 | // MARK: - YamlParser 11 | 12 | public struct YamlParser { 13 | public static func parse(_ yaml: String, 14 | env: [String: String] = ProcessInfo.processInfo.environment) throws -> [String: Any] { 15 | do { 16 | return try Yams.load(yaml: yaml, .default, 17 | .swiftlintContructor(env: env)) as? [String: Any] ?? [:] 18 | } catch { 19 | throw YamlParserError.yamlParsing("\(error)") 20 | } 21 | } 22 | } 23 | 24 | private extension Constructor { 25 | static func swiftlintContructor(env: [String: String]) -> Constructor { 26 | return Constructor(customScalarMap(env: env)) 27 | } 28 | 29 | static func customScalarMap(env: [String: String]) -> ScalarMap { 30 | var map = defaultScalarMap 31 | map[.str] = String.constructExpandingEnvVars(env: env) 32 | map[.bool] = Bool.constructUsingOnlyTrueAndFalse 33 | 34 | return map 35 | } 36 | } 37 | 38 | private extension String { 39 | static func constructExpandingEnvVars(env: [String: String]) -> (_ scalar: Node.Scalar) -> String? { 40 | return { (scalar: Node.Scalar) -> String? in 41 | return scalar.string.expandingEnvVars(env: env) 42 | } 43 | } 44 | 45 | func expandingEnvVars(env: [String: String]) -> String { 46 | var result = self 47 | for (key, value) in env { 48 | result = result.replacingOccurrences(of: "${\(key)}", with: value) 49 | } 50 | 51 | return result 52 | } 53 | } 54 | 55 | private extension Bool { 56 | static func constructUsingOnlyTrueAndFalse(from scalar: Node.Scalar) -> Bool? { 57 | switch scalar.string.lowercased() { 58 | case "true": 59 | return true 60 | case "false": 61 | return false 62 | default: 63 | return nil 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Protocols/ASTRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public protocol ASTRule: Rule { 4 | associatedtype KindType: RawRepresentable 5 | func validate(file: File, kind: KindType, dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] 6 | } 7 | 8 | public extension ASTRule where KindType.RawValue == String { 9 | func validate(file: File) -> [StyleViolation] { 10 | return validate(file: file, dictionary: file.structure.dictionary) 11 | } 12 | 13 | func validate(file: File, dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 14 | return dictionary.substructure.flatMap { subDict -> [StyleViolation] in 15 | var violations = validate(file: file, dictionary: subDict) 16 | 17 | if let kindString = subDict.kind, 18 | let kind = KindType(rawValue: kindString) { 19 | violations += validate(file: file, kind: kind, dictionary: subDict) 20 | } 21 | 22 | return violations 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Protocols/CacheDescriptionProvider.swift: -------------------------------------------------------------------------------- 1 | internal protocol CacheDescriptionProvider { 2 | var cacheDescription: String { get } 3 | } 4 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Protocols/Reporter.swift: -------------------------------------------------------------------------------- 1 | public protocol Reporter: CustomStringConvertible { 2 | static var identifier: String { get } 3 | static var isRealtime: Bool { get } 4 | 5 | static func generateReport(_ violations: [StyleViolation]) -> String 6 | } 7 | 8 | public func reporterFrom(identifier: String) -> Reporter.Type { 9 | switch identifier { 10 | case XcodeReporter.identifier: 11 | return XcodeReporter.self 12 | case JSONReporter.identifier: 13 | return JSONReporter.self 14 | case CSVReporter.identifier: 15 | return CSVReporter.self 16 | case CheckstyleReporter.identifier: 17 | return CheckstyleReporter.self 18 | case JUnitReporter.identifier: 19 | return JUnitReporter.self 20 | case HTMLReporter.identifier: 21 | return HTMLReporter.self 22 | case EmojiReporter.identifier: 23 | return EmojiReporter.self 24 | case SonarQubeReporter.identifier: 25 | return SonarQubeReporter.self 26 | case MarkdownReporter.identifier: 27 | return MarkdownReporter.self 28 | default: 29 | queuedFatalError("no reporter with identifier '\(identifier)' available.") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Protocols/RuleConfiguration.swift: -------------------------------------------------------------------------------- 1 | public protocol RuleConfiguration { 2 | var consoleDescription: String { get } 3 | 4 | mutating func apply(configuration: Any) throws 5 | func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool 6 | } 7 | 8 | extension RuleConfiguration { 9 | internal var cacheDescription: String { 10 | return (self as? CacheDescriptionProvider)?.cacheDescription ?? consoleDescription 11 | } 12 | } 13 | 14 | public extension RuleConfiguration where Self: Equatable { 15 | func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool { 16 | return self == ruleConfiguration as? Self 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/CSVReporter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private extension String { 4 | func escapedForCSV() -> String { 5 | let escapedString = replacingOccurrences(of: "\"", with: "\"\"") 6 | if escapedString.contains(",") || escapedString.contains("\n") { 7 | return "\"\(escapedString)\"" 8 | } 9 | return escapedString 10 | } 11 | } 12 | 13 | public struct CSVReporter: Reporter { 14 | public static let identifier = "csv" 15 | public static let isRealtime = false 16 | 17 | public var description: String { 18 | return "Reports violations as a newline-separated string of comma-separated values (CSV)." 19 | } 20 | 21 | public static func generateReport(_ violations: [StyleViolation]) -> String { 22 | let keys = [ 23 | "file", 24 | "line", 25 | "character", 26 | "severity", 27 | "type", 28 | "reason", 29 | "rule_id" 30 | ].joined(separator: ",") 31 | 32 | let rows = [keys] + violations.map(csvRow(for:)) 33 | return rows.joined(separator: "\n") 34 | } 35 | 36 | fileprivate static func csvRow(for violation: StyleViolation) -> String { 37 | return [ 38 | violation.location.file?.escapedForCSV() ?? "", 39 | violation.location.line?.description ?? "", 40 | violation.location.character?.description ?? "", 41 | violation.severity.rawValue.capitalized, 42 | violation.ruleDescription.name.escapedForCSV(), 43 | violation.reason.escapedForCSV(), 44 | violation.ruleDescription.identifier 45 | ].joined(separator: ",") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/CheckstyleReporter.swift: -------------------------------------------------------------------------------- 1 | public struct CheckstyleReporter: Reporter { 2 | public static let identifier = "checkstyle" 3 | public static let isRealtime = false 4 | 5 | public var description: String { 6 | return "Reports violations as Checkstyle XML." 7 | } 8 | 9 | public static func generateReport(_ violations: [StyleViolation]) -> String { 10 | return [ 11 | "\n", 12 | violations 13 | .group(by: { ($0.location.file ?? "").escapedForXML() }) 14 | .sorted(by: { $0.key < $1.key }) 15 | .map(generateForViolationFile).joined(), 16 | "\n" 17 | ].joined() 18 | } 19 | 20 | private static func generateForViolationFile(_ file: String, violations: [StyleViolation]) -> String { 21 | return [ 22 | "\n\t\n", 23 | violations.map(generateForSingleViolation).joined(), 24 | "\t" 25 | ].joined() 26 | } 27 | 28 | private static func generateForSingleViolation(_ violation: StyleViolation) -> String { 29 | let line: Int = violation.location.line ?? 0 30 | let col: Int = violation.location.character ?? 0 31 | let severity: String = violation.severity.rawValue 32 | let reason: String = violation.reason.escapedForXML() 33 | let identifier: String = violation.ruleDescription.identifier 34 | let source: String = "swiftlint.rules.\(identifier)".escapedForXML() 35 | return [ 36 | "\t\t\n" 41 | ].joined() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/EmojiReporter.swift: -------------------------------------------------------------------------------- 1 | public struct EmojiReporter: Reporter { 2 | public static let identifier = "emoji" 3 | public static let isRealtime = false 4 | 5 | public var description: String { 6 | return "Reports violations in the format that's both fun and easy to read." 7 | } 8 | 9 | public static func generateReport(_ violations: [StyleViolation]) -> String { 10 | return violations 11 | .group(by: { $0.location.file ?? "Other" }) 12 | .sorted(by: { $0.key < $1.key }) 13 | .map(report).joined(separator: "\n") 14 | } 15 | 16 | private static func report(for file: String, with violations: [StyleViolation]) -> String { 17 | let lines = [file] + violations.sorted { lhs, rhs in 18 | guard lhs.severity == rhs.severity else { 19 | return lhs.severity > rhs.severity 20 | } 21 | return lhs.location > rhs.location 22 | }.map { violation in 23 | let emoji = (violation.severity == .error) ? "⛔️" : "⚠️" 24 | let lineString: String 25 | if let line = violation.location.line { 26 | lineString = "Line \(line): " 27 | } else { 28 | lineString = "" 29 | } 30 | return "\(emoji) \(lineString)\(violation.reason)" 31 | } 32 | return lines.joined(separator: "\n") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/JSONReporter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct JSONReporter: Reporter { 5 | public static let identifier = "json" 6 | public static let isRealtime = false 7 | 8 | public var description: String { 9 | return "Reports violations as a JSON array." 10 | } 11 | 12 | public static func generateReport(_ violations: [StyleViolation]) -> String { 13 | return toJSON(violations.map(dictionary(for:))) 14 | } 15 | 16 | fileprivate static func dictionary(for violation: StyleViolation) -> [String: Any] { 17 | return [ 18 | "file": violation.location.file ?? NSNull() as Any, 19 | "line": violation.location.line ?? NSNull() as Any, 20 | "character": violation.location.character ?? NSNull() as Any, 21 | "severity": violation.severity.rawValue.capitalized, 22 | "type": violation.ruleDescription.name, 23 | "rule_id": violation.ruleDescription.identifier, 24 | "reason": violation.reason 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/JUnitReporter.swift: -------------------------------------------------------------------------------- 1 | public struct JUnitReporter: Reporter { 2 | public static let identifier = "junit" 3 | public static let isRealtime = false 4 | 5 | public var description: String { 6 | return "Reports violations as JUnit XML." 7 | } 8 | 9 | public static func generateReport(_ violations: [StyleViolation]) -> String { 10 | return "\n" + 11 | violations.map({ violation -> String in 12 | let fileName = (violation.location.file ?? "").escapedForXML() 13 | let severity = violation.severity.rawValue + ":\n" 14 | let message = severity + "Line:" + String(violation.location.line ?? 0) + " " 15 | let reason = violation.reason.escapedForXML() 16 | return [ 17 | "\n\t\n", 18 | "" + message + "", 19 | "\t" 20 | ].joined() 21 | }).joined() + "\n" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/MarkdownReporter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private extension String { 4 | func escapedForMarkdown() -> String { 5 | let escapedString = replacingOccurrences(of: "\"", with: "\"\"") 6 | if escapedString.contains("|") || escapedString.contains("\n") { 7 | return "\"\(escapedString)\"" 8 | } 9 | return escapedString 10 | } 11 | } 12 | 13 | public struct MarkdownReporter: Reporter { 14 | public static let identifier = "markdown" 15 | public static let isRealtime = false 16 | 17 | public var description: String { 18 | return "Reports violations as markdown formated (with tables)" 19 | } 20 | 21 | public static func generateReport(_ violations: [StyleViolation]) -> String { 22 | let keys = [ 23 | "file", 24 | "line", 25 | "severity", 26 | "reason", 27 | "rule_id" 28 | ].joined(separator: " | ") 29 | 30 | let rows = [keys, "--- | --- | --- | --- | ---"] + violations.map(markdownRow(for:)) 31 | return rows.joined(separator: "\n") 32 | } 33 | 34 | fileprivate static func markdownRow(for violation: StyleViolation) -> String { 35 | return [ 36 | violation.location.file?.escapedForMarkdown() ?? "", 37 | violation.location.line?.description ?? "", 38 | severity(for: violation.severity), 39 | violation.ruleDescription.name.escapedForMarkdown() + ": " + violation.reason.escapedForMarkdown(), 40 | violation.ruleDescription.identifier 41 | ].joined(separator: " | ") 42 | } 43 | 44 | fileprivate static func severity(for severity: ViolationSeverity) -> String { 45 | switch severity { 46 | case .error: 47 | return ":stop\\_sign:" 48 | case .warning: 49 | return ":warning:" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/SonarQubeReporter.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct SonarQubeReporter: Reporter { 4 | public static let identifier = "sonarqube" 5 | public static let isRealtime = false 6 | 7 | public var description: String { 8 | return "Reports violations in SonarQube import format." 9 | } 10 | 11 | public static func generateReport(_ violations: [StyleViolation]) -> String { 12 | return toJSON(["issues": violations.map(dictionary(for:))]) 13 | } 14 | 15 | // refer to https://docs.sonarqube.org/display/SONAR/Generic+Issue+Data 16 | private static func dictionary(for violation: StyleViolation) -> [String: Any] { 17 | return [ 18 | "engineId": "SwiftLint", 19 | "ruleId": violation.ruleDescription.identifier, 20 | "primaryLocation": [ 21 | "message": violation.reason, 22 | "filePath": violation.location.relativeFile ?? "", 23 | "textRange": [ 24 | "startLine": violation.location.line ?? 1 25 | ] 26 | ], 27 | "type": "CODE_SMELL", 28 | "severity": violation.severity == .error ? "MAJOR": "MINOR" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Reporters/XcodeReporter.swift: -------------------------------------------------------------------------------- 1 | public struct XcodeReporter: Reporter { 2 | public static let identifier = "xcode" 3 | public static let isRealtime = true 4 | 5 | public var description: String { 6 | return "Reports violations in the format Xcode uses to display in the IDE. (default)" 7 | } 8 | 9 | public static func generateReport(_ violations: [StyleViolation]) -> String { 10 | return violations.map(generateForSingleViolation).joined(separator: "\n") 11 | } 12 | 13 | internal static func generateForSingleViolation(_ violation: StyleViolation) -> String { 14 | // {full_path_to_file}{:line}{:character}: {error,warning}: {content} 15 | return [ 16 | "\(violation.location): ", 17 | "\(violation.severity.rawValue): ", 18 | "\(violation.ruleDescription.name) Violation: ", 19 | violation.reason, 20 | " (\(violation.ruleDescription.identifier))" 21 | ].joined() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/DiscouragedObjectLiteralRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct DiscouragedObjectLiteralRule: ASTRule, OptInRule, ConfigurationProviderRule { 4 | public var configuration = ObjectLiteralConfiguration() 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "discouraged_object_literal", 10 | name: "Discouraged Object Literal", 11 | description: "Prefer initializers over object literals.", 12 | kind: .idiomatic, 13 | nonTriggeringExamples: [ 14 | "let image = UIImage(named: aVariable)", 15 | "let image = UIImage(named: \"interpolated \\(variable)\")", 16 | "let color = UIColor(red: value, green: value, blue: value, alpha: 1)", 17 | "let image = NSImage(named: aVariable)", 18 | "let image = NSImage(named: \"interpolated \\(variable)\")", 19 | "let color = NSColor(red: value, green: value, blue: value, alpha: 1)" 20 | ], 21 | triggeringExamples: [ 22 | "let image = ↓#imageLiteral(resourceName: \"image.jpg\")", 23 | "let color = ↓#colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)" 24 | ] 25 | ) 26 | 27 | public func validate(file: File, 28 | kind: SwiftExpressionKind, 29 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 30 | guard let offset = dictionary.offset, kind == .objectLiteral else { return [] } 31 | 32 | if !configuration.imageLiteral && dictionary.name == "imageLiteral" { 33 | return [] 34 | } 35 | 36 | if !configuration.colorLiteral && dictionary.name == "colorLiteral" { 37 | return [] 38 | } 39 | 40 | return [StyleViolation(ruleDescription: type(of: self).description, 41 | severity: configuration.severityConfiguration.severity, 42 | location: Location(file: file, byteOffset: offset))] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/DiscouragedOptionalBooleanRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct DiscouragedOptionalBooleanRule: OptInRule, ConfigurationProviderRule, AutomaticTestableRule { 5 | public var configuration = SeverityConfiguration(.warning) 6 | 7 | public init() {} 8 | 9 | public static let description = RuleDescription( 10 | identifier: "discouraged_optional_boolean", 11 | name: "Discouraged Optional Boolean", 12 | description: "Prefer non-optional booleans over optional booleans.", 13 | kind: .idiomatic, 14 | nonTriggeringExamples: DiscouragedOptionalBooleanRuleExamples.nonTriggeringExamples, 15 | triggeringExamples: DiscouragedOptionalBooleanRuleExamples.triggeringExamples 16 | ) 17 | 18 | public func validate(file: File) -> [StyleViolation] { 19 | let booleanPattern = "Bool\\?" 20 | let optionalPattern = "Optional\\.some\\(\\s*(true|false)\\s*\\)" 21 | let pattern = "(" + [booleanPattern, optionalPattern].joined(separator: "|") + ")" 22 | let excludingKinds = SyntaxKind.commentAndStringKinds 23 | 24 | return file.match(pattern: pattern, excludingSyntaxKinds: excludingKinds).map { 25 | StyleViolation(ruleDescription: type(of: self).description, 26 | severity: configuration.severity, 27 | location: Location(file: file, characterOffset: $0.location)) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/DuplicateImportsRuleExamples.swift: -------------------------------------------------------------------------------- 1 | internal struct DuplicateImportsRuleExamples { 2 | static let nonTriggeringExamples: [String] = [ 3 | "import A\nimport B\nimport C", 4 | "import A.B\nimport A.C", 5 | """ 6 | #if DEBUG 7 | @testable import KsApi 8 | #else 9 | import KsApi 10 | #endif 11 | """, 12 | "import A // module\nimport B // module" 13 | ] 14 | 15 | static let triggeringExamples: [String] = { 16 | var list: [String] = [ 17 | "import Foundation\nimport Dispatch\n↓import Foundation", 18 | "import Foundation\n↓import Foundation.NSString", 19 | "↓import Foundation.NSString\nimport Foundation", 20 | "↓import A.B.C\nimport A.B", 21 | "import A.B\n↓import A.B.C", 22 | """ 23 | import A 24 | #if DEBUG 25 | @testable import KsApi 26 | #else 27 | import KsApi 28 | #endif 29 | ↓import A 30 | """ 31 | ] 32 | 33 | list += DuplicateImportsRule.importKinds.map { importKind in 34 | return """ 35 | import A 36 | ↓import \(importKind) A.Foo 37 | """ 38 | } 39 | 40 | list += DuplicateImportsRule.importKinds.map { importKind in 41 | return """ 42 | import A 43 | ↓import \(importKind) A.B.Foo 44 | """ 45 | } 46 | 47 | list += DuplicateImportsRule.importKinds.map { importKind in 48 | return """ 49 | import A.B 50 | ↓import \(importKind) A.B.Foo 51 | """ 52 | } 53 | 54 | return list 55 | }() 56 | } 57 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/FallthroughRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct FallthroughRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "fallthrough", 10 | name: "Fallthrough", 11 | description: "Fallthrough should be avoided.", 12 | kind: .idiomatic, 13 | nonTriggeringExamples: [ 14 | """ 15 | switch foo { 16 | case .bar, .bar2, .bar3: 17 | something() 18 | } 19 | """ 20 | ], 21 | triggeringExamples: [ 22 | """ 23 | switch foo { 24 | case .bar: 25 | ↓fallthrough 26 | case .bar2: 27 | something() 28 | } 29 | """ 30 | ] 31 | ) 32 | 33 | public func validate(file: File) -> [StyleViolation] { 34 | return file.match(pattern: "fallthrough", with: [.keyword]).map { 35 | StyleViolation(ruleDescription: type(of: self).description, 36 | severity: configuration.severity, 37 | location: Location(file: file, characterOffset: $0.location)) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/ForceCastRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct ForceCastRule: ConfigurationProviderRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.error) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "force_cast", 10 | name: "Force Cast", 11 | description: "Force casts should be avoided.", 12 | kind: .idiomatic, 13 | nonTriggeringExamples: [ 14 | "NSNumber() as? Int\n" 15 | ], 16 | triggeringExamples: [ "NSNumber() ↓as! Int\n" ] 17 | ) 18 | 19 | public func validate(file: File) -> [StyleViolation] { 20 | return file.match(pattern: "as!", with: [.keyword]).map { 21 | StyleViolation(ruleDescription: type(of: self).description, 22 | severity: configuration.severity, 23 | location: Location(file: file, characterOffset: $0.location)) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/ForceTryRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct ForceTryRule: ConfigurationProviderRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.error) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "force_try", 10 | name: "Force Try", 11 | description: "Force tries should be avoided.", 12 | kind: .idiomatic, 13 | nonTriggeringExamples: [ 14 | """ 15 | func a() throws {} 16 | do { 17 | try a() 18 | } catch {} 19 | """ 20 | ], 21 | triggeringExamples: [ 22 | """ 23 | func a() throws {} 24 | ↓try! a() 25 | """ 26 | ] 27 | ) 28 | 29 | public func validate(file: File) -> [StyleViolation] { 30 | return file.match(pattern: "try!", with: [.keyword]).map { 31 | StyleViolation(ruleDescription: type(of: self).description, 32 | severity: configuration.severity, 33 | location: Location(file: file, characterOffset: $0.location)) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/IsDisjointRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct IsDisjointRule: ConfigurationProviderRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "is_disjoint", 10 | name: "Is Disjoint", 11 | description: "Prefer using `Set.isDisjoint(with:)` over `Set.intersection(_:).isEmpty`.", 12 | kind: .idiomatic, 13 | nonTriggeringExamples: [ 14 | "_ = Set(syntaxKinds).isDisjoint(with: commentAndStringKindsSet)", 15 | "let isObjc = !objcAttributes.isDisjoint(with: dictionary.enclosedSwiftAttributes)", 16 | "_ = Set(syntaxKinds).intersection(commentAndStringKindsSet)", 17 | "_ = !objcAttributes.intersection(dictionary.enclosedSwiftAttributes)" 18 | ], 19 | triggeringExamples: [ 20 | "_ = Set(syntaxKinds).↓intersection(commentAndStringKindsSet).isEmpty", 21 | "let isObjc = !objcAttributes.↓intersection(dictionary.enclosedSwiftAttributes).isEmpty" 22 | ] 23 | ) 24 | 25 | public func validate(file: File) -> [StyleViolation] { 26 | let pattern = "\\bintersection\\(\\S+\\)\\.isEmpty" 27 | let excludingKinds = SyntaxKind.commentAndStringKinds 28 | return file.match(pattern: pattern, excludingSyntaxKinds: excludingKinds).map { 29 | StyleViolation(ruleDescription: type(of: self).description, 30 | severity: configuration.severity, 31 | location: Location(file: file, characterOffset: $0.location)) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/LegacyConstantRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct LegacyConstantRule: CorrectableRule, ConfigurationProviderRule, AutomaticTestableRule { 5 | public var configuration = SeverityConfiguration(.warning) 6 | 7 | public init() {} 8 | 9 | public static let description = RuleDescription( 10 | identifier: "legacy_constant", 11 | name: "Legacy Constant", 12 | description: "Struct-scoped constants are preferred over legacy global constants.", 13 | kind: .idiomatic, 14 | nonTriggeringExamples: LegacyConstantRuleExamples.nonTriggeringExamples, 15 | triggeringExamples: LegacyConstantRuleExamples.triggeringExamples, 16 | corrections: LegacyConstantRuleExamples.corrections 17 | ) 18 | 19 | private static let legacyConstants: [String] = { 20 | return Array(LegacyConstantRule.legacyPatterns.keys) 21 | }() 22 | 23 | private static let legacyPatterns = LegacyConstantRuleExamples.patterns 24 | 25 | public func validate(file: File) -> [StyleViolation] { 26 | let pattern = "\\b" + LegacyConstantRule.legacyConstants.joined(separator: "|") 27 | 28 | return file.match(pattern: pattern, range: nil) 29 | .filter { Set($0.1).isSubset(of: [.identifier]) } 30 | .map { $0.0 } 31 | .map { 32 | StyleViolation(ruleDescription: type(of: self).description, 33 | severity: configuration.severity, 34 | location: Location(file: file, characterOffset: $0.location)) 35 | } 36 | } 37 | 38 | public func correct(file: File) -> [Correction] { 39 | var wordBoundPatterns: [String: String] = [:] 40 | LegacyConstantRule.legacyPatterns.forEach { key, value in 41 | wordBoundPatterns["\\b" + key] = value 42 | } 43 | 44 | return file.correct(legacyRule: self, patterns: wordBoundPatterns) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/LegacyConstantRuleExamples.swift: -------------------------------------------------------------------------------- 1 | internal struct LegacyConstantRuleExamples { 2 | static let nonTriggeringExamples = [ 3 | "CGRect.infinite", 4 | "CGPoint.zero", 5 | "CGRect.zero", 6 | "CGSize.zero", 7 | "NSPoint.zero", 8 | "NSRect.zero", 9 | "NSSize.zero", 10 | "CGRect.null", 11 | "CGFloat.pi", 12 | "Float.pi" 13 | ] 14 | 15 | static let triggeringExamples = [ 16 | "↓CGRectInfinite", 17 | "↓CGPointZero", 18 | "↓CGRectZero", 19 | "↓CGSizeZero", 20 | "↓NSZeroPoint", 21 | "↓NSZeroRect", 22 | "↓NSZeroSize", 23 | "↓CGRectNull", 24 | "↓CGFloat(M_PI)", 25 | "↓Float(M_PI)" 26 | ] 27 | 28 | static let corrections = [ 29 | "↓CGRectInfinite": "CGRect.infinite", 30 | "↓CGPointZero": "CGPoint.zero", 31 | "↓CGRectZero": "CGRect.zero", 32 | "↓CGSizeZero": "CGSize.zero", 33 | "↓NSZeroPoint": "NSPoint.zero", 34 | "↓NSZeroRect": "NSRect.zero", 35 | "↓NSZeroSize": "NSSize.zero", 36 | "↓CGRectNull": "CGRect.null", 37 | "↓CGRectInfinite\n↓CGRectNull\n": "CGRect.infinite\nCGRect.null\n", 38 | "↓CGFloat(M_PI)": "CGFloat.pi", 39 | "↓Float(M_PI)": "Float.pi", 40 | "↓CGFloat(M_PI)\n↓Float(M_PI)\n": "CGFloat.pi\nFloat.pi\n" 41 | ] 42 | 43 | static let patterns = [ 44 | "CGRectInfinite": "CGRect.infinite", 45 | "CGPointZero": "CGPoint.zero", 46 | "CGRectZero": "CGRect.zero", 47 | "CGSizeZero": "CGSize.zero", 48 | "NSZeroPoint": "NSPoint.zero", 49 | "NSZeroRect": "NSRect.zero", 50 | "NSZeroSize": "NSSize.zero", 51 | "CGRectNull": "CGRect.null", 52 | "CGFloat\\(M_PI\\)": "CGFloat.pi", 53 | "Float\\(M_PI\\)": "Float.pi" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/LegacyRandomRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct LegacyRandomRule: ASTRule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static var description = RuleDescription( 9 | identifier: "legacy_random", 10 | name: "Legacy Random", 11 | description: "Prefer using `type.random(in:)` over legacy functions.", 12 | kind: .idiomatic, 13 | minSwiftVersion: .fourDotTwo, 14 | nonTriggeringExamples: [ 15 | "Int.random(in: 0..<10)\n", 16 | "Double.random(in: 8.6...111.34)\n", 17 | "Float.random(in: 0 ..< 1)\n" 18 | ], 19 | triggeringExamples: [ 20 | "↓arc4random(10)\n", 21 | "↓arc4random_uniform(83)\n", 22 | "↓drand48(52)\n" 23 | ] 24 | ) 25 | 26 | private let legacyRandomFunctions: Set = [ 27 | "arc4random", 28 | "arc4random_uniform", 29 | "drand48" 30 | ] 31 | 32 | public func validate( 33 | file: File, 34 | kind: SwiftExpressionKind, 35 | dictionary: [String: SourceKitRepresentable] 36 | ) -> [StyleViolation] { 37 | guard containsViolation(kind: kind, dictionary: dictionary), 38 | let offset = dictionary.offset else { 39 | return [] 40 | } 41 | 42 | let location = Location(file: file, byteOffset: offset) 43 | return [ 44 | StyleViolation(ruleDescription: type(of: self).description, 45 | severity: configuration.severity, 46 | location: location) 47 | ] 48 | } 49 | 50 | private func containsViolation(kind: SwiftExpressionKind, dictionary: [String: SourceKitRepresentable]) -> Bool { 51 | guard kind == .call, 52 | let name = dictionary.name, 53 | legacyRandomFunctions.contains(name) else { 54 | return false 55 | } 56 | 57 | return true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/NoExtensionAccessModifierRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct NoExtensionAccessModifierRule: ASTRule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.error) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "no_extension_access_modifier", 10 | name: "No Extension Access Modifier", 11 | description: "Prefer not to use extension access modifiers", 12 | kind: .idiomatic, 13 | nonTriggeringExamples: [ 14 | "extension String {}", 15 | "\n\n extension String {}" 16 | ], 17 | triggeringExamples: [ 18 | "↓private extension String {}", 19 | "↓public \n extension String {}", 20 | "↓open extension String {}", 21 | "↓internal extension String {}", 22 | "↓fileprivate extension String {}" 23 | ] 24 | ) 25 | 26 | public func validate(file: File, kind: SwiftDeclarationKind, 27 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 28 | guard kind == .extension, let offset = dictionary.offset else { 29 | return [] 30 | } 31 | 32 | let syntaxTokens = file.syntaxMap.tokens 33 | let parts = syntaxTokens.prefix(while: { offset > $0.offset }) 34 | guard let aclToken = parts.last, file.isACL(token: aclToken) else { 35 | return [] 36 | } 37 | 38 | return [ 39 | StyleViolation(ruleDescription: type(of: self).description, 40 | severity: configuration.severity, 41 | location: Location(file: file, byteOffset: aclToken.offset)) 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/RedundantNilCoalescingRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct RedundantNilCoalescingRule: OptInRule, SubstitutionCorrectableRule, ConfigurationProviderRule, 5 | AutomaticTestableRule { 6 | public var configuration = SeverityConfiguration(.warning) 7 | 8 | public init() {} 9 | 10 | public static let description = RuleDescription( 11 | identifier: "redundant_nil_coalescing", 12 | name: "Redundant Nil Coalescing", 13 | description: "nil coalescing operator is only evaluated if the lhs is nil" + 14 | ", coalescing operator with nil as rhs is redundant", 15 | kind: .idiomatic, 16 | nonTriggeringExamples: [ 17 | "var myVar: Int?; myVar ?? 0\n" 18 | ], 19 | triggeringExamples: [ 20 | "var myVar: Int? = nil; myVar↓ ?? nil\n", 21 | "var myVar: Int? = nil; myVar↓??nil\n" 22 | ], 23 | corrections: [ 24 | "var myVar: Int? = nil; let foo = myVar↓ ?? nil\n": "var myVar: Int? = nil; let foo = myVar\n", 25 | "var myVar: Int? = nil; let foo = myVar↓??nil\n": "var myVar: Int? = nil; let foo = myVar\n" 26 | ] 27 | ) 28 | 29 | public func validate(file: File) -> [StyleViolation] { 30 | return violationRanges(in: file).map { 31 | StyleViolation(ruleDescription: type(of: self).description, 32 | severity: configuration.severity, 33 | location: Location(file: file, characterOffset: $0.location)) 34 | } 35 | } 36 | 37 | public func substitution(for violationRange: NSRange, in file: File) -> (NSRange, String) { 38 | return (violationRange, "") 39 | } 40 | 41 | public func violationRanges(in file: File) -> [NSRange] { 42 | return file.match(pattern: "\\s?\\?{2}\\s*nil\\b", with: [.keyword]) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Idiomatic/ToggleBoolRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct ToggleBoolRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static var description = RuleDescription( 9 | identifier: "toggle_bool", 10 | name: "Toggle Bool", 11 | description: "Prefer `someBool.toggle()` over `someBool = !someBool`.", 12 | kind: .idiomatic, 13 | minSwiftVersion: .fourDotTwo, 14 | nonTriggeringExamples: [ 15 | "isHidden.toggle()\n", 16 | "view.clipsToBounds.toggle()\n", 17 | "func foo() { abc.toggle() }", 18 | "view.clipsToBounds = !clipsToBounds\n", 19 | "disconnected = !connected\n" 20 | ], 21 | triggeringExamples: [ 22 | "↓isHidden = !isHidden\n", 23 | "↓view.clipsToBounds = !view.clipsToBounds\n", 24 | "func foo() { ↓abc = !abc }" 25 | ] 26 | ) 27 | 28 | public func validate(file: File) -> [StyleViolation] { 29 | let pattern = "(? [StyleViolation] { 32 | return violationRanges(in: file).map { 33 | StyleViolation(ruleDescription: type(of: self).description, 34 | severity: configuration.severity, 35 | location: Location(file: file, characterOffset: $0.location)) 36 | } 37 | } 38 | 39 | public func violationRanges(in file: File) -> [NSRange] { 40 | return file.match(pattern: "(;+([^\\S\\n]?)*)+;?$", 41 | excludingSyntaxKinds: SyntaxKind.commentAndStringKinds) 42 | } 43 | 44 | public func substitution(for violationRange: NSRange, in file: File) -> (NSRange, String) { 45 | return (violationRange, "") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Lint/DiscouragedDirectInitRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct DiscouragedDirectInitRule: ASTRule, ConfigurationProviderRule { 4 | public var configuration = DiscouragedDirectInitConfiguration() 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "discouraged_direct_init", 10 | name: "Discouraged Direct Initialization", 11 | description: "Discouraged direct initialization of types that can be harmful.", 12 | kind: .lint, 13 | nonTriggeringExamples: [ 14 | "let foo = UIDevice.current", 15 | "let foo = Bundle.main", 16 | "let foo = Bundle(path: \"bar\")", 17 | "let foo = Bundle(identifier: \"bar\")", 18 | "let foo = Bundle.init(path: \"bar\")", 19 | "let foo = Bundle.init(identifier: \"bar\")" 20 | ], 21 | triggeringExamples: [ 22 | "↓UIDevice()", 23 | "↓Bundle()", 24 | "let foo = ↓UIDevice()", 25 | "let foo = ↓Bundle()", 26 | "let foo = bar(bundle: ↓Bundle(), device: ↓UIDevice())", 27 | "↓UIDevice.init()", 28 | "↓Bundle.init()", 29 | "let foo = ↓UIDevice.init()", 30 | "let foo = ↓Bundle.init()", 31 | "let foo = bar(bundle: ↓Bundle.init(), device: ↓UIDevice.init())" 32 | ] 33 | ) 34 | 35 | public func validate(file: File, 36 | kind: SwiftExpressionKind, 37 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 38 | guard 39 | kind == .call, 40 | let offset = dictionary.nameOffset, 41 | let name = dictionary.name, 42 | dictionary.bodyLength == 0, 43 | configuration.discouragedInits.contains(name) 44 | else { 45 | return [] 46 | } 47 | 48 | return [StyleViolation(ruleDescription: type(of: self).description, 49 | severity: configuration.severity, 50 | location: Location(file: file, byteOffset: offset))] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Lint/NSLocalizedStringKeyRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct NSLocalizedStringKeyRule: ASTRule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule { 5 | public var configuration = SeverityConfiguration(.warning) 6 | 7 | public init() {} 8 | 9 | public static let description = RuleDescription( 10 | identifier: "nslocalizedstring_key", 11 | name: "NSLocalizedString Key", 12 | description: "Static strings should be used as key in NSLocalizedString in order to genstrings work.", 13 | kind: .lint, 14 | nonTriggeringExamples: [ 15 | "NSLocalizedString(\"key\", comment: nil)", 16 | "NSLocalizedString(\"key\" + \"2\", comment: nil)" 17 | ], 18 | triggeringExamples: [ 19 | "NSLocalizedString(↓method(), comment: nil)", 20 | "NSLocalizedString(↓\"key_\\(param)\", comment: nil)" 21 | ] 22 | ) 23 | 24 | public func validate(file: File, 25 | kind: SwiftExpressionKind, 26 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 27 | guard kind == .call, 28 | dictionary.name == "NSLocalizedString", 29 | let firstArgument = dictionary.enclosedArguments.first, 30 | firstArgument.name == nil, 31 | let offset = firstArgument.offset, 32 | let length = firstArgument.length, 33 | case let kinds = file.syntaxMap.kinds(inByteRange: NSRange(location: offset, length: length)), 34 | !kinds.allSatisfy({ $0 == .string }) else { 35 | return [] 36 | } 37 | 38 | return [ 39 | StyleViolation(ruleDescription: type(of: self).description, 40 | severity: configuration.severity, 41 | location: Location(file: file, byteOffset: offset)) 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Lint/NotificationCenterDetachmentRuleExamples.swift: -------------------------------------------------------------------------------- 1 | internal struct NotificationCenterDetachmentRuleExamples { 2 | static let nonTriggeringExamples = [ 3 | "class Foo { \n" + 4 | " deinit {\n" + 5 | " NotificationCenter.default.removeObserver(self)\n" + 6 | " }\n" + 7 | "}\n", 8 | 9 | "class Foo { \n" + 10 | " func bar() {\n" + 11 | " NotificationCenter.default.removeObserver(otherObject)\n" + 12 | " }\n" + 13 | "}\n" 14 | ] 15 | 16 | static let triggeringExamples = [ 17 | "class Foo { \n" + 18 | " func bar() {\n" + 19 | " ↓NotificationCenter.default.removeObserver(self)\n" + 20 | " }\n" + 21 | "}\n" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Lint/ProhibitedInterfaceBuilderRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct ProhibitedInterfaceBuilderRule: ConfigurationProviderRule, ASTRule, OptInRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "prohibited_interface_builder", 10 | name: "Prohibited Interface Builder", 11 | description: "Creating views using Interface Builder should be avoided.", 12 | kind: .lint, 13 | nonTriggeringExamples: [ 14 | "var label: UILabel!", 15 | "@objc func buttonTapped(_ sender: UIButton) {}" 16 | ].map(wrapExample), 17 | triggeringExamples: [ 18 | "@IBOutlet ↓var label: UILabel!", 19 | "@IBAction ↓func buttonTapped(_ sender: UIButton) {}" 20 | ].map(wrapExample) 21 | ) 22 | 23 | public func validate(file: File, kind: SwiftDeclarationKind, 24 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 25 | guard let offset = dictionary.offset else { 26 | return [] 27 | } 28 | 29 | func makeViolation() -> StyleViolation { 30 | return StyleViolation(ruleDescription: type(of: self).description, 31 | severity: configuration.severity, 32 | location: Location(file: file, byteOffset: offset)) 33 | } 34 | 35 | if kind == .varInstance && dictionary.enclosedSwiftAttributes.contains(.iboutlet) { 36 | return [makeViolation()] 37 | } 38 | 39 | if kind == .functionMethodInstance && dictionary.enclosedSwiftAttributes.contains(.ibaction) { 40 | return [makeViolation()] 41 | } 42 | 43 | return [] 44 | } 45 | } 46 | 47 | private func wrapExample(_ text: String) -> String { 48 | return """ 49 | class ViewController: UIViewController { 50 | \(text) 51 | } 52 | """ 53 | } 54 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Lint/StrongIBOutletRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct StrongIBOutletRule: ConfigurationProviderRule, ASTRule, OptInRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "strong_iboutlet", 10 | name: "Strong IBOutlet", 11 | description: "@IBOutlets shouldn't be declared as weak.", 12 | kind: .lint, 13 | nonTriggeringExamples: [ 14 | "@IBOutlet var label: UILabel?", 15 | "weak var label: UILabel?" 16 | ].map(wrapExample), 17 | triggeringExamples: [ 18 | "@IBOutlet weak ↓var label: UILabel?", 19 | "@IBOutlet unowned ↓var label: UILabel!", 20 | "@IBOutlet weak ↓var textField: UITextField?" 21 | ].map(wrapExample) 22 | ) 23 | 24 | public func validate(file: File, kind: SwiftDeclarationKind, 25 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 26 | guard kind == .varInstance, 27 | case let attributes = dictionary.enclosedSwiftAttributes, 28 | attributes.contains(.iboutlet), 29 | attributes.contains(.weak), 30 | let offset = dictionary.offset else { 31 | return [] 32 | } 33 | 34 | return [ 35 | StyleViolation(ruleDescription: type(of: self).description, 36 | severity: configuration.severity, 37 | location: Location(file: file, byteOffset: offset)) 38 | ] 39 | } 40 | } 41 | 42 | private func wrapExample(_ text: String) -> String { 43 | return """ 44 | class ViewController: UIViewController { 45 | \(text) 46 | } 47 | """ 48 | } 49 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Lint/SuperfluousDisableCommandRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct SuperfluousDisableCommandRule: ConfigurationProviderRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "superfluous_disable_command", 10 | name: "Superfluous Disable Command", 11 | description: "SwiftLint 'disable' commands are superfluous when the disabled rule would not have " + 12 | "triggered a violation in the disabled region.", 13 | kind: .lint 14 | ) 15 | 16 | public func validate(file: File) -> [StyleViolation] { 17 | // This rule is implemented in Linter.swift 18 | return [] 19 | } 20 | 21 | public func reason(for rule: Rule.Type) -> String { 22 | return self.reason(for: rule.description.identifier) 23 | } 24 | 25 | public func reason(for rule: String) -> String { 26 | return "SwiftLint rule '\(rule)' did not trigger a violation " + 27 | "in the disabled region. Please remove the disable command." 28 | } 29 | 30 | public func reason(forNonExistentRule rule: String) -> String { 31 | return "'\(rule)' is not a valid SwiftLint rule. Please remove it from the disable command." 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Metrics/FileLengthRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct FileLengthRule: ConfigurationProviderRule { 4 | public var configuration = FileLengthRuleConfiguration(warning: 400, error: 1000) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "file_length", 10 | name: "File Line Length", 11 | description: "Files should not span too many lines.", 12 | kind: .metrics, 13 | nonTriggeringExamples: [ 14 | repeatElement("print(\"swiftlint\")\n", count: 400).joined() 15 | ], 16 | triggeringExamples: [ 17 | repeatElement("print(\"swiftlint\")\n", count: 401).joined(), 18 | (repeatElement("print(\"swiftlint\")\n", count: 400) + ["//\n"]).joined() 19 | ] 20 | ) 21 | 22 | public func validate(file: File) -> [StyleViolation] { 23 | func lineCountWithoutComments() -> Int { 24 | let commentKinds = SyntaxKind.commentKinds 25 | let lineCount = file.syntaxKindsByLines.filter { kinds in 26 | return !Set(kinds).isSubset(of: commentKinds) 27 | }.count 28 | return lineCount 29 | } 30 | 31 | var lineCount = file.lines.count 32 | let hasViolation = configuration.severityConfiguration.params.contains { 33 | $0.value < lineCount 34 | } 35 | 36 | if hasViolation && configuration.ignoreCommentOnlyLines { 37 | lineCount = lineCountWithoutComments() 38 | } 39 | 40 | for parameter in configuration.severityConfiguration.params where lineCount > parameter.value { 41 | let reason = "File should contain \(configuration.severityConfiguration.warning) lines or less: " + 42 | "currently contains \(lineCount)" 43 | return [StyleViolation(ruleDescription: type(of: self).description, 44 | severity: parameter.severity, 45 | location: Location(file: file.path, line: lineCount), 46 | reason: reason)] 47 | } 48 | 49 | return [] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Metrics/FunctionBodyLengthRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct FunctionBodyLengthRule: ASTRule, ConfigurationProviderRule { 4 | public var configuration = SeverityLevelsConfiguration(warning: 40, error: 100) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "function_body_length", 10 | name: "Function Body Length", 11 | description: "Functions bodies should not span too many lines.", 12 | kind: .metrics 13 | ) 14 | 15 | public func validate(file: File, kind: SwiftDeclarationKind, 16 | dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { 17 | guard SwiftDeclarationKind.functionKinds.contains(kind), 18 | let offset = dictionary.offset, 19 | let bodyOffset = dictionary.bodyOffset, 20 | let bodyLength = dictionary.bodyLength, 21 | case let contentsNSString = file.contents.bridge(), 22 | let startLine = contentsNSString.lineAndCharacter(forByteOffset: bodyOffset)?.line, 23 | let endLine = contentsNSString.lineAndCharacter(forByteOffset: bodyOffset + bodyLength)?.line 24 | else { 25 | return [] 26 | } 27 | for parameter in configuration.params { 28 | let (exceeds, lineCount) = file.exceedsLineCountExcludingCommentsAndWhitespace( 29 | startLine, endLine, parameter.value 30 | ) 31 | guard exceeds else { continue } 32 | return [StyleViolation(ruleDescription: type(of: self).description, 33 | severity: parameter.severity, 34 | location: Location(file: file, byteOffset: offset), 35 | reason: "Function body should span \(configuration.warning) lines or less " + 36 | "excluding comments and whitespace: currently spans \(lineCount) " + 37 | "lines")] 38 | } 39 | return [] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Performance/EmptyCountRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct EmptyCountRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.error) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "empty_count", 10 | name: "Empty Count", 11 | description: "Prefer checking `isEmpty` over comparing `count` to zero.", 12 | kind: .performance, 13 | nonTriggeringExamples: [ 14 | "var count = 0\n", 15 | "[Int]().isEmpty\n", 16 | "[Int]().count > 1\n", 17 | "[Int]().count == 1\n", 18 | "[Int]().count == 0xff\n", 19 | "[Int]().count == 0b01\n", 20 | "[Int]().count == 0o07\n", 21 | "discount == 0\n", 22 | "order.discount == 0\n" 23 | ], 24 | triggeringExamples: [ 25 | "[Int]().↓count == 0\n", 26 | "[Int]().↓count > 0\n", 27 | "[Int]().↓count != 0\n", 28 | "[Int]().↓count == 0x0\n", 29 | "[Int]().↓count == 0x00_00\n", 30 | "[Int]().↓count == 0b00\n", 31 | "[Int]().↓count == 0o00\n", 32 | "↓count == 0\n" 33 | ] 34 | ) 35 | 36 | public func validate(file: File) -> [StyleViolation] { 37 | let pattern = "\\bcount\\s*(==|!=|<|<=|>|>=)\\s*0(\\b|([box][0_]+\\b){1})" 38 | let excludingKinds = SyntaxKind.commentAndStringKinds 39 | return file.match(pattern: pattern, excludingSyntaxKinds: excludingKinds).map { 40 | StyleViolation(ruleDescription: type(of: self).description, 41 | severity: configuration.severity, 42 | location: Location(file: file, characterOffset: $0.location)) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Performance/EmptyStringRule.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct EmptyStringRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule { 4 | public var configuration = SeverityConfiguration(.warning) 5 | 6 | public init() {} 7 | 8 | public static let description = RuleDescription( 9 | identifier: "empty_string", 10 | name: "Empty String", 11 | description: "Prefer checking `isEmpty` over comparing `string` to an empty string literal.", 12 | kind: .performance, 13 | nonTriggeringExamples: [ 14 | "myString.isEmpty", 15 | "!myString.isEmpy" 16 | ], 17 | triggeringExamples: [ 18 | "myString↓ == \"\"", 19 | "myString↓ != \"\"" 20 | ] 21 | ) 22 | 23 | public func validate(file: File) -> [StyleViolation] { 24 | let pattern = "\\b\\s*(==|!=)\\s*\"\"" 25 | return file.match(pattern: pattern, with: [.string]).map { 26 | StyleViolation(ruleDescription: type(of: self).description, 27 | severity: configuration.severity, 28 | location: Location(file: file, characterOffset: $0.location)) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/AttributesConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct AttributesConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var alwaysOnSameLine = Set() 4 | private(set) var alwaysOnNewLine = Set() 5 | 6 | public var consoleDescription: String { 7 | return severityConfiguration.consoleDescription + 8 | ", always_on_same_line: \(alwaysOnSameLine.sorted())" + 9 | ", always_on_line_above: \(alwaysOnNewLine.sorted())" 10 | } 11 | 12 | public init(alwaysOnSameLine: [String] = ["@IBAction", "@NSManaged"], 13 | alwaysInNewLine: [String] = []) { 14 | self.alwaysOnSameLine = Set(alwaysOnSameLine) 15 | self.alwaysOnNewLine = Set(alwaysOnNewLine) 16 | } 17 | 18 | public mutating func apply(configuration: Any) throws { 19 | guard let configuration = configuration as? [String: Any] else { 20 | throw ConfigurationError.unknownConfiguration 21 | } 22 | 23 | if let alwaysOnSameLine = configuration["always_on_same_line"] as? [String] { 24 | self.alwaysOnSameLine = Set(alwaysOnSameLine) 25 | } 26 | 27 | if let alwaysOnNewLine = configuration["always_on_line_above"] as? [String] { 28 | self.alwaysOnNewLine = Set(alwaysOnNewLine) 29 | } 30 | 31 | if let severityString = configuration["severity"] as? String { 32 | try severityConfiguration.apply(configuration: severityString) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/CollectionAlignmentConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct CollectionAlignmentConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var alignColons = false 4 | 5 | init() {} 6 | 7 | public var consoleDescription: String { 8 | return severityConfiguration.consoleDescription + ", align_colons: \(alignColons)" 9 | } 10 | 11 | public mutating func apply(configuration: Any) throws { 12 | guard let configuration = configuration as? [String: Any] else { 13 | throw ConfigurationError.unknownConfiguration 14 | } 15 | 16 | alignColons = configuration["align_colons"] as? Bool ?? false 17 | 18 | if let severityString = configuration["severity"] as? String { 19 | try severityConfiguration.apply(configuration: severityString) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/ColonConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct ColonConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var flexibleRightSpacing = false 4 | private(set) var applyToDictionaries = true 5 | 6 | public var consoleDescription: String { 7 | return severityConfiguration.consoleDescription + 8 | ", flexible_right_spacing: \(flexibleRightSpacing)" + 9 | ", apply_to_dictionaries: \(applyToDictionaries)" 10 | } 11 | 12 | public mutating func apply(configuration: Any) throws { 13 | guard let configuration = configuration as? [String: Any] else { 14 | throw ConfigurationError.unknownConfiguration 15 | } 16 | 17 | flexibleRightSpacing = configuration["flexible_right_spacing"] as? Bool == true 18 | applyToDictionaries = configuration["apply_to_dictionaries"] as? Bool ?? true 19 | 20 | if let severityString = configuration["severity"] as? String { 21 | try severityConfiguration.apply(configuration: severityString) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/ConditionalReturnsOnNewlineConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct ConditionalReturnsOnNewlineConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var ifOnly = false 4 | 5 | public var consoleDescription: String { 6 | return [severityConfiguration.consoleDescription, "if_only: \(ifOnly)"].joined(separator: ", ") 7 | } 8 | 9 | public mutating func apply(configuration: Any) throws { 10 | guard let configuration = configuration as? [String: Any] else { 11 | throw ConfigurationError.unknownConfiguration 12 | } 13 | 14 | ifOnly = configuration["if_only"] as? Bool ?? false 15 | 16 | if let severityString = configuration["severity"] as? String { 17 | try severityConfiguration.apply(configuration: severityString) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/DiscouragedDirectInitConfiguration.swift: -------------------------------------------------------------------------------- 1 | private func toExplicitInitMethod(typeName: String) -> String { 2 | return "\(typeName).init" 3 | } 4 | 5 | public struct DiscouragedDirectInitConfiguration: RuleConfiguration, Equatable { 6 | public var severityConfiguration = SeverityConfiguration(.warning) 7 | 8 | public var consoleDescription: String { 9 | return severityConfiguration.consoleDescription + ", types: \(discouragedInits.sorted(by: <))" 10 | } 11 | 12 | public var severity: ViolationSeverity { 13 | return severityConfiguration.severity 14 | } 15 | 16 | private(set) public var discouragedInits: Set 17 | 18 | private let defaultDiscouragedInits = [ 19 | "Bundle", 20 | "UIDevice" 21 | ] 22 | 23 | init() { 24 | discouragedInits = Set(defaultDiscouragedInits + defaultDiscouragedInits.map(toExplicitInitMethod)) 25 | } 26 | 27 | // MARK: - RuleConfiguration 28 | 29 | public mutating func apply(configuration: Any) throws { 30 | guard let configuration = configuration as? [String: Any] else { 31 | throw ConfigurationError.unknownConfiguration 32 | } 33 | 34 | if let severityString = configuration["severity"] as? String { 35 | try severityConfiguration.apply(configuration: severityString) 36 | } 37 | 38 | if let types = [String].array(of: configuration["types"]) { 39 | discouragedInits = Set(types + types.map(toExplicitInitMethod)) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/FileNameConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct FileNameConfiguration: RuleConfiguration, Equatable { 2 | public var consoleDescription: String { 3 | return "(severity) \(severity.consoleDescription), " + 4 | "excluded: \(excluded.sorted())" 5 | } 6 | 7 | private(set) public var severity: SeverityConfiguration 8 | private(set) public var excluded: Set 9 | private(set) public var prefixPattern: String 10 | private(set) public var suffixPattern: String 11 | 12 | public init(severity: ViolationSeverity, excluded: [String] = [], 13 | prefixPattern: String = "", suffixPattern: String = "\\+.*") { 14 | self.severity = SeverityConfiguration(severity) 15 | self.excluded = Set(excluded) 16 | self.prefixPattern = prefixPattern 17 | self.suffixPattern = suffixPattern 18 | } 19 | 20 | public mutating func apply(configuration: Any) throws { 21 | guard let configurationDict = configuration as? [String: Any] else { 22 | throw ConfigurationError.unknownConfiguration 23 | } 24 | 25 | if let severityConfiguration = configurationDict["severity"] { 26 | try severity.apply(configuration: severityConfiguration) 27 | } 28 | if let excluded = [String].array(of: configurationDict["excluded"]) { 29 | self.excluded = Set(excluded) 30 | } 31 | if let prefixPattern = configurationDict["prefix_pattern"] as? String { 32 | self.prefixPattern = prefixPattern 33 | } 34 | if let suffixPattern = configurationDict["suffix_pattern"] as? String { 35 | self.suffixPattern = suffixPattern 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/FileTypesOrderConfiguration.swift: -------------------------------------------------------------------------------- 1 | enum FileType: String { 2 | case supportingType = "supporting_type" 3 | case mainType = "main_type" 4 | case `extension` = "extension" 5 | } 6 | 7 | public struct FileTypesOrderConfiguration: RuleConfiguration { 8 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 9 | private(set) var order: [[FileType]] = [ 10 | [.supportingType], 11 | [.mainType], 12 | [.extension] 13 | ] 14 | 15 | public var consoleDescription: String { 16 | return severityConfiguration.consoleDescription + 17 | ", order: \(String(describing: order))" 18 | } 19 | 20 | public mutating func apply(configuration: Any) throws { 21 | guard let configuration = configuration as? [String: Any] else { 22 | throw ConfigurationError.unknownConfiguration 23 | } 24 | 25 | var customOrder = [[FileType]]() 26 | if let custom = configuration["order"] as? [Any] { 27 | for entry in custom { 28 | if let singleEntry = entry as? String { 29 | if let fileType = FileType(rawValue: singleEntry) { 30 | customOrder.append([fileType]) 31 | } 32 | } else if let arrayEntry = entry as? [String] { 33 | let fileTypes = arrayEntry.compactMap { FileType(rawValue: $0) } 34 | customOrder.append(fileTypes) 35 | } 36 | } 37 | } 38 | 39 | if !customOrder.isEmpty { 40 | self.order = customOrder 41 | } 42 | } 43 | } 44 | 45 | extension FileTypesOrderConfiguration: Equatable { 46 | public static func == (lhs: FileTypesOrderConfiguration, rhs: FileTypesOrderConfiguration) -> Bool { 47 | guard lhs.order.count == rhs.order.count else { return false } 48 | 49 | for (leftIndex, leftOrderEntry) in lhs.order.enumerated() { 50 | guard rhs.order[leftIndex] == leftOrderEntry else { return false } 51 | } 52 | 53 | return lhs.severityConfiguration == rhs.severityConfiguration 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/ImplicitlyUnwrappedOptionalConfiguration.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable:next type_name 2 | public enum ImplicitlyUnwrappedOptionalModeConfiguration: String { 3 | case all = "all" 4 | case allExceptIBOutlets = "all_except_iboutlets" 5 | 6 | init(value: Any) throws { 7 | if let string = (value as? String)?.lowercased(), 8 | let value = ImplicitlyUnwrappedOptionalModeConfiguration(rawValue: string) { 9 | self = value 10 | } else { 11 | throw ConfigurationError.unknownConfiguration 12 | } 13 | } 14 | } 15 | 16 | public struct ImplicitlyUnwrappedOptionalConfiguration: RuleConfiguration, Equatable { 17 | private(set) var severity: SeverityConfiguration 18 | private(set) var mode: ImplicitlyUnwrappedOptionalModeConfiguration 19 | 20 | init(mode: ImplicitlyUnwrappedOptionalModeConfiguration, severity: SeverityConfiguration) { 21 | self.mode = mode 22 | self.severity = severity 23 | } 24 | 25 | public var consoleDescription: String { 26 | return severity.consoleDescription + 27 | ", mode: \(mode)" 28 | } 29 | 30 | public mutating func apply(configuration: Any) throws { 31 | guard let configuration = configuration as? [String: Any] else { 32 | throw ConfigurationError.unknownConfiguration 33 | } 34 | 35 | if let modeString = configuration["mode"] { 36 | try mode = ImplicitlyUnwrappedOptionalModeConfiguration(value: modeString) 37 | } 38 | 39 | if let severityString = configuration["severity"] as? String { 40 | try severity.apply(configuration: severityString) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/MissingDocsRuleConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct MissingDocsRuleConfiguration: RuleConfiguration, Equatable { 2 | private(set) var parameters = [RuleParameter]() 3 | 4 | public var consoleDescription: String { 5 | return parameters.group { $0.severity }.sorted { $0.key.rawValue < $1.key.rawValue }.map { 6 | "\($0.rawValue): \($1.map { $0.value.description }.sorted(by: <).joined(separator: ", "))" 7 | }.joined(separator: ", ") 8 | } 9 | 10 | public mutating func apply(configuration: Any) throws { 11 | guard let dict = configuration as? [String: Any] else { 12 | throw ConfigurationError.unknownConfiguration 13 | } 14 | let parameters = try dict.flatMap { (key: String, value: Any) -> [RuleParameter] in 15 | guard let severity = ViolationSeverity(rawValue: key) else { 16 | throw ConfigurationError.unknownConfiguration 17 | } 18 | if let array = [String].array(of: value) { 19 | return try array.map { 20 | guard let acl = AccessControlLevel(description: $0) else { 21 | throw ConfigurationError.unknownConfiguration 22 | } 23 | return RuleParameter(severity: severity, value: acl) 24 | } 25 | } else if let string = value as? String, let acl = AccessControlLevel(description: string) { 26 | return [RuleParameter(severity: severity, value: acl)] 27 | } 28 | throw ConfigurationError.unknownConfiguration 29 | } 30 | guard parameters.count == parameters.map({ $0.value }).unique.count else { 31 | throw ConfigurationError.unknownConfiguration 32 | } 33 | self.parameters = parameters 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/ModifierOrderConfiguration.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | 3 | public struct ModifierOrderConfiguration: RuleConfiguration, Equatable { 4 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 5 | private(set) var preferredModifierOrder = [SwiftDeclarationAttributeKind.ModifierGroup]() 6 | 7 | public var consoleDescription: String { 8 | return severityConfiguration.consoleDescription + ", preferred_modifier_order: \(preferredModifierOrder)" 9 | } 10 | 11 | public init(preferredModifierOrder: [SwiftDeclarationAttributeKind.ModifierGroup] = []) { 12 | self.preferredModifierOrder = preferredModifierOrder 13 | } 14 | 15 | public mutating func apply(configuration: Any) throws { 16 | guard let configuration = configuration as? [String: Any] else { 17 | throw ConfigurationError.unknownConfiguration 18 | } 19 | 20 | if let preferredModifierOrder = configuration["preferred_modifier_order"] as? [String] { 21 | self.preferredModifierOrder = try preferredModifierOrder.map { 22 | guard let modifierGroup = SwiftDeclarationAttributeKind.ModifierGroup(rawValue: $0), 23 | modifierGroup != .atPrefixed else { 24 | throw ConfigurationError.unknownConfiguration 25 | } 26 | 27 | return modifierGroup 28 | } 29 | } 30 | 31 | if let severityString = configuration["severity"] as? String { 32 | try severityConfiguration.apply(configuration: severityString) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/NestingConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct NestingConfiguration: RuleConfiguration, Equatable { 2 | public var consoleDescription: String { 3 | return "(type_level) \(typeLevel.shortConsoleDescription), " + 4 | "(statement_level) \(statementLevel.shortConsoleDescription)" 5 | } 6 | 7 | var typeLevel: SeverityLevelsConfiguration 8 | var statementLevel: SeverityLevelsConfiguration 9 | 10 | public init(typeLevelWarning: Int, 11 | typeLevelError: Int?, 12 | statementLevelWarning: Int, 13 | statementLevelError: Int?) { 14 | typeLevel = SeverityLevelsConfiguration(warning: typeLevelWarning, error: typeLevelError) 15 | statementLevel = SeverityLevelsConfiguration(warning: statementLevelWarning, error: statementLevelError) 16 | } 17 | 18 | public mutating func apply(configuration: Any) throws { 19 | guard let configurationDict = configuration as? [String: Any] else { 20 | throw ConfigurationError.unknownConfiguration 21 | } 22 | 23 | if let typeLevelConfiguration = configurationDict["type_level"] { 24 | try typeLevel.apply(configuration: typeLevelConfiguration) 25 | } 26 | if let statementLevelConfiguration = configurationDict["statement_level"] { 27 | try statementLevel.apply(configuration: statementLevelConfiguration) 28 | } 29 | } 30 | 31 | func severity(with config: SeverityLevelsConfiguration, for level: Int) -> ViolationSeverity? { 32 | if let error = config.error, level > error { 33 | return .error 34 | } else if level > config.warning { 35 | return .warning 36 | } 37 | return nil 38 | } 39 | 40 | func threshold(with config: SeverityLevelsConfiguration, for severity: ViolationSeverity) -> Int { 41 | switch severity { 42 | case .error: return config.error ?? config.warning 43 | case .warning: return config.warning 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/ObjectLiteralConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct ObjectLiteralConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var imageLiteral = true 4 | private(set) var colorLiteral = true 5 | 6 | public var consoleDescription: String { 7 | return severityConfiguration.consoleDescription 8 | + ", image_literal: \(imageLiteral)" 9 | + ", color_literal: \(colorLiteral)" 10 | } 11 | 12 | public mutating func apply(configuration: Any) throws { 13 | guard let configuration = configuration as? [String: Any] else { 14 | throw ConfigurationError.unknownConfiguration 15 | } 16 | 17 | imageLiteral = configuration["image_literal"] as? Bool ?? true 18 | colorLiteral = configuration["color_literal"] as? Bool ?? true 19 | 20 | if let severityString = configuration["severity"] as? String { 21 | try severityConfiguration.apply(configuration: severityString) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/PrefixedConstantRuleConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct PrefixedConstantRuleConfiguration: RuleConfiguration, Equatable { 2 | var severityConfiguration = SeverityConfiguration(.warning) 3 | var onlyPrivateMembers = false 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", only_private: \(onlyPrivateMembers)" 7 | } 8 | 9 | public init(onlyPrivateMembers: Bool) { 10 | self.onlyPrivateMembers = onlyPrivateMembers 11 | } 12 | 13 | public mutating func apply(configuration: Any) throws { 14 | guard let configuration = configuration as? [String: Any] else { 15 | throw ConfigurationError.unknownConfiguration 16 | } 17 | 18 | onlyPrivateMembers = (configuration["only_private"] as? Bool == true) 19 | 20 | if let severityString = configuration["severity"] as? String { 21 | try severityConfiguration.apply(configuration: severityString) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/PrivateOutletRuleConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct PrivateOutletRuleConfiguration: RuleConfiguration, Equatable { 2 | var severityConfiguration = SeverityConfiguration(.warning) 3 | var allowPrivateSet = false 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", allow_private_set: \(allowPrivateSet)" 7 | } 8 | 9 | public init(allowPrivateSet: Bool) { 10 | self.allowPrivateSet = allowPrivateSet 11 | } 12 | 13 | public mutating func apply(configuration: Any) throws { 14 | guard let configuration = configuration as? [String: Any] else { 15 | throw ConfigurationError.unknownConfiguration 16 | } 17 | 18 | allowPrivateSet = (configuration["allow_private_set"] as? Bool == true) 19 | 20 | if let severityString = configuration["severity"] as? String { 21 | try severityConfiguration.apply(configuration: severityString) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/PrivateOverFilePrivateRuleConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct PrivateOverFilePrivateRuleConfiguration: RuleConfiguration, Equatable { 2 | public var severityConfiguration = SeverityConfiguration(.warning) 3 | public var validateExtensions = false 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", validate_extensions: \(validateExtensions)" 7 | } 8 | 9 | // MARK: - RuleConfiguration 10 | 11 | public mutating func apply(configuration: Any) throws { 12 | guard let configuration = configuration as? [String: Any] else { 13 | throw ConfigurationError.unknownConfiguration 14 | } 15 | 16 | if let severityString = configuration["severity"] as? String { 17 | try severityConfiguration.apply(configuration: severityString) 18 | } 19 | 20 | validateExtensions = configuration["validate_extensions"] as? Bool ?? false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/ProhibitedSuperConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct ProhibitedSuperConfiguration: RuleConfiguration, Equatable { 2 | var severityConfiguration = SeverityConfiguration(.warning) 3 | var excluded = [String]() 4 | var included = ["*"] 5 | 6 | private(set) var resolvedMethodNames = [ 7 | // NSFileProviderExtension 8 | "providePlaceholder(at:completionHandler:)", 9 | // NSTextInput 10 | "doCommand(by:)", 11 | // NSView 12 | "updateLayer()", 13 | // UIViewController 14 | "loadView()" 15 | ] 16 | 17 | init() {} 18 | 19 | public var consoleDescription: String { 20 | return severityConfiguration.consoleDescription + 21 | ", excluded: [\(excluded)]" + 22 | ", included: [\(included)]" 23 | } 24 | 25 | public mutating func apply(configuration: Any) throws { 26 | guard let configuration = configuration as? [String: Any] else { 27 | throw ConfigurationError.unknownConfiguration 28 | } 29 | 30 | if let severityString = configuration["severity"] as? String { 31 | try severityConfiguration.apply(configuration: severityString) 32 | } 33 | 34 | if let excluded = [String].array(of: configuration["excluded"]) { 35 | self.excluded = excluded 36 | } 37 | 38 | if let included = [String].array(of: configuration["included"]) { 39 | self.included = included 40 | } 41 | 42 | resolvedMethodNames = calculateResolvedMethodNames() 43 | } 44 | 45 | public var severity: ViolationSeverity { 46 | return severityConfiguration.severity 47 | } 48 | 49 | private func calculateResolvedMethodNames() -> [String] { 50 | var names = [String]() 51 | if included.contains("*") && !excluded.contains("*") { 52 | names += resolvedMethodNames 53 | } 54 | names += included.filter { $0 != "*" } 55 | names = names.filter { !excluded.contains($0) } 56 | return names 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/SeverityConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct SeverityConfiguration: RuleConfiguration, Equatable { 2 | public var consoleDescription: String { 3 | return severity.rawValue 4 | } 5 | 6 | var severity: ViolationSeverity 7 | 8 | public init(_ severity: ViolationSeverity) { 9 | self.severity = severity 10 | } 11 | 12 | public mutating func apply(configuration: Any) throws { 13 | let configString = configuration as? String 14 | let configDict = configuration as? [String: Any] 15 | guard let severityString: String = configString ?? configDict?["severity"] as? String, 16 | let severity = severity(fromString: severityString) else { 17 | throw ConfigurationError.unknownConfiguration 18 | } 19 | self.severity = severity 20 | } 21 | 22 | private func severity(fromString string: String) -> ViolationSeverity? { 23 | return ViolationSeverity(rawValue: string.lowercased()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/SeverityLevelsConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct SeverityLevelsConfiguration: RuleConfiguration, Equatable { 2 | public var consoleDescription: String { 3 | let errorString: String 4 | if let errorValue = error { 5 | errorString = ", error: \(errorValue)" 6 | } else { 7 | errorString = "" 8 | } 9 | return "warning: \(warning)" + errorString 10 | } 11 | 12 | public var shortConsoleDescription: String { 13 | if let errorValue = error { 14 | return "w/e: \(warning)/\(errorValue)" 15 | } 16 | return "w: \(warning)" 17 | } 18 | 19 | var warning: Int 20 | var error: Int? 21 | 22 | var params: [RuleParameter] { 23 | if let error = error { 24 | return [RuleParameter(severity: .error, value: error), 25 | RuleParameter(severity: .warning, value: warning)] 26 | } 27 | return [RuleParameter(severity: .warning, value: warning)] 28 | } 29 | 30 | public mutating func apply(configuration: Any) throws { 31 | if let configurationArray = [Int].array(of: configuration), !configurationArray.isEmpty { 32 | warning = configurationArray[0] 33 | error = (configurationArray.count > 1) ? configurationArray[1] : nil 34 | } else if let configDict = configuration as? [String: Int?], 35 | !configDict.isEmpty, Set(configDict.keys).isSubset(of: ["warning", "error"]) { 36 | warning = (configDict["warning"] as? Int) ?? warning 37 | error = configDict["error"] as? Int 38 | } else { 39 | throw ConfigurationError.unknownConfiguration 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/StatementModeConfiguration.swift: -------------------------------------------------------------------------------- 1 | public enum StatementModeConfiguration: String { 2 | case `default` = "default" 3 | case uncuddledElse = "uncuddled_else" 4 | 5 | init(value: Any) throws { 6 | if let string = (value as? String)?.lowercased(), 7 | let value = StatementModeConfiguration(rawValue: string) { 8 | self = value 9 | } else { 10 | throw ConfigurationError.unknownConfiguration 11 | } 12 | } 13 | } 14 | 15 | public struct StatementConfiguration: RuleConfiguration, Equatable { 16 | public var consoleDescription: String { 17 | return "(statement_mode) \(statementMode.rawValue), " + 18 | "(severity) \(severity.consoleDescription)" 19 | } 20 | 21 | var statementMode: StatementModeConfiguration 22 | var severity: SeverityConfiguration 23 | 24 | public init(statementMode: StatementModeConfiguration, 25 | severity: SeverityConfiguration) { 26 | self.statementMode = statementMode 27 | self.severity = severity 28 | } 29 | 30 | public mutating func apply(configuration: Any) throws { 31 | guard let configurationDict = configuration as? [String: Any] else { 32 | throw ConfigurationError.unknownConfiguration 33 | } 34 | if let statementModeConfiguration = configurationDict["statement_mode"] { 35 | try statementMode = StatementModeConfiguration(value: statementModeConfiguration) 36 | } 37 | if let severityConfiguration = configurationDict["severity"] { 38 | try severity.apply(configuration: severityConfiguration) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/SwitchCaseAlignmentConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct SwitchCaseAlignmentConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var indentedCases = false 4 | 5 | init() {} 6 | 7 | public var consoleDescription: String { 8 | return severityConfiguration.consoleDescription + ", indented_cases: \(indentedCases)" 9 | } 10 | 11 | public mutating func apply(configuration: Any) throws { 12 | guard let configuration = configuration as? [String: Any] else { 13 | throw ConfigurationError.unknownConfiguration 14 | } 15 | 16 | indentedCases = configuration["indented_cases"] as? Bool ?? false 17 | 18 | if let severityString = configuration["severity"] as? String { 19 | try severityConfiguration.apply(configuration: severityString) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/TrailingClosureConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct TrailingClosureConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var onlySingleMutedParameter: Bool 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", only_single_muted_parameter: \(onlySingleMutedParameter)" 7 | } 8 | 9 | public init(onlySingleMutedParameter: Bool = false) { 10 | self.onlySingleMutedParameter = onlySingleMutedParameter 11 | } 12 | 13 | public mutating func apply(configuration: Any) throws { 14 | guard let configuration = configuration as? [String: Any] else { 15 | throw ConfigurationError.unknownConfiguration 16 | } 17 | 18 | onlySingleMutedParameter = (configuration["only_single_muted_parameter"] as? Bool == true) 19 | 20 | if let severityString = configuration["severity"] as? String { 21 | try severityConfiguration.apply(configuration: severityString) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/TrailingCommaConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct TrailingCommaConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var mandatoryComma: Bool 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", mandatory_comma: \(mandatoryComma)" 7 | } 8 | 9 | public init(mandatoryComma: Bool = false) { 10 | self.mandatoryComma = mandatoryComma 11 | } 12 | 13 | public mutating func apply(configuration: Any) throws { 14 | guard let configuration = configuration as? [String: Any] else { 15 | throw ConfigurationError.unknownConfiguration 16 | } 17 | 18 | mandatoryComma = (configuration["mandatory_comma"] as? Bool == true) 19 | 20 | if let severityString = configuration["severity"] as? String { 21 | try severityConfiguration.apply(configuration: severityString) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/TrailingWhitespaceConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct TrailingWhitespaceConfiguration: RuleConfiguration, Equatable { 2 | var severityConfiguration = SeverityConfiguration(.warning) 3 | var ignoresEmptyLines = false 4 | var ignoresComments = true 5 | 6 | public var consoleDescription: String { 7 | return severityConfiguration.consoleDescription + 8 | ", ignores_empty_lines: \(ignoresEmptyLines)" + 9 | ", ignores_comments: \(ignoresComments)" 10 | } 11 | 12 | public init(ignoresEmptyLines: Bool, ignoresComments: Bool) { 13 | self.ignoresEmptyLines = ignoresEmptyLines 14 | self.ignoresComments = ignoresComments 15 | } 16 | 17 | public mutating func apply(configuration: Any) throws { 18 | guard let configuration = configuration as? [String: Any] else { 19 | throw ConfigurationError.unknownConfiguration 20 | } 21 | 22 | ignoresEmptyLines = (configuration["ignores_empty_lines"] as? Bool == true) 23 | ignoresComments = (configuration["ignores_comments"] as? Bool == true) 24 | 25 | if let severityString = configuration["severity"] as? String { 26 | try severityConfiguration.apply(configuration: severityString) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/UnusedOptionalBindingConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct UnusedOptionalBindingConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var ignoreOptionalTry: Bool 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", ignore_optional_try: \(ignoreOptionalTry)" 7 | } 8 | 9 | public init(ignoreOptionalTry: Bool) { 10 | self.ignoreOptionalTry = ignoreOptionalTry 11 | } 12 | 13 | public mutating func apply(configuration: Any) throws { 14 | guard let configuration = configuration as? [String: Any] else { 15 | throw ConfigurationError.unknownConfiguration 16 | } 17 | 18 | if let ignoreOptionalTry = configuration["ignore_optional_try"] as? Bool { 19 | self.ignoreOptionalTry = ignoreOptionalTry 20 | } 21 | 22 | if let severityString = configuration["severity"] as? String { 23 | try severityConfiguration.apply(configuration: severityString) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/RuleConfigurations/VerticalWhitespaceConfiguration.swift: -------------------------------------------------------------------------------- 1 | public struct VerticalWhitespaceConfiguration: RuleConfiguration, Equatable { 2 | private(set) var severityConfiguration = SeverityConfiguration(.warning) 3 | private(set) var maxEmptyLines: Int 4 | 5 | public var consoleDescription: String { 6 | return severityConfiguration.consoleDescription + ", max_empty_lines: \(maxEmptyLines)" 7 | } 8 | 9 | public init(maxEmptyLines: Int) { 10 | self.maxEmptyLines = maxEmptyLines 11 | } 12 | 13 | public mutating func apply(configuration: Any) throws { 14 | guard let configuration = configuration as? [String: Any] else { 15 | throw ConfigurationError.unknownConfiguration 16 | } 17 | 18 | if let maxEmptyLines = configuration["max_empty_lines"] as? Int { 19 | self.maxEmptyLines = maxEmptyLines 20 | } 21 | 22 | if let severityString = configuration["severity"] as? String { 23 | try severityConfiguration.apply(configuration: severityString) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Style/ClosingBraceRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct ClosingBraceRule: SubstitutionCorrectableRule, ConfigurationProviderRule, AutomaticTestableRule { 5 | public var configuration = SeverityConfiguration(.warning) 6 | 7 | public init() {} 8 | 9 | public static let description = RuleDescription( 10 | identifier: "closing_brace", 11 | name: "Closing Brace Spacing", 12 | description: "Closing brace with closing parenthesis " + 13 | "should not have any whitespaces in the middle.", 14 | kind: .style, 15 | nonTriggeringExamples: [ 16 | "[].map({ })", 17 | "[].map(\n { }\n)" 18 | ], 19 | triggeringExamples: [ 20 | "[].map({ ↓} )", 21 | "[].map({ ↓}\t)" 22 | ], 23 | corrections: [ 24 | "[].map({ ↓} )\n": "[].map({ })\n" 25 | ] 26 | ) 27 | 28 | public func validate(file: File) -> [StyleViolation] { 29 | return violationRanges(in: file).map { 30 | StyleViolation(ruleDescription: type(of: self).description, 31 | severity: configuration.severity, 32 | location: Location(file: file, characterOffset: $0.location)) 33 | } 34 | } 35 | 36 | public func violationRanges(in file: File) -> [NSRange] { 37 | return file.match(pattern: "(\\}[ \\t]+\\))", excludingSyntaxKinds: SyntaxKind.commentAndStringKinds) 38 | } 39 | 40 | public func substitution(for violationRange: NSRange, in file: File) -> (NSRange, String) { 41 | return (violationRange, "})") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Style/IdentifierNameRuleExamples.swift: -------------------------------------------------------------------------------- 1 | internal struct IdentifierNameRuleExamples { 2 | static let nonTriggeringExamples = [ 3 | "let myLet = 0", 4 | "var myVar = 0", 5 | "private let _myLet = 0", 6 | "class Abc { static let MyLet = 0 }", 7 | "let URL: NSURL? = nil", 8 | "let XMLString: String? = nil", 9 | "override var i = 0", 10 | "enum Foo { case myEnum }", 11 | "func isOperator(name: String) -> Bool", 12 | "func typeForKind(_ kind: SwiftDeclarationKind) -> String", 13 | "func == (lhs: SyntaxToken, rhs: SyntaxToken) -> Bool", 14 | "override func IsOperator(name: String) -> Bool", 15 | "enum Foo { case `private` }", 16 | "enum Foo { case value(String) }" 17 | ] 18 | 19 | static let triggeringExamples = [ 20 | "↓let MyLet = 0", 21 | "↓let _myLet = 0", 22 | "private ↓let myLet_ = 0", 23 | "↓let myExtremelyVeryVeryVeryVeryVeryVeryLongLet = 0", 24 | "↓var myExtremelyVeryVeryVeryVeryVeryVeryLongVar = 0", 25 | "private ↓let _myExtremelyVeryVeryVeryVeryVeryVeryLongLet = 0", 26 | "↓let i = 0", 27 | "↓var id = 0", 28 | "private ↓let _i = 0", 29 | "↓func IsOperator(name: String) -> Bool", 30 | "enum Foo { case ↓MyEnum }" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Style/MultilineArgumentsRuleExamples.swift: -------------------------------------------------------------------------------- 1 | internal struct MultilineArgumentsRuleExamples { 2 | static let nonTriggeringExamples = [ 3 | "foo()", 4 | "foo(\n" + 5 | " \n" + 6 | ")", 7 | "foo { }", 8 | "foo {\n" + 9 | " \n" + 10 | "}", 11 | "foo(0)", 12 | "foo(0, 1)", 13 | "foo(0, 1) { }", 14 | "foo(0, param1: 1)", 15 | "foo(0, param1: 1) { }", 16 | "foo(param1: 1)", 17 | "foo(param1: 1) { }", 18 | "foo(param1: 1, param2: true) { }", 19 | "foo(param1: 1, param2: true, param3: [3]) { }", 20 | "foo(param1: 1, param2: true, param3: [3]) {\n" + 21 | " bar()\n" + 22 | "}", 23 | "foo(param1: 1,\n" + 24 | " param2: true,\n" + 25 | " param3: [3])", 26 | "foo(\n" + 27 | " param1: 1, param2: true, param3: [3]\n" + 28 | ")", 29 | "foo(\n" + 30 | " param1: 1,\n" + 31 | " param2: true,\n" + 32 | " param3: [3]\n" + 33 | ")" 34 | ] 35 | 36 | static let triggeringExamples = [ 37 | "foo(0,\n" + 38 | " param1: 1, ↓param2: true, ↓param3: [3])", 39 | "foo(0, ↓param1: 1,\n" + 40 | " param2: true, ↓param3: [3])", 41 | "foo(0, ↓param1: 1, ↓param2: true,\n" + 42 | " param3: [3])", 43 | "foo(\n" + 44 | " 0, ↓param1: 1,\n" + 45 | " param2: true, ↓param3: [3]\n" + 46 | ")" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Rules/Style/ProtocolPropertyAccessorsOrderRule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | public struct ProtocolPropertyAccessorsOrderRule: ConfigurationProviderRule, SubstitutionCorrectableRule, 5 | AutomaticTestableRule { 6 | public var configuration = SeverityConfiguration(.warning) 7 | 8 | public init() {} 9 | 10 | public static let description = RuleDescription( 11 | identifier: "protocol_property_accessors_order", 12 | name: "Protocol Property Accessors Order", 13 | description: "When declaring properties in protocols, the order of accessors should be `get set`.", 14 | kind: .style, 15 | nonTriggeringExamples: [ 16 | "protocol Foo {\n var bar: String { get set }\n }", 17 | "protocol Foo {\n var bar: String { get }\n }", 18 | "protocol Foo {\n var bar: String { set }\n }" 19 | ], 20 | triggeringExamples: [ 21 | "protocol Foo {\n var bar: String { ↓set get }\n }" 22 | ], 23 | corrections: [ 24 | "protocol Foo {\n var bar: String { ↓set get }\n }": 25 | "protocol Foo {\n var bar: String { get set }\n }" 26 | ] 27 | ) 28 | 29 | public func validate(file: File) -> [StyleViolation] { 30 | return violationRanges(in: file).map { 31 | StyleViolation(ruleDescription: type(of: self).description, 32 | severity: configuration.severity, 33 | location: Location(file: file, characterOffset: $0.location)) 34 | } 35 | } 36 | 37 | public func violationRanges(in file: File) -> [NSRange] { 38 | return file.match(pattern: "\\bset\\s*get\\b", with: [.keyword, .keyword]) 39 | } 40 | 41 | public func substitution(for violationRange: NSRange, in file: File) -> (NSRange, String) { 42 | return (violationRange, "get set") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/SwiftLintFramework/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.31.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Realm. All rights reserved. 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/swiftlint/Commands/GenerateDocsCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import Result 3 | import SwiftLintFramework 4 | 5 | struct GenerateDocsCommand: CommandProtocol { 6 | let verb = "generate-docs" 7 | let function = "Generates markdown documentation for all rules" 8 | 9 | func run(_ options: GenerateDocsOptions) -> Result<(), CommandantError<()>> { 10 | let text = masterRuleList.generateDocumentation() 11 | 12 | if let path = options.path { 13 | do { 14 | try text.write(toFile: path, atomically: true, encoding: .utf8) 15 | } catch { 16 | return .failure(.usageError(description: error.localizedDescription)) 17 | } 18 | } else { 19 | queuedPrint(text) 20 | } 21 | 22 | return .success(()) 23 | } 24 | } 25 | 26 | struct GenerateDocsOptions: OptionsProtocol { 27 | let path: String? 28 | 29 | static func create(_ path: String?) -> GenerateDocsOptions { 30 | return self.init(path: path) 31 | } 32 | 33 | static func evaluate(_ mode: CommandMode) -> Result>> { 34 | return create 35 | <*> mode <| Option(key: "path", defaultValue: nil, 36 | usage: "the path where the documentation should be saved. " + 37 | "If not present, it'll be printed to the output.") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Source/swiftlint/Commands/VersionCommand.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import Result 3 | import SwiftLintFramework 4 | 5 | struct VersionCommand: CommandProtocol { 6 | let verb = "version" 7 | let function = "Display the current version of SwiftLint" 8 | 9 | func run(_ options: NoOptions>) -> Result<(), CommandantError<()>> { 10 | print(Version.current.value) 11 | return .success(()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/swiftlint/Extensions/Reporter+CommandLine.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | 3 | extension Reporter { 4 | static func report(violations: [StyleViolation], realtimeCondition: Bool) { 5 | if isRealtime == realtimeCondition { 6 | let report = generateReport(violations) 7 | if !report.isEmpty { 8 | queuedPrint(report) 9 | } 10 | } 11 | } 12 | } 13 | 14 | func reporterFrom(optionsReporter: String, configuration: Configuration) -> Reporter.Type { 15 | let string = optionsReporter.isEmpty ? configuration.reporter : optionsReporter 16 | return reporterFrom(identifier: string) 17 | } 18 | -------------------------------------------------------------------------------- /Source/swiftlint/Helpers/Benchmark.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | struct BenchmarkEntry { 5 | let id: String 6 | let time: Double 7 | } 8 | 9 | struct Benchmark { 10 | private let name: String 11 | private var entries = [BenchmarkEntry]() 12 | 13 | init(name: String) { 14 | self.name = name 15 | } 16 | 17 | mutating func record(id: String, time: Double) { 18 | entries.append(BenchmarkEntry(id: id, time: time)) 19 | } 20 | 21 | mutating func record(file: File, from start: Date) { 22 | record(id: file.path ?? "", time: -start.timeIntervalSinceNow) 23 | } 24 | 25 | func save() { 26 | // Decomposed to improve compile times 27 | let entriesDict: [String: Double] = entries.reduce(into: [String: Double]()) { accu, idAndTime in 28 | accu[idAndTime.id] = (accu[idAndTime.id] ?? 0) + idAndTime.time 29 | } 30 | let entriesKeyValues: [(String, Double)] = entriesDict.sorted { $0.1 < $1.1 } 31 | let lines: [String] = entriesKeyValues.map { id, time -> String in 32 | return "\(numberFormatter.string(from: NSNumber(value: time))!): \(id)" 33 | } 34 | let string: String = lines.joined(separator: "\n") + "\n" 35 | let url = URL(fileURLWithPath: "benchmark_\(name)_\(timestamp).txt") 36 | try? string.data(using: .utf8)?.write(to: url, options: [.atomic]) 37 | } 38 | } 39 | 40 | private let numberFormatter: NumberFormatter = { 41 | let formatter = NumberFormatter() 42 | formatter.numberStyle = .decimal 43 | formatter.minimumFractionDigits = 3 44 | return formatter 45 | }() 46 | 47 | private let timestamp: String = { 48 | let formatter = DateFormatter() 49 | formatter.dateFormat = "yyyy_MM_dd_HH_mm_ss" 50 | return formatter.string(from: Date()) 51 | }() 52 | -------------------------------------------------------------------------------- /Source/swiftlint/Helpers/CommonOptions.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import SwiftLintFramework 3 | 4 | func pathOption(action: String) -> Option { 5 | return Option(key: "path", 6 | defaultValue: "", 7 | usage: "the path to the file or directory to \(action)") 8 | } 9 | 10 | func pathsArgument(action: String) -> Argument<[String]> { 11 | return Argument(defaultValue: [""], 12 | usage: "list of paths to the files or directories to \(action)") 13 | } 14 | 15 | let configOption = Option(key: "config", 16 | defaultValue: Configuration.fileName, 17 | usage: "the path to SwiftLint's configuration file") 18 | 19 | let useScriptInputFilesOption = Option(key: "use-script-input-files", 20 | defaultValue: false, 21 | usage: "read SCRIPT_INPUT_FILE* environment variables " + 22 | "as files") 23 | 24 | func quietOption(action: String) -> Option { 25 | return Option(key: "quiet", 26 | defaultValue: false, 27 | usage: "don't print status logs like '\(action.capitalized) ' & " + 28 | "'Done \(action)'") 29 | } 30 | -------------------------------------------------------------------------------- /Source/swiftlint/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.31.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2015 Realm. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Source/swiftlint/main.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | import Dispatch 3 | import SwiftLintFramework 4 | 5 | DispatchQueue.global().async { 6 | let registry = CommandRegistry>() 7 | registry.register(LintCommand()) 8 | registry.register(AutoCorrectCommand()) 9 | registry.register(AnalyzeCommand()) 10 | registry.register(VersionCommand()) 11 | registry.register(RulesCommand()) 12 | registry.register(GenerateDocsCommand()) 13 | registry.register(HelpCommand(registry: registry)) 14 | 15 | registry.main(defaultVerb: LintCommand().verb) { error in 16 | queuedPrintError(String(describing: error)) 17 | } 18 | } 19 | 20 | dispatchMain() 21 | -------------------------------------------------------------------------------- /SwiftLint.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftLint' 3 | s.version = `make get_version` 4 | s.summary = 'A tool to enforce Swift style and conventions.' 5 | s.homepage = 'https://github.com/realm/SwiftLint' 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = { 'JP Simard' => 'jp@jpsim.com' } 8 | s.source = { :http => "#{s.homepage}/releases/download/#{s.version}/portable_swiftlint.zip" } 9 | s.preserve_paths = '*' 10 | s.exclude_files = '**/file.zip' 11 | end 12 | -------------------------------------------------------------------------------- /SwiftLint.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftLint.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftLint.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftLint.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftLintFramework.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftLintFramework' 3 | s.version = `make get_version` 4 | s.summary = 'A tool to enforce Swift style and conventions.' 5 | s.homepage = 'https://github.com/realm/SwiftLint' 6 | s.source = { :git => s.homepage + '.git', :tag => s.version } 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { 'JP Simard' => 'jp@jpsim.com' } 9 | s.platform = :osx, '10.10' 10 | s.source_files = 'Source/SwiftLintFramework/**/*.swift' 11 | s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' } 12 | s.dependency 'SourceKittenFramework', '~> 0.22' 13 | s.dependency 'Yams', '~> 1.0' 14 | end 15 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/CollectionAlignmentRuleTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class CollectionAlignmentRuleTests: XCTestCase { 5 | func testWithDefaultConfiguration() { 6 | verifyRule(CollectionAlignmentRule.description) 7 | } 8 | 9 | func testCollectionAlignmentWithAlignLeft() { 10 | let baseDescription = CollectionAlignmentRule.description 11 | let examples = CollectionAlignmentRule.Examples(alignColons: false) 12 | 13 | let description = baseDescription.with(nonTriggeringExamples: examples.nonTriggeringExamples, 14 | triggeringExamples: examples.triggeringExamples) 15 | 16 | verifyRule(description) 17 | } 18 | 19 | func testCollectionAlignmentWithAlignColons() { 20 | let baseDescription = CollectionAlignmentRule.description 21 | let examples = CollectionAlignmentRule.Examples(alignColons: true) 22 | 23 | let description = baseDescription.with(nonTriggeringExamples: examples.nonTriggeringExamples, 24 | triggeringExamples: examples.triggeringExamples) 25 | 26 | verifyRule(description, ruleConfiguration: ["align_colons": true]) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/CompilerProtocolInitRuleTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class CompilerProtocolInitRuleTests: XCTestCase { 5 | private let ruleID = CompilerProtocolInitRule.description.identifier 6 | 7 | func testWithDefaultConfiguration() { 8 | verifyRule(CompilerProtocolInitRule.description) 9 | } 10 | 11 | func testViolationMessageForExpressibleByIntegerLiteral() { 12 | guard let config = makeConfig(nil, ruleID) else { 13 | XCTFail("Failed to create configuration") 14 | return 15 | } 16 | let allViolations = violations("let a = NSNumber(integerLiteral: 1)", config: config) 17 | 18 | let compilerProtocolInitViolation = allViolations.first { $0.ruleDescription.identifier == ruleID } 19 | if let violation = compilerProtocolInitViolation { 20 | XCTAssertEqual( 21 | violation.reason, 22 | "The initializers declared in compiler protocol ExpressibleByIntegerLiteral " + 23 | "shouldn't be called directly." 24 | ) 25 | } else { 26 | XCTFail("A compiler protocol init violation should have been triggered!") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/ConditionalReturnsOnNewlineRuleTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class ConditionalReturnsOnNewlineRuleTests: XCTestCase { 5 | func testConditionalReturnsOnNewlineWithDefaultConfiguration() { 6 | // Test with default parameters 7 | verifyRule(ConditionalReturnsOnNewlineRule.description) 8 | } 9 | 10 | func testConditionalReturnsOnNewlineWithIfOnly() { 11 | // Test with `if_only` set to true 12 | let nonTriggeringExamples = [ 13 | "guard true else {\n return true\n}", 14 | "guard true,\n let x = true else {\n return true\n}", 15 | "if true else {\n return true\n}", 16 | "if true,\n let x = true else {\n return true\n}", 17 | "if textField.returnKeyType == .Next {", 18 | "if true { // return }", 19 | "/*if true { */ return }", 20 | "guard true else { return }" 21 | ] 22 | let triggeringExamples = [ 23 | "↓if true { return }", 24 | "↓if true { break } else { return }", 25 | "↓if true { break } else { return }", 26 | "↓if true { return \"YES\" } else { return \"NO\" }" 27 | ] 28 | 29 | let description = ConditionalReturnsOnNewlineRule.description 30 | .with(triggeringExamples: triggeringExamples) 31 | .with(nonTriggeringExamples: nonTriggeringExamples) 32 | 33 | verifyRule(description, ruleConfiguration: ["if_only": true]) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/ConfigurationAliasesTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | @testable import SwiftLintFramework 4 | import XCTest 5 | 6 | final class ConfigurationAliasesTests: XCTestCase { 7 | let testRuleList = RuleList(rules: RuleWithLevelsMock.self) 8 | 9 | func testConfiguresCorrectlyFromDeprecatedAlias() throws { 10 | let ruleConfiguration = [1, 2] 11 | let config = ["mock": ruleConfiguration] 12 | let rules = try testRuleList.configuredRules(with: config) 13 | XCTAssertTrue(rules == [try RuleWithLevelsMock(configuration: ruleConfiguration)]) 14 | } 15 | 16 | func testReturnsNilWithDuplicatedConfiguration() { 17 | let dict = ["mock": [1, 2], "severity_level_mock": [1, 3]] 18 | let configuration = Configuration(dict: dict, ruleList: testRuleList) 19 | XCTAssertNil(configuration) 20 | } 21 | 22 | func testInitsFromDeprecatedAlias() { 23 | let ruleConfiguration = [1, 2] 24 | let configuration = Configuration(dict: ["mock": ruleConfiguration], ruleList: testRuleList) 25 | XCTAssertNotNil(configuration) 26 | } 27 | 28 | func testWhitelistRulesFromDeprecatedAlias() { 29 | let configuration = Configuration(dict: ["whitelist_rules": ["mock"]], ruleList: testRuleList)! 30 | let configuredIdentifiers = configuration.rules.map { 31 | type(of: $0).description.identifier 32 | } 33 | XCTAssertEqual(configuredIdentifiers, ["severity_level_mock"]) 34 | } 35 | 36 | func testDisabledRulesFromDeprecatedAlias() { 37 | let configuration = Configuration(dict: ["disabled_rules": ["mock"]], ruleList: testRuleList)! 38 | XCTAssert(configuration.rules.isEmpty) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/ContainsOverFirstNotNilRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class ContainsOverFirstNotNilRuleTests: XCTestCase { 5 | func testWithDefaultConfiguration() { 6 | verifyRule(ContainsOverFirstNotNilRule.description) 7 | } 8 | 9 | // MARK: - Reasons 10 | 11 | func testFirstReason() { 12 | let string = "↓myList.first { $0 % 2 == 0 } != nil" 13 | let violations = self.violations(string) 14 | 15 | XCTAssertEqual(violations.count, 1) 16 | XCTAssertEqual(violations.first?.reason, "Prefer `contains` over `first(where:) != nil`") 17 | } 18 | 19 | func testFirstIndexReason() { 20 | let string = "↓myList.firstIndex { $0 % 2 == 0 } != nil" 21 | let violations = self.violations(string) 22 | 23 | XCTAssertEqual(violations.count, 1) 24 | XCTAssertEqual(violations.first?.reason, "Prefer `contains` over `firstIndex(where:) != nil`") 25 | } 26 | 27 | // MARK: - Private 28 | 29 | private func violations(_ string: String, config: Any? = nil) -> [StyleViolation] { 30 | guard let config = makeConfig(config, ContainsOverFirstNotNilRule.description.identifier) else { 31 | return [] 32 | } 33 | 34 | return SwiftLintFrameworkTests.violations(string, config: config) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/DeploymentTargetRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class DeploymentTargetRuleTests: XCTestCase { 5 | func testRule() { 6 | verifyRule(DeploymentTargetRule.description) 7 | } 8 | 9 | // MARK: - Reasons 10 | 11 | func testMacOSAttributeReason() { 12 | let string = "@availability(macOS 10.11, *)\nclass A {}" 13 | let violations = self.violations(string, config: ["macOS_deployment_target": "10.14.0"]) 14 | 15 | let expectedMessage = "Availability attribute is using a version (10.11) that is satisfied by " + 16 | "the deployment target (10.14) for platform macOS." 17 | XCTAssertEqual(violations.count, 1) 18 | XCTAssertEqual(violations.first?.reason, expectedMessage) 19 | } 20 | 21 | func testWatchOSConditionReason() { 22 | let string = "if #available(watchOS 4, *) {}" 23 | let violations = self.violations(string, config: ["watchOS_deployment_target": "5.0.1"]) 24 | 25 | let expectedMessage = "Availability condition is using a version (4) that is satisfied by " + 26 | "the deployment target (5.0.1) for platform watchOS." 27 | XCTAssertEqual(violations.count, 1) 28 | XCTAssertEqual(violations.first?.reason, expectedMessage) 29 | } 30 | 31 | private func violations(_ string: String, config: Any?) -> [StyleViolation] { 32 | guard let config = makeConfig(config, DeploymentTargetRule.description.identifier) else { 33 | return [] 34 | } 35 | 36 | return SwiftLintFrameworkTests.violations(string, config: config) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/DiscouragedDirectInitRuleTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftLintFramework 3 | import XCTest 4 | 5 | class DiscouragedDirectInitRuleTests: XCTestCase { 6 | let baseDescription = DiscouragedDirectInitRule.description 7 | 8 | func testDiscouragedDirectInitWithDefaultConfiguration() { 9 | verifyRule(baseDescription) 10 | } 11 | 12 | func testDiscouragedDirectInitWithConfiguredSeverity() { 13 | verifyRule(baseDescription, ruleConfiguration: ["severity": "error"]) 14 | } 15 | 16 | func testDiscouragedDirectInitWithNewIncludedTypes() { 17 | let triggeringExamples = [ 18 | "let foo = ↓Foo()", 19 | "let bar = ↓Bar()" 20 | ] 21 | 22 | let nonTriggeringExamples = [ 23 | "let foo = Foo(arg: toto)", 24 | "let bar = Bar(arg: \"toto\")" 25 | ] 26 | 27 | let description = baseDescription 28 | .with(triggeringExamples: triggeringExamples) 29 | .with(nonTriggeringExamples: nonTriggeringExamples) 30 | 31 | verifyRule(description, ruleConfiguration: ["types": ["Foo", "Bar"]]) 32 | } 33 | 34 | func testDiscouragedDirectInitWithReplacedTypes() { 35 | let triggeringExamples = [ 36 | "let bundle = ↓Bundle()" 37 | ] 38 | 39 | let nonTriggeringExamples = [ 40 | "let device = UIDevice()" 41 | ] 42 | 43 | let description = baseDescription 44 | .with(triggeringExamples: triggeringExamples) 45 | .with(nonTriggeringExamples: nonTriggeringExamples) 46 | 47 | verifyRule(description, ruleConfiguration: ["types": ["Bundle"]]) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/DiscouragedObjectLiteralRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class DiscouragedObjectLiteralRuleTests: XCTestCase { 5 | func testWithDefaultConfiguration() { 6 | verifyRule(DiscouragedObjectLiteralRule.description) 7 | } 8 | 9 | func testWithImageLiteral() { 10 | let baseDescription = DiscouragedObjectLiteralRule.description 11 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ 12 | "let color = #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)" 13 | ] 14 | let triggeringExamples = [ 15 | "let image = ↓#imageLiteral(resourceName: \"image.jpg\")" 16 | ] 17 | 18 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples, 19 | triggeringExamples: triggeringExamples) 20 | 21 | verifyRule(description, ruleConfiguration: ["image_literal": true, "color_literal": false]) 22 | } 23 | 24 | func testWithColorLiteral() { 25 | let baseDescription = DiscouragedObjectLiteralRule.description 26 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ 27 | "let image = #imageLiteral(resourceName: \"image.jpg\")" 28 | ] 29 | let triggeringExamples = [ 30 | "let color = ↓#colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)" 31 | ] 32 | 33 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples, 34 | triggeringExamples: triggeringExamples) 35 | 36 | verifyRule(description, ruleConfiguration: ["image_literal": false, "color_literal": true]) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/DocumentationTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import SwiftLintFramework 3 | import XCTest 4 | 5 | private let projectRoot = #file.bridge() 6 | .deletingLastPathComponent.bridge() 7 | .deletingLastPathComponent.bridge() 8 | .deletingLastPathComponent 9 | 10 | class DocumentationTests: XCTestCase { 11 | func testRulesDocumentationIsUpdated() throws { 12 | guard SwiftVersion.current >= .fourDotOne else { 13 | return 14 | } 15 | 16 | let docsPath = "\(projectRoot)/Rules.md" 17 | let existingDocs = try String(contentsOfFile: docsPath) 18 | let updatedDocs = masterRuleList.generateDocumentation() 19 | 20 | XCTAssertEqual(existingDocs, updatedDocs) 21 | 22 | if existingDocs != updatedDocs { 23 | // Overwrite Rules.md with latest version 24 | try updatedDocs.data(using: .utf8)?.write(to: URL(fileURLWithPath: docsPath)) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/ExtendedNSStringTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | class ExtendedNSStringTests: XCTestCase { 5 | func testLineAndCharacterForByteOffset_forContentsContainingMultibyteCharacters() { 6 | let contents = "" + 7 | "import Foundation\n" + // 18 characters 8 | "class Test {\n" + // 13 characters 9 | "func test() {\n" + // 14 characters 10 | "// 日本語コメント : comment in Japanese\n" + // 33 characters 11 | "// do something\n" + // 16 characters 12 | "}\n" + 13 | "}" 14 | let string = NSString(string: contents) 15 | // A character placed on 80 offset indicates a white-space before 'do' at 5th line. 16 | if let lineAndCharacter = string.lineAndCharacter(forCharacterOffset: 80) { 17 | XCTAssertEqual(lineAndCharacter.line, 5) 18 | XCTAssertEqual(lineAndCharacter.character, 3) 19 | } else { 20 | XCTFail("NSString.lineAndCharacterForByteOffset should return non-nil tuple.") 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/FileLengthRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class FileLengthRuleTests: XCTestCase { 5 | func testFileLengthWithDefaultConfiguration() { 6 | verifyRule(FileLengthRule.description, commentDoesntViolate: false, 7 | testMultiByteOffsets: false, testShebang: false) 8 | } 9 | 10 | func testFileLengthIgnoringLinesWithOnlyComments() { 11 | let triggeringExamples = [ 12 | repeatElement("print(\"swiftlint\")\n", count: 401).joined() 13 | ] 14 | let nonTriggeringExamples = [ 15 | (repeatElement("print(\"swiftlint\")\n", count: 400) + ["//\n"]).joined(), 16 | repeatElement("print(\"swiftlint\")\n", count: 400).joined() 17 | ] 18 | 19 | let description = FileLengthRule.description 20 | .with(nonTriggeringExamples: nonTriggeringExamples) 21 | .with(triggeringExamples: triggeringExamples) 22 | 23 | verifyRule(description, ruleConfiguration: ["ignore_comment_only_lines": true], 24 | testMultiByteOffsets: false, testShebang: false) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/ImplicitlyUnwrappedOptionalConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | import SourceKittenFramework 2 | @testable import SwiftLintFramework 3 | import XCTest 4 | 5 | // swiftlint:disable:next type_name 6 | class ImplicitlyUnwrappedOptionalConfigurationTests: XCTestCase { 7 | func testImplicitlyUnwrappedOptionalConfigurationProperlyAppliesConfigurationFromDictionary() throws { 8 | var configuration = ImplicitlyUnwrappedOptionalConfiguration(mode: .allExceptIBOutlets, 9 | severity: SeverityConfiguration(.warning)) 10 | 11 | try configuration.apply(configuration: ["mode": "all", "severity": "error"]) 12 | XCTAssertEqual(configuration.mode, .all) 13 | XCTAssertEqual(configuration.severity.severity, .error) 14 | 15 | try configuration.apply(configuration: ["mode": "all_except_iboutlets"]) 16 | XCTAssertEqual(configuration.mode, .allExceptIBOutlets) 17 | XCTAssertEqual(configuration.severity.severity, .error) 18 | 19 | try configuration.apply(configuration: ["severity": "warning"]) 20 | XCTAssertEqual(configuration.mode, .allExceptIBOutlets) 21 | XCTAssertEqual(configuration.severity.severity, .warning) 22 | 23 | try configuration.apply(configuration: ["mode": "all", "severity": "warning"]) 24 | XCTAssertEqual(configuration.mode, .all) 25 | XCTAssertEqual(configuration.severity.severity, .warning) 26 | } 27 | 28 | func testImplicitlyUnwrappedOptionalConfigurationThrowsOnBadConfig() { 29 | let badConfigs: [[String: Any]] = [ 30 | ["mode": "everything"], 31 | ["mode": false], 32 | ["mode": 42] 33 | ] 34 | 35 | for badConfig in badConfigs { 36 | var configuration = ImplicitlyUnwrappedOptionalConfiguration(mode: .allExceptIBOutlets, 37 | severity: SeverityConfiguration(.warning)) 38 | checkError(ConfigurationError.unknownConfiguration) { 39 | try configuration.apply(configuration: badConfig) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/ImplicitlyUnwrappedOptionalRuleTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import SwiftLintFramework 3 | import XCTest 4 | 5 | class ImplicitlyUnwrappedOptionalRuleTests: XCTestCase { 6 | func testWithDefaultConfiguration() { 7 | verifyRule(ImplicitlyUnwrappedOptionalRule.description) 8 | } 9 | 10 | func testImplicitlyUnwrappedOptionalRuleDefaultConfiguration() { 11 | let rule = ImplicitlyUnwrappedOptionalRule() 12 | XCTAssertEqual(rule.configuration.mode, .allExceptIBOutlets) 13 | XCTAssertEqual(rule.configuration.severity.severity, .warning) 14 | } 15 | 16 | func testImplicitlyUnwrappedOptionalRuleWarnsOnOutletsInAllMode() { 17 | let baseDescription = ImplicitlyUnwrappedOptionalRule.description 18 | let triggeringExamples = [ 19 | "@IBOutlet private var label: UILabel!", 20 | "@IBOutlet var label: UILabel!", 21 | "let int: Int!" 22 | ] 23 | 24 | let nonTriggeringExamples = ["if !boolean {}"] 25 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 26 | .with(triggeringExamples: triggeringExamples) 27 | 28 | verifyRule(description, ruleConfiguration: ["mode": "all"], 29 | commentDoesntViolate: true, stringDoesntViolate: true) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/PrefixedTopLevelConstantRuleTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | final class PrefixedTopLevelConstantRuleTests: XCTestCase { 5 | func testDefaultConfiguration() { 6 | verifyRule(PrefixedTopLevelConstantRule.description) 7 | } 8 | 9 | func testPrivateOnly() { 10 | let triggeringExamples = [ 11 | "private let ↓Foo = 20.0", 12 | "fileprivate let ↓foo = 20.0" 13 | ] 14 | let nonTriggeringExamples = [ 15 | "let Foo = 20.0", 16 | "internal let Foo = \"Foo\"", 17 | "public let Foo = 20.0" 18 | ] 19 | 20 | let description = PrefixedTopLevelConstantRule.description 21 | .with(triggeringExamples: triggeringExamples) 22 | .with(nonTriggeringExamples: nonTriggeringExamples) 23 | 24 | verifyRule(description, ruleConfiguration: ["only_private": true]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/PrivateOutletRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class PrivateOutletRuleTests: XCTestCase { 5 | func testWithDefaultConfiguration() { 6 | verifyRule(PrivateOutletRule.description) 7 | } 8 | 9 | func testWithAllowPrivateSet() { 10 | let baseDescription = PrivateOutletRule.description 11 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ 12 | "class Foo {\n @IBOutlet private(set) var label: UILabel?\n}\n", 13 | "class Foo {\n @IBOutlet private(set) var label: UILabel!\n}\n", 14 | "class Foo {\n @IBOutlet weak private(set) var label: UILabel?\n}\n", 15 | "class Foo {\n @IBOutlet private(set) weak var label: UILabel?\n}\n" 16 | ] 17 | 18 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 19 | verifyRule(description, ruleConfiguration: ["allow_private_set": true]) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/PrivateOverFilePrivateRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class PrivateOverFilePrivateRuleTests: XCTestCase { 5 | func testPrivateOverFilePrivateWithDefaultConfiguration() { 6 | verifyRule(PrivateOverFilePrivateRule.description) 7 | } 8 | 9 | func testPrivateOverFilePrivateValidatingExtensions() { 10 | let baseDescription = PrivateOverFilePrivateRule.description 11 | let triggeringExamples = baseDescription.triggeringExamples + [ 12 | "↓fileprivate extension String {}", 13 | "↓fileprivate \n extension String {}", 14 | "↓fileprivate extension \n String {}" 15 | ] 16 | let corrections = [ 17 | "↓fileprivate extension String {}": "private extension String {}", 18 | "↓fileprivate \n extension String {}": "private \n extension String {}", 19 | "↓fileprivate extension \n String {}": "private extension \n String {}" 20 | ] 21 | 22 | let description = baseDescription.with(nonTriggeringExamples: []) 23 | .with(triggeringExamples: triggeringExamples).with(corrections: corrections) 24 | verifyRule(description, ruleConfiguration: ["validate_extensions": true]) 25 | } 26 | 27 | func testPrivateOverFilePrivateNotValidatingExtensions() { 28 | let baseDescription = PrivateOverFilePrivateRule.description 29 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ 30 | "fileprivate extension String {}" 31 | ] 32 | 33 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 34 | verifyRule(description, ruleConfiguration: ["validate_extensions": false]) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedCSVReporterOutput.csv: -------------------------------------------------------------------------------- 1 | file,line,character,severity,type,reason,rule_id 2 | filename,1,2,Warning,Line Length,Violation Reason.,line_length 3 | filename,1,2,Error,Line Length,Violation Reason.,line_length 4 | filename,1,2,Error,Syntactic Sugar,"Shorthand syntactic sugar should be used, i.e. [Int] instead of Array.",syntactic_sugar 5 | ,,,Error,Colon,Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.,colon -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedCheckstyleReporterOutput.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedEmojiReporterOutput.txt: -------------------------------------------------------------------------------- 1 | Other 2 | ⛔️ Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. 3 | filename 4 | ⛔️ Line 1: Violation Reason. 5 | ⛔️ Line 1: Shorthand syntactic sugar should be used, i.e. [Int] instead of Array. 6 | ⚠️ Line 1: Violation Reason. -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedJSONReporterOutput.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "reason" : "Violation Reason.", 4 | "character" : 2, 5 | "file" : "filename", 6 | "rule_id" : "line_length", 7 | "line" : 1, 8 | "severity" : "Warning", 9 | "type" : "Line Length" 10 | }, 11 | { 12 | "reason" : "Violation Reason.", 13 | "character" : 2, 14 | "file" : "filename", 15 | "rule_id" : "line_length", 16 | "line" : 1, 17 | "severity" : "Error", 18 | "type" : "Line Length" 19 | }, 20 | { 21 | "reason" : "Shorthand syntactic sugar should be used, i.e. [Int] instead of Array.", 22 | "character" : 2, 23 | "file" : "filename", 24 | "rule_id" : "syntactic_sugar", 25 | "line" : 1, 26 | "severity" : "Error", 27 | "type" : "Syntactic Sugar" 28 | }, 29 | { 30 | "reason" : "Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.", 31 | "character" : null, 32 | "file" : null, 33 | "rule_id" : "colon", 34 | "line" : null, 35 | "severity" : "Error", 36 | "type" : "Colon" 37 | } 38 | ] -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedJunitReporterOutput.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | warning: 5 | Line:1 6 | 7 | error: 8 | Line:1 9 | 10 | error: 11 | Line:1 12 | 13 | error: 14 | Line:0 15 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedMarkdownReporterOutput.md: -------------------------------------------------------------------------------- 1 | file | line | severity | reason | rule_id 2 | --- | --- | --- | --- | --- 3 | filename | 1 | :warning: | Line Length: Violation Reason. | line_length 4 | filename | 1 | :stop\_sign: | Line Length: Violation Reason. | line_length 5 | filename | 1 | :stop\_sign: | Syntactic Sugar: Shorthand syntactic sugar should be used, i.e. [Int] instead of Array. | syntactic_sugar 6 | | | :stop\_sign: | Colon: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. | colon -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedSonarQubeReporterOutput.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues":[ 3 | { 4 | "engineId":"SwiftLint", 5 | "primaryLocation":{ 6 | "filePath":"filename", 7 | "message":"Violation Reason.", 8 | "textRange":{ 9 | "startLine":1 10 | } 11 | }, 12 | "ruleId":"line_length", 13 | "severity":"MINOR", 14 | "type":"CODE_SMELL" 15 | }, 16 | { 17 | "engineId":"SwiftLint", 18 | "primaryLocation":{ 19 | "filePath":"filename", 20 | "message":"Violation Reason.", 21 | "textRange":{ 22 | "startLine":1 23 | } 24 | }, 25 | "ruleId":"line_length", 26 | "severity":"MAJOR", 27 | "type":"CODE_SMELL" 28 | }, 29 | { 30 | "engineId":"SwiftLint", 31 | "primaryLocation":{ 32 | "filePath":"filename", 33 | "message":"Shorthand syntactic sugar should be used, i.e. [Int] instead of Array.", 34 | "textRange":{ 35 | "startLine":1 36 | } 37 | }, 38 | "ruleId":"syntactic_sugar", 39 | "severity":"MAJOR", 40 | "type":"CODE_SMELL" 41 | }, 42 | { 43 | "engineId":"SwiftLint", 44 | "primaryLocation":{ 45 | "filePath":"", 46 | "message":"Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.", 47 | "textRange":{ 48 | "startLine":1 49 | } 50 | }, 51 | "ruleId":"colon", 52 | "severity":"MAJOR", 53 | "type":"CODE_SMELL" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/CannedXcodeReporterOutput.txt: -------------------------------------------------------------------------------- 1 | filename:1:2: warning: Line Length Violation: Violation Reason. (line_length) 2 | filename:1:2: error: Line Length Violation: Violation Reason. (line_length) 3 | filename:1:2: error: Syntactic Sugar Violation: Shorthand syntactic sugar should be used, i.e. [Int] instead of Array. (syntactic_sugar) 4 | :1:1: error: Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameCaseMismatch.swift: -------------------------------------------------------------------------------- 1 | // FileNameCaseMismatch.Swift 2 | // Copyright © 2016 3 | struct A {} 4 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMatchingComplex.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 2 | // File: "FileNameMatchingComplex.swift" 3 | struct A {} 4 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMatchingSimple.swift: -------------------------------------------------------------------------------- 1 | // FileNameMatchingSimple.swift 2 | // Copyright © 2016 3 | struct A {} 4 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMismatch.swift: -------------------------------------------------------------------------------- 1 | // AFileNameMismatch.swift 2 | // Copyright © 2016 3 | struct A {} 4 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMissing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016 3 | struct A {} 4 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtension.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension Bool { 5 | func toggle() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtensionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SomeModule 2 | import XCTest 3 | 4 | class BoolExtensionTests: XCTestCase { 5 | func testExample() { 6 | // some code 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/BoolExtensions.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension Bool { 5 | func toggle() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool+SwiftLint.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension Bool { 5 | func toggle() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionBool.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension Bool { 5 | func toggle() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/ExtensionsBool.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension Bool { 5 | func toggle() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFrameworkTests 2 | import XCTest 3 | 4 | extension AttributesRuleTests { 5 | static var allTests: [(String, (AttributesRuleTests) -> () throws -> Void)] = [ 6 | ("testAttributesWithDefaultConfiguration", testAttributesWithDefaultConfiguration), 7 | ("testAttributesWithAlwaysOnSameLine", testAttributesWithAlwaysOnSameLine), 8 | ("testAttributesWithAlwaysOnLineAbove", testAttributesWithAlwaysOnLineAbove) 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyClass.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyStruct.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyStructf.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/NSString+Extension.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension NSString { 5 | func asdf() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Notification.Name+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Notification.Name { 4 | } 5 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/SLBoolExtension.swift: -------------------------------------------------------------------------------- 1 | struct MyStruct {} 2 | class MyClass {} 3 | 4 | extension Bool { 5 | func toggle() {} 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/main.swift: -------------------------------------------------------------------------------- 1 | struct A {} 2 | 3 | print("hello") 4 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - force_cast 3 | included: 4 | - "everything" 5 | excluded: 6 | - "the place where i committed many coding sins" 7 | line_length: 10000000000 8 | reporter: "json" 9 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Directory.swift/DirectoryLevel1.swift: -------------------------------------------------------------------------------- 1 | // This is just a mock Swift file 2 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level0.swift: -------------------------------------------------------------------------------- 1 | // This is just a mock Swift file 2 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level1.swift: -------------------------------------------------------------------------------- 1 | // This is just a mock Swift file 2 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - force_try 3 | included: 4 | - "everything" 5 | excluded: 6 | - "the place where i committed many coding sins" 7 | line_length: 10000000000 8 | reporter: "json" 9 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/Level2.swift: -------------------------------------------------------------------------------- 1 | // This is just a mock Swift file 2 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/Level3/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - force_try 3 | - todo 4 | included: 5 | - "everything on level 3" 6 | excluded: 7 | - "the place where i committed many coding sins" 8 | line_length: 10000000000 9 | reporter: "json" 10 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/Level3/Level3.swift: -------------------------------------------------------------------------------- 1 | // This is just a mock Swift file 2 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/custom_rules.yml: -------------------------------------------------------------------------------- 1 | custom_rules: 2 | no_abcd: 3 | name: "Don't use abcd" 4 | regex: 'abcd' -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/custom_rules_disabled.yml: -------------------------------------------------------------------------------- 1 | custom_rules: 2 | no_abcd: 3 | name: "Don't use abcd" 4 | regex: 'abcd' 5 | disabled_rules: 6 | - no_abc -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/custom.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - force_cast 3 | included: 4 | - "only a few things" 5 | excluded: 6 | - "the place where i committed many coding sins" 7 | line_length: 10000000000 8 | reporter: "json" 9 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/ProjectMock/custom_rules.yml: -------------------------------------------------------------------------------- 1 | custom_rules: 2 | no_abc: 3 | name: "Don't use abc" 4 | regex: 'abc' -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/test.txt: -------------------------------------------------------------------------------- 1 | // My file with 2 | // a pattern 3 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Resources/test.yml: -------------------------------------------------------------------------------- 1 | dictionary1: 2 | bool: true 3 | int: 1 4 | double: 1.0 5 | string: string 6 | array: 7 | - true 8 | - 1 9 | - 1.0 10 | - string 11 | - bool: true 12 | int: 1 13 | double: 1.0 14 | string: string 15 | 16 | dictionary2: 17 | bool: true 18 | int: 1 19 | double: 1.0 20 | string: string 21 | array: 22 | - true 23 | - 1 24 | - 1.0 25 | - string 26 | - bool: true 27 | int: 1 28 | double: 1.0 29 | string: string 30 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/RuleDescription+Examples.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | 3 | extension RuleDescription { 4 | func with(nonTriggeringExamples: [String], 5 | triggeringExamples: [String]) -> RuleDescription { 6 | return RuleDescription(identifier: identifier, 7 | name: name, 8 | description: description, 9 | kind: kind, 10 | nonTriggeringExamples: nonTriggeringExamples, 11 | triggeringExamples: triggeringExamples, 12 | corrections: corrections, 13 | deprecatedAliases: deprecatedAliases) 14 | } 15 | 16 | func with(nonTriggeringExamples: [String]) -> RuleDescription { 17 | return with(nonTriggeringExamples: nonTriggeringExamples, 18 | triggeringExamples: triggeringExamples) 19 | } 20 | 21 | func with(triggeringExamples: [String]) -> RuleDescription { 22 | return with(nonTriggeringExamples: nonTriggeringExamples, 23 | triggeringExamples: triggeringExamples) 24 | } 25 | 26 | func with(corrections: [String: String]) -> RuleDescription { 27 | return RuleDescription(identifier: identifier, 28 | name: name, 29 | description: description, 30 | kind: kind, 31 | nonTriggeringExamples: nonTriggeringExamples, 32 | triggeringExamples: triggeringExamples, 33 | corrections: corrections, 34 | deprecatedAliases: deprecatedAliases) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/RulesTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class RulesTests: XCTestCase { 5 | func testLeadingWhitespace() { 6 | verifyRule(LeadingWhitespaceRule.description, skipDisableCommandTests: true, 7 | testMultiByteOffsets: false, testShebang: false) 8 | } 9 | 10 | func testMark() { 11 | verifyRule(MarkRule.description, skipCommentTests: true) 12 | } 13 | 14 | func testRequiredEnumCase() { 15 | let configuration = ["NetworkResponsable": ["notConnected": "error"]] 16 | verifyRule(RequiredEnumCaseRule.description, ruleConfiguration: configuration) 17 | } 18 | 19 | func testTrailingNewline() { 20 | verifyRule(TrailingNewlineRule.description, commentDoesntViolate: false, 21 | stringDoesntViolate: false) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/StatementPositionRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class StatementPositionRuleTests: XCTestCase { 5 | func testStatementPosition() { 6 | verifyRule(StatementPositionRule.description) 7 | } 8 | 9 | func testStatementPositionUncuddled() { 10 | let configuration = ["statement_mode": "uncuddled_else"] 11 | verifyRule(StatementPositionRule.uncuddledDescription, ruleConfiguration: configuration) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 0.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/SwitchCaseAlignmentRuleTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class SwitchCaseAlignmentRuleTests: XCTestCase { 5 | func testWithDefaultConfiguration() { 6 | verifyRule(SwitchCaseAlignmentRule.description) 7 | } 8 | 9 | func testSwitchCaseAlignmentWithoutIndentedCases() { 10 | let baseDescription = SwitchCaseAlignmentRule.description 11 | let examples = SwitchCaseAlignmentRule.Examples(indentedCases: false) 12 | 13 | let description = baseDescription.with(nonTriggeringExamples: examples.nonTriggeringExamples, 14 | triggeringExamples: examples.triggeringExamples) 15 | 16 | verifyRule(description) 17 | } 18 | 19 | func testSwitchCaseAlignmentWithIndentedCases() { 20 | let baseDescription = SwitchCaseAlignmentRule.description 21 | let examples = SwitchCaseAlignmentRule.Examples(indentedCases: true) 22 | 23 | let description = baseDescription.with(nonTriggeringExamples: examples.nonTriggeringExamples, 24 | triggeringExamples: examples.triggeringExamples) 25 | 26 | verifyRule(description, ruleConfiguration: ["indented_cases": true]) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/TodoRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class TodoRuleTests: XCTestCase { 5 | func testTodo() { 6 | verifyRule(TodoRule.description, commentDoesntViolate: false) 7 | } 8 | 9 | func testTodoMessage() { 10 | let string = "fatalError() // TODO: Implement" 11 | let violations = self.violations(string) 12 | XCTAssertEqual(violations.count, 1) 13 | XCTAssertEqual(violations.first!.reason, "TODOs should be resolved (Implement).") 14 | } 15 | 16 | func testFixMeMessage() { 17 | let string = "fatalError() // FIXME: Implement" 18 | let violations = self.violations(string) 19 | XCTAssertEqual(violations.count, 1) 20 | XCTAssertEqual(violations.first!.reason, "FIXMEs should be resolved (Implement).") 21 | } 22 | 23 | private func violations(_ string: String) -> [StyleViolation] { 24 | let config = makeConfig(nil, TodoRule.description.identifier)! 25 | return SwiftLintFrameworkTests.violations(string, config: config) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/TrailingClosureConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class TrailingClosureConfigurationTests: XCTestCase { 5 | func testDefaultConfiguration() { 6 | let config = TrailingClosureConfiguration() 7 | XCTAssertEqual(config.severityConfiguration.severity, .warning) 8 | XCTAssertFalse(config.onlySingleMutedParameter) 9 | } 10 | 11 | func testApplyingCustomConfiguration() throws { 12 | var config = TrailingClosureConfiguration() 13 | try config.apply(configuration: ["severity": "error", 14 | "only_single_muted_parameter": true]) 15 | XCTAssertEqual(config.severityConfiguration.severity, .error) 16 | XCTAssertTrue(config.onlySingleMutedParameter) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/TrailingClosureRuleTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class TrailingClosureRuleTests: XCTestCase { 5 | func testDefaultConfiguration() { 6 | verifyRule(TrailingClosureRule.description) 7 | } 8 | 9 | func testWithOnlySingleMutedParameterEnabled() { 10 | let originalDescription = TrailingClosureRule.description 11 | let description = originalDescription 12 | .with(nonTriggeringExamples: originalDescription.nonTriggeringExamples + [ 13 | "foo.reduce(0, combine: { $0 + 1 })", 14 | "offsets.sorted(by: { $0.offset < $1.offset })", 15 | "foo.something(0, { $0 + 1 })" 16 | ]) 17 | .with(triggeringExamples: ["foo.map({ $0 + 1 })"]) 18 | 19 | verifyRule(description, ruleConfiguration: ["only_single_muted_parameter": true]) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/TrailingWhitespaceTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class TrailingWhitespaceTests: XCTestCase { 5 | func testWithDefaultConfiguration() { 6 | verifyRule(TrailingWhitespaceRule.description) 7 | } 8 | 9 | func testWithIgnoresEmptyLinesEnabled() { 10 | // Perform additional tests with the ignores_empty_lines setting enabled. 11 | // The set of non-triggering examples is extended by a whitespace-indented empty line 12 | let baseDescription = TrailingWhitespaceRule.description 13 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [" \n"] 14 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 15 | 16 | verifyRule(description, 17 | ruleConfiguration: ["ignores_empty_lines": true, "ignores_comments": true]) 18 | } 19 | 20 | func testWithIgnoresCommentsDisabled() { 21 | // Perform additional tests with the ignores_comments settings disabled. 22 | let baseDescription = TrailingWhitespaceRule.description 23 | let triggeringComments = ["// \n", "let name: String // \n"] 24 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples 25 | .filter { !triggeringComments.contains($0) } 26 | let triggeringExamples = baseDescription.triggeringExamples + triggeringComments 27 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 28 | .with(triggeringExamples: triggeringExamples) 29 | verifyRule(description, 30 | ruleConfiguration: ["ignores_empty_lines": false, "ignores_comments": false], 31 | commentDoesntViolate: false) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/TypeNameRuleTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftLintFramework 2 | import XCTest 3 | 4 | class TypeNameRuleTests: XCTestCase { 5 | func testTypeName() { 6 | verifyRule(TypeNameRule.description) 7 | } 8 | 9 | func testTypeNameWithAllowedSymbols() { 10 | let baseDescription = TypeNameRule.description 11 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ 12 | "class MyType$ {}", 13 | "struct MyType$ {}", 14 | "enum MyType$ {}", 15 | "typealias Foo$ = Void", 16 | "protocol Foo {\n associatedtype Bar$\n }" 17 | ] 18 | 19 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 20 | verifyRule(description, ruleConfiguration: ["allowed_symbols": ["$"]]) 21 | } 22 | 23 | func testTypeNameWithAllowedSymbolsAndViolation() { 24 | let baseDescription = TypeNameRule.description 25 | let triggeringExamples = [ 26 | "class ↓My_Type$ {}" 27 | ] 28 | 29 | let description = baseDescription.with(triggeringExamples: triggeringExamples) 30 | verifyRule(description, ruleConfiguration: ["allowed_symbols": ["$", "%"]]) 31 | } 32 | 33 | func testTypeNameWithIgnoreStartWithLowercase() { 34 | let baseDescription = TypeNameRule.description 35 | let triggeringExamplesToRemove = [ 36 | "private typealias ↓foo = Void", 37 | "class ↓myType {}", 38 | "struct ↓myType {}", 39 | "enum ↓myType {}" 40 | ] 41 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + 42 | triggeringExamplesToRemove.map { $0.replacingOccurrences(of: "↓", with: "") } 43 | let triggeringExamples = baseDescription.triggeringExamples 44 | .filter { !triggeringExamplesToRemove.contains($0) } 45 | 46 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 47 | .with(triggeringExamples: triggeringExamples) 48 | 49 | verifyRule(description, ruleConfiguration: ["validates_start_with_lowercase": false]) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/UnusedOptionalBindingRuleTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import SwiftLintFramework 3 | import XCTest 4 | 5 | class UnusedOptionalBindingRuleTests: XCTestCase { 6 | func testDefaultConfiguration() { 7 | let baseDescription = UnusedOptionalBindingRule.description 8 | let triggeringExamples = baseDescription.triggeringExamples + [ 9 | "guard let _ = try? alwaysThrows() else { return }" 10 | ] 11 | 12 | let description = baseDescription.with(triggeringExamples: triggeringExamples) 13 | verifyRule(description) 14 | } 15 | 16 | func testIgnoreOptionalTryEnabled() { 17 | // Perform additional tests with the ignore_optional_try settings enabled. 18 | let baseDescription = UnusedOptionalBindingRule.description 19 | let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [ 20 | "guard let _ = try? alwaysThrows() else { return }" 21 | ] 22 | 23 | let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples) 24 | verifyRule(description, ruleConfiguration: ["ignore_optional_try": true]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/XCTestCase+BundlePath.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | extension XCTestCase { 5 | var testResourcesPath: String { 6 | return URL(fileURLWithPath: #file).deletingLastPathComponent() 7 | .appendingPathComponent("Resources").path.absolutePathStandardized() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/SwiftLintFrameworkTests/YamlParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiftLintFramework 2 | import XCTest 3 | 4 | class YamlParserTests: XCTestCase { 5 | func testParseEmptyString() { 6 | XCTAssertEqual((try YamlParser.parse("", env: [:])).count, 0, 7 | "Parsing empty YAML string should succeed") 8 | } 9 | 10 | func testParseValidString() { 11 | XCTAssertEqual(try YamlParser.parse("a: 1\nb: 2", env: [:]).count, 2, 12 | "Parsing valid YAML string should succeed") 13 | } 14 | 15 | func testParseReplacesEnvVar() throws { 16 | let env = ["PROJECT_NAME": "SwiftLint"] 17 | let string = "excluded:\n - ${PROJECT_NAME}/Extensions" 18 | let result = try YamlParser.parse(string, env: env) 19 | 20 | XCTAssertEqual(result["excluded"] as? [String] ?? [], ["SwiftLint/Extensions"]) 21 | } 22 | 23 | func testParseTreatNoAsString() throws { 24 | let string = "excluded:\n - no" 25 | let result = try YamlParser.parse(string, env: [:]) 26 | 27 | XCTAssertEqual(result["excluded"] as? [String] ?? [], ["no"]) 28 | } 29 | 30 | func testParseTreatYesAsString() throws { 31 | let string = "excluded:\n - yes" 32 | let result = try YamlParser.parse(string, env: [:]) 33 | 34 | XCTAssertEqual(result["excluded"] as? [String] ?? [], ["yes"]) 35 | } 36 | 37 | func testParseTreatOnAsString() throws { 38 | let string = "excluded:\n - on" 39 | let result = try YamlParser.parse(string, env: [:]) 40 | 41 | XCTAssertEqual(result["excluded"] as? [String] ?? [], ["on"]) 42 | } 43 | 44 | func testParseTreatOffAsString() throws { 45 | let string = "excluded:\n - off" 46 | let result = try YamlParser.parse(string, env: [:]) 47 | 48 | XCTAssertEqual(result["excluded"] as? [String] ?? [], ["off"]) 49 | } 50 | 51 | func testParseInvalidStringThrows() { 52 | checkError(YamlParserError.yamlParsing("2:1: error: parser: did not find expected :\na\n^")) { 53 | _ = try YamlParser.parse("|\na", env: [:]) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/custom-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAllen0400/SwiftLint/ad2733391a006fdf82ce90ffe539612813ea8caf/assets/custom-rule.png -------------------------------------------------------------------------------- /assets/presentation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAllen0400/SwiftLint/ad2733391a006fdf82ce90ffe539612813ea8caf/assets/presentation.jpg -------------------------------------------------------------------------------- /assets/realm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAllen0400/SwiftLint/ad2733391a006fdf82ce90ffe539612813ea8caf/assets/realm.png -------------------------------------------------------------------------------- /assets/runscript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAllen0400/SwiftLint/ad2733391a006fdf82ce90ffe539612813ea8caf/assets/runscript.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAllen0400/SwiftLint/ad2733391a006fdf82ce90ffe539612813ea8caf/assets/screenshot.png -------------------------------------------------------------------------------- /script/LICENSE.md: -------------------------------------------------------------------------------- 1 | **Copyright (c) 2013 Justin Spahr-Summers** 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /script/Version.swift.template: -------------------------------------------------------------------------------- 1 | public struct Version { 2 | public let value: String 3 | 4 | public static let current = Version(value: "__VERSION__") 5 | } 6 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export SCRIPT_DIR=$(dirname "$0") 4 | 5 | ## 6 | ## Bootstrap Process 7 | ## 8 | 9 | main () 10 | { 11 | submodules=$(git submodule status) 12 | local result=$? 13 | 14 | if [ "$result" -ne "0" ] 15 | then 16 | exit $result 17 | fi 18 | 19 | if [ -n "$submodules" ] 20 | then 21 | echo "*** Updating submodules..." 22 | update_submodules 23 | fi 24 | } 25 | 26 | bootstrap_submodule () 27 | { 28 | local bootstrap="script/bootstrap" 29 | 30 | if [ -e "$bootstrap" ] 31 | then 32 | echo "*** Bootstrapping $name..." 33 | "$bootstrap" >/dev/null 34 | else 35 | update_submodules 36 | fi 37 | } 38 | 39 | update_submodules () 40 | { 41 | git submodule sync --quiet && git submodule update --init && git submodule foreach --quiet bootstrap_submodule 42 | } 43 | 44 | export -f bootstrap_submodule 45 | export -f update_submodules 46 | 47 | main 48 | -------------------------------------------------------------------------------- /script/check-xcode-version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Check the minimum required Xcode version. Meant to be run 4 | # as part of an Xcode Run Script build phase. 5 | 6 | required_xcode_version=6.1.1 7 | 8 | if [ ${XCODE_VERSION_ACTUAL} -lt ${required_xcode_version//.} ] 9 | then 10 | echo "error: Xcode ${required_xcode_version} or later is required to build ${PRODUCT_NAME}" 11 | exit 1 12 | fi 13 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | make test package 2 | -------------------------------------------------------------------------------- /script/extract-tool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Extracts the swiftlint CLI tool from its application bundle. Meant to be run 4 | # as part of an Xcode Run Script build phase. 5 | 6 | cp -v "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_NAME}" 7 | --------------------------------------------------------------------------------