├── .swift-version
├── script
├── cibuild
├── Version.swift.template
├── extract-tool
├── check-xcode-version
├── bootstrap
└── LICENSE.md
├── .gitattributes
├── Tests
└── SwiftLintFrameworkTests
│ ├── Resources
│ ├── test.txt
│ ├── ProjectMock
│ │ ├── Level0.swift
│ │ ├── Level1
│ │ │ ├── Level1.swift
│ │ │ └── Level2
│ │ │ │ ├── Level2.swift
│ │ │ │ ├── Level3
│ │ │ │ ├── Level3.swift
│ │ │ │ └── .swiftlint.yml
│ │ │ │ ├── custom_rules.yml
│ │ │ │ ├── custom_rules_disabled.yml
│ │ │ │ └── .swiftlint.yml
│ │ ├── Directory.swift
│ │ │ └── DirectoryLevel1.swift
│ │ ├── custom_rules.yml
│ │ ├── .swiftlint.yml
│ │ └── custom.yml
│ ├── FileNameRuleFixtures
│ │ ├── main.swift
│ │ ├── MyClass.swift
│ │ ├── MyStruct.swift
│ │ ├── MyStructf.swift
│ │ ├── Notification.Name+Extension.swift
│ │ ├── BoolExtension.swift
│ │ ├── BoolExtensions.swift
│ │ ├── ExtensionBool.swift
│ │ ├── ExtensionsBool.swift
│ │ ├── SLBoolExtension.swift
│ │ ├── NSString+Extension.swift
│ │ ├── ExtensionBool+SwiftLint.swift
│ │ ├── BoolExtensionTests.swift
│ │ └── LinuxMain.swift
│ ├── FileHeaderRuleFixtures
│ │ ├── FileNameMissing.swift
│ │ ├── FileNameMismatch.swift
│ │ ├── FileNameCaseMismatch.swift
│ │ ├── FileNameMatchingSimple.swift
│ │ └── FileNameMatchingComplex.swift
│ ├── CannedEmojiReporterOutput.txt
│ ├── CannedCSVReporterOutput.csv
│ ├── CannedXcodeReporterOutput.txt
│ ├── test.yml
│ ├── CannedMarkdownReporterOutput.md
│ ├── CannedCheckstyleReporterOutput.xml
│ ├── CannedJunitReporterOutput.xml
│ ├── CannedJSONReporterOutput.json
│ └── CannedSonarQubeReporterOutput.json
│ ├── XCTestCase+BundlePath.swift
│ ├── StatementPositionRuleTests.swift
│ ├── TrailingClosureConfigurationTests.swift
│ ├── RulesTests.swift
│ ├── Supporting Files
│ └── Info.plist
│ ├── TrailingClosureRuleTests.swift
│ ├── PrefixedTopLevelConstantRuleTests.swift
│ ├── DocumentationTests.swift
│ ├── PrivateOutletRuleTests.swift
│ ├── TodoRuleTests.swift
│ ├── FileLengthRuleTests.swift
│ ├── UnusedOptionalBindingRuleTests.swift
│ ├── ExtendedNSStringTests.swift
│ ├── CompilerProtocolInitRuleTests.swift
│ ├── CollectionAlignmentRuleTests.swift
│ ├── SwitchCaseAlignmentRuleTests.swift
│ ├── ContainsOverFirstNotNilRuleTests.swift
│ ├── ImplicitlyUnwrappedOptionalRuleTests.swift
│ ├── ConditionalReturnsOnNewlineRuleTests.swift
│ ├── DeploymentTargetRuleTests.swift
│ ├── DiscouragedDirectInitRuleTests.swift
│ ├── PrivateOverFilePrivateRuleTests.swift
│ ├── ConfigurationAliasesTests.swift
│ ├── RuleDescription+Examples.swift
│ ├── TrailingWhitespaceTests.swift
│ ├── DiscouragedObjectLiteralRuleTests.swift
│ ├── YamlParserTests.swift
│ ├── ImplicitlyUnwrappedOptionalConfigurationTests.swift
│ └── TypeNameRuleTests.swift
├── assets
├── realm.png
├── runscript.png
├── screenshot.png
├── custom-rule.png
└── presentation.jpg
├── Cartfile
├── Gemfile
├── Cartfile.private
├── Source
├── SwiftLintFramework
│ ├── Models
│ │ ├── ConfigurationError.swift
│ │ ├── Version.swift
│ │ ├── RuleKind.swift
│ │ ├── Correction.swift
│ │ ├── RuleParameter.swift
│ │ ├── ViolationSeverity.swift
│ │ ├── StyleViolation.swift
│ │ ├── RuleIdentifier.swift
│ │ ├── Region.swift
│ │ ├── RuleDescription.swift
│ │ ├── AccessControlLevel.swift
│ │ └── YamlParser.swift
│ ├── Protocols
│ │ ├── CacheDescriptionProvider.swift
│ │ ├── RuleConfiguration.swift
│ │ ├── ASTRule.swift
│ │ └── Reporter.swift
│ ├── Extensions
│ │ ├── NSRange+SwiftLint.swift
│ │ ├── SwiftExpressionKind.swift
│ │ ├── CharacterSet+SwiftLint.swift
│ │ ├── Request+DisableSourceKit.swift
│ │ ├── Configuration+IndentationStyle.swift
│ │ ├── String+XML.swift
│ │ ├── SyntaxMap+SwiftLint.swift
│ │ ├── SwiftDeclarationKind+SwiftLint.swift
│ │ ├── SyntaxKind+SwiftLint.swift
│ │ ├── NSRegularExpression+SwiftLint.swift
│ │ ├── QueuedPrint.swift
│ │ ├── FileManager+SwiftLint.swift
│ │ ├── Structure+SwiftLint.swift
│ │ └── Configuration+LintableFiles.swift
│ ├── Helpers
│ │ ├── RegexHelpers.swift
│ │ ├── Glob.swift
│ │ └── NamespaceCollector.swift
│ ├── Rules
│ │ ├── Lint
│ │ │ ├── NotificationCenterDetachmentRuleExamples.swift
│ │ │ ├── SuperfluousDisableCommandRule.swift
│ │ │ ├── StrongIBOutletRule.swift
│ │ │ ├── NSLocalizedStringKeyRule.swift
│ │ │ ├── ProhibitedInterfaceBuilderRule.swift
│ │ │ └── DiscouragedDirectInitRule.swift
│ │ ├── RuleConfigurations
│ │ │ ├── ConditionalReturnsOnNewlineConfiguration.swift
│ │ │ ├── CollectionAlignmentConfiguration.swift
│ │ │ ├── SwitchCaseAlignmentConfiguration.swift
│ │ │ ├── PrivateOverFilePrivateRuleConfiguration.swift
│ │ │ ├── PrivateOutletRuleConfiguration.swift
│ │ │ ├── SeverityConfiguration.swift
│ │ │ ├── PrefixedConstantRuleConfiguration.swift
│ │ │ ├── TrailingCommaConfiguration.swift
│ │ │ ├── VerticalWhitespaceConfiguration.swift
│ │ │ ├── ObjectLiteralConfiguration.swift
│ │ │ ├── TrailingClosureConfiguration.swift
│ │ │ ├── UnusedOptionalBindingConfiguration.swift
│ │ │ ├── ColonConfiguration.swift
│ │ │ ├── TrailingWhitespaceConfiguration.swift
│ │ │ ├── DiscouragedDirectInitConfiguration.swift
│ │ │ ├── AttributesConfiguration.swift
│ │ │ ├── ModifierOrderConfiguration.swift
│ │ │ ├── StatementModeConfiguration.swift
│ │ │ ├── ImplicitlyUnwrappedOptionalConfiguration.swift
│ │ │ ├── FileNameConfiguration.swift
│ │ │ ├── SeverityLevelsConfiguration.swift
│ │ │ ├── MissingDocsRuleConfiguration.swift
│ │ │ ├── NestingConfiguration.swift
│ │ │ ├── ProhibitedSuperConfiguration.swift
│ │ │ └── FileTypesOrderConfiguration.swift
│ │ ├── Idiomatic
│ │ │ ├── ForceCastRule.swift
│ │ │ ├── ForceTryRule.swift
│ │ │ ├── FallthroughRule.swift
│ │ │ ├── DiscouragedOptionalBooleanRule.swift
│ │ │ ├── ToggleBoolRule.swift
│ │ │ ├── IsDisjointRule.swift
│ │ │ ├── DuplicateImportsRuleExamples.swift
│ │ │ ├── NoExtensionAccessModifierRule.swift
│ │ │ ├── LegacyConstantRuleExamples.swift
│ │ │ ├── TrailingSemicolonRule.swift
│ │ │ ├── RedundantNilCoalescingRule.swift
│ │ │ ├── LegacyConstantRule.swift
│ │ │ ├── DiscouragedObjectLiteralRule.swift
│ │ │ └── LegacyRandomRule.swift
│ │ ├── Performance
│ │ │ ├── EmptyStringRule.swift
│ │ │ └── EmptyCountRule.swift
│ │ ├── Style
│ │ │ ├── IdentifierNameRuleExamples.swift
│ │ │ ├── MultilineArgumentsRuleExamples.swift
│ │ │ ├── ClosingBraceRule.swift
│ │ │ └── ProtocolPropertyAccessorsOrderRule.swift
│ │ └── Metrics
│ │ │ ├── FunctionBodyLengthRule.swift
│ │ │ └── FileLengthRule.swift
│ ├── Supporting Files
│ │ └── Info.plist
│ └── Reporters
│ │ ├── XcodeReporter.swift
│ │ ├── JSONReporter.swift
│ │ ├── JUnitReporter.swift
│ │ ├── SonarQubeReporter.swift
│ │ ├── EmojiReporter.swift
│ │ ├── CSVReporter.swift
│ │ ├── CheckstyleReporter.swift
│ │ └── MarkdownReporter.swift
└── swiftlint
│ ├── Commands
│ ├── VersionCommand.swift
│ └── GenerateDocsCommand.swift
│ ├── Extensions
│ └── Reporter+CommandLine.swift
│ ├── main.swift
│ ├── Supporting Files
│ └── Info.plist
│ └── Helpers
│ ├── CommonOptions.swift
│ └── Benchmark.swift
├── SwiftLint.xcodeproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── .sourcery
├── MasterRuleList.stencil
├── AutomaticRuleTests.stencil
└── LinuxMain.stencil
├── Cartfile.resolved
├── SwiftLint.xcworkspace
├── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── contents.xcworkspacedata
├── SwiftLint.podspec
├── SwiftLintFramework.podspec
├── .github
└── ISSUE_TEMPLATE
│ ├── rule-request.md
│ └── bug_report.md
├── .gitmodules
├── Releasing.md
├── LICENSE
├── .gitignore
└── Package.swift
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.2.1
2 |
--------------------------------------------------------------------------------
/script/cibuild:
--------------------------------------------------------------------------------
1 | make test package
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | CHANGELOG.md merge=union
2 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/test.txt:
--------------------------------------------------------------------------------
1 | // My file with
2 | // a pattern
3 |
--------------------------------------------------------------------------------
/assets/realm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAllen0400/SwiftLint/HEAD/assets/realm.png
--------------------------------------------------------------------------------
/assets/runscript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAllen0400/SwiftLint/HEAD/assets/runscript.png
--------------------------------------------------------------------------------
/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAllen0400/SwiftLint/HEAD/assets/screenshot.png
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "jpsim/SourceKitten" ~> 0.22.0
2 | github "scottrhoyt/SwiftyTextTable" ~> 0.8.2
3 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level0.swift:
--------------------------------------------------------------------------------
1 | // This is just a mock Swift file
2 |
--------------------------------------------------------------------------------
/assets/custom-rule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAllen0400/SwiftLint/HEAD/assets/custom-rule.png
--------------------------------------------------------------------------------
/assets/presentation.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAllen0400/SwiftLint/HEAD/assets/presentation.jpg
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/main.swift:
--------------------------------------------------------------------------------
1 | struct A {}
2 |
3 | print("hello")
4 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level1.swift:
--------------------------------------------------------------------------------
1 | // This is just a mock Swift file
2 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyClass.swift:
--------------------------------------------------------------------------------
1 | struct MyStruct {}
2 | class MyClass {}
3 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/Level2.swift:
--------------------------------------------------------------------------------
1 | // This is just a mock Swift file
2 |
--------------------------------------------------------------------------------
/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/ProjectMock/Level1/Level2/Level3/Level3.swift:
--------------------------------------------------------------------------------
1 | // This is just a mock Swift file
2 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Directory.swift/DirectoryLevel1.swift:
--------------------------------------------------------------------------------
1 | // This is just a mock Swift file
2 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMissing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2016
3 | struct A {}
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | source "https://rubygems.org"
3 |
4 | gem "danger"
5 | gem "cocoapods", "~> 1.6.0.beta"
6 |
--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
1 | github "Carthage/Commandant" ~> 0.15.0
2 | github "jpsim/Yams" ~> 1.0.1
3 | github "jspahrsummers/xcconfigs" ~> 0.12.0
4 |
--------------------------------------------------------------------------------
/Source/SwiftLintFramework/Models/ConfigurationError.swift:
--------------------------------------------------------------------------------
1 | public enum ConfigurationError: Error {
2 | case unknownConfiguration
3 | }
4 |
--------------------------------------------------------------------------------
/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/FileHeaderRuleFixtures/FileNameMismatch.swift:
--------------------------------------------------------------------------------
1 | // AFileNameMismatch.swift
2 | // Copyright © 2016
3 | struct A {}
4 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameCaseMismatch.swift:
--------------------------------------------------------------------------------
1 | // FileNameCaseMismatch.Swift
2 | // Copyright © 2016
3 | struct A {}
4 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Notification.Name+Extension.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Notification.Name {
4 | }
5 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/ProjectMock/Level1/Level2/custom_rules.yml:
--------------------------------------------------------------------------------
1 | custom_rules:
2 | no_abcd:
3 | name: "Don't use abcd"
4 | regex: 'abcd'
--------------------------------------------------------------------------------
/Source/SwiftLintFramework/Protocols/CacheDescriptionProvider.swift:
--------------------------------------------------------------------------------
1 | internal protocol CacheDescriptionProvider {
2 | var cacheDescription: String { get }
3 | }
4 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMatchingSimple.swift:
--------------------------------------------------------------------------------
1 | // FileNameMatchingSimple.swift
2 | // Copyright © 2016
3 | struct A {}
4 |
--------------------------------------------------------------------------------
/script/Version.swift.template:
--------------------------------------------------------------------------------
1 | public struct Version {
2 | public let value: String
3 |
4 | public static let current = Version(value: "__VERSION__")
5 | }
6 |
--------------------------------------------------------------------------------
/Tests/SwiftLintFrameworkTests/Resources/FileHeaderRuleFixtures/FileNameMatchingComplex.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2016
2 | // File: "FileNameMatchingComplex.swift"
3 | struct A {}
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/BoolExtensions.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/SLBoolExtension.swift:
--------------------------------------------------------------------------------
1 | struct MyStruct {}
2 | class MyClass {}
3 |
4 | extension Bool {
5 | func toggle() {}
6 | }
7 |
--------------------------------------------------------------------------------
/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/ExtensionBool+SwiftLint.swift:
--------------------------------------------------------------------------------
1 | struct MyStruct {}
2 | class MyClass {}
3 |
4 | extension Bool {
5 | func toggle() {}
6 | }
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/SwiftLint.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/SwiftLint.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/SwiftLint.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 %}
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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/Resources/CannedCheckstyleReporterOutput.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/SwiftLint.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
18 |
19 |
21 |
22 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | ]
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.. = 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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | guard let config = makeConfig(config, DeploymentTargetRule.description.identifier) else {
33 | return []
34 | }
35 |
36 | return SwiftLintFrameworkTests.violations(string, config: config)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/Rules/Idiomatic/TrailingSemicolonRule.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SourceKittenFramework
3 |
4 | public struct TrailingSemicolonRule: SubstitutionCorrectableRule, ConfigurationProviderRule, AutomaticTestableRule {
5 | public var configuration = SeverityConfiguration(.warning)
6 |
7 | public init() {}
8 |
9 | public static let description = RuleDescription(
10 | identifier: "trailing_semicolon",
11 | name: "Trailing Semicolon",
12 | description: "Lines should not have trailing semicolons.",
13 | kind: .idiomatic,
14 | nonTriggeringExamples: [ "let a = 0\n" ],
15 | triggeringExamples: [
16 | "let a = 0↓;\n",
17 | "let a = 0↓;\nlet b = 1\n",
18 | "let a = 0↓;;\n",
19 | "let a = 0↓; ;;\n",
20 | "let a = 0↓; ; ;\n"
21 | ],
22 | corrections: [
23 | "let a = 0↓;\n": "let a = 0\n",
24 | "let a = 0↓;\nlet b = 1\n": "let a = 0\nlet b = 1\n",
25 | "let a = 0↓;;\n": "let a = 0\n",
26 | "let a = 0↓; ;;\n": "let a = 0\n",
27 | "let a = 0↓; ; ;\n": "let a = 0\n"
28 | ]
29 | )
30 |
31 | public func validate(file: File) -> [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/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/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/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/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/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/Helpers/NamespaceCollector.swift:
--------------------------------------------------------------------------------
1 | import SourceKittenFramework
2 |
3 | struct NamespaceCollector {
4 | struct Element {
5 | let name: String
6 | let kind: SwiftDeclarationKind
7 | let offset: Int
8 | let dictionary: [String: SourceKitRepresentable]
9 |
10 | init?(dictionary: [String: SourceKitRepresentable], namespace: [String]) {
11 | guard let name = dictionary.name,
12 | let kind = dictionary.kind.flatMap(SwiftDeclarationKind.init),
13 | let offset = dictionary.offset else {
14 | return nil
15 | }
16 |
17 | self.name = (namespace + [name]).joined(separator: ".")
18 | self.kind = kind
19 | self.offset = offset
20 | self.dictionary = dictionary
21 | }
22 | }
23 |
24 | private let dictionary: [String: SourceKitRepresentable]
25 |
26 | init(dictionary: [String: SourceKitRepresentable]) {
27 | self.dictionary = dictionary
28 | }
29 |
30 | func findAllElements(of types: Set,
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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------