diff --git a/CHANGELOG.md b/CHANGELOG.md index e33ba0b341..efe55e30cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ configuration files. [JP Simard](https://github.com/jpsim) +#### Experimental + +* Add a new `swiftlint analyze` command which can lint Swift files using the + full type-checked AST. Rules of the `AnalyzerRule` type will be added over + time. The compiler log path containing the clean `swiftc` build command + invocation (incremental builds will fail) must be passed to `analyze` via + the `--compiler-log-path` flag. + e.g. `--compiler-log-path /path/to/xcodebuild.log` + [JP Simard](https://github.com/jpsim) + +* Add a new opt-in `explicit_self` analyzer rule to enforce the use of explicit + references to `self.` when accessing instance variables or functions. + [JP Simard](https://github.com/jpsim) + #### Enhancements * Improve performance of `line_length` and diff --git a/README.md b/README.md index cd7ba0bc99..253ed603d5 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ swiftlint( $ swiftlint help Available commands: + analyze [Experimental] Run analysis rules autocorrect Automatically correct warnings and errors generate-docs Generates markdown documentation for all rules help Display general or command-specific help @@ -153,8 +154,8 @@ Available commands: Run `swiftlint` in the directory containing the Swift files to lint. Directories will be searched recursively. -To specify a list of files when using `lint` or `autocorrect` (like the list of -files modified by Xcode specified by the +To specify a list of files when using `lint`, `autocorrect` or `analyze` +(like the list of files modified by Xcode specified by the [`ExtraBuildPhase`](https://github.com/norio-nomura/ExtraBuildPhase) Xcode plugin, or modified files in the working tree based on `git ls-files -m`), you can do so by passing the option `--use-script-input-files` and setting the @@ -304,6 +305,9 @@ Rule inclusion: * `whitelist_rules`: Acts as a whitelist, only the rules specified in this list will be enabled. Can not be specified alongside `disabled_rules` or `opt_in_rules`. +* `analyzer_rules`: This is an entirely separate list of rules that are only + run by the `analyze` command. All analyzer rules are opt-in, so this is the + only configurable rule list (there is no disabled/whitelist equivalent). ```yaml disabled_rules: # rule identifiers to exclude from running @@ -322,6 +326,8 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. - Source/ExcludedFolder - Source/ExcludedFile.swift - Source/*/ExcludedFile.swift # Exclude files with a wildcard +analyzer_rules: # Rules run by `swiftlint analyze` (experimental) + - explicit_self # configurable rules can be customized from this configuration file # binary rules can set their severity level @@ -437,6 +443,18 @@ Standard linting is disabled while correcting because of the high likelihood of violations (or their offsets) being incorrect after modifying a file while applying corrections. +### Analyze (experimental) + +The _experimental_ `swiftlint analyze` command can lint Swift files using the +full type-checked AST. The compiler log path containing the clean `swiftc` build +command invocation (incremental builds will fail) must be passed to `analyze` +via the `--compiler-log-path` flag. +e.g. `--compiler-log-path /path/to/xcodebuild.log` + +This command and related code in SwiftLint is subject to substantial changes at +any time while this feature is marked as experimental. Analyzer rules also tend +to be considerably slower than lint rules. + ## License [MIT licensed.](LICENSE) diff --git a/Rules.md b/Rules.md index f28ab2a56a..e54874e420 100644 --- a/Rules.md +++ b/Rules.md @@ -35,6 +35,7 @@ * [Explicit ACL](#explicit-acl) * [Explicit Enum Raw Value](#explicit-enum-raw-value) * [Explicit Init](#explicit-init) +* [Explicit Self](#explicit-self) * [Explicit Top Level ACL](#explicit-top-level-acl) * [Explicit Type Interface](#explicit-type-interface) * [Extension Access Modifier](#extension-access-modifier) @@ -148,9 +149,9 @@ ## AnyObject Protocol -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`anyobject_protocol` | Disabled | Yes | lint | 4.1.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`anyobject_protocol` | Disabled | Yes | lint | No | 4.1.0 Prefer using `AnyObject` over `class` for class-only protocols. @@ -204,9 +205,9 @@ protocol SomeClassOnlyProtocol: ↓class, SomeInheritedProtocol {} ## Array Init -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`array_init` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`array_init` | Disabled | No | lint | No | 3.0.0 Prefer using `Array(seq)` over `seq.map { $0 }` to convert a sequence into an Array. @@ -313,9 +314,9 @@ foo.something { RouteMapper.map($0) } ## Attributes -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`attributes` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`attributes` | Disabled | No | style | No | 3.0.0 Attributes should be on their own lines in functions and types, but on the same line as variables and imports. @@ -675,9 +676,9 @@ class DeleteMeTests: XCTestCase { ## Block Based KVO -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`block_based_kvo` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`block_based_kvo` | Enabled | No | idiomatic | No | 3.0.0 Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. @@ -718,9 +719,9 @@ class Foo: NSObject { ## Class Delegate Protocol -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`class_delegate_protocol` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`class_delegate_protocol` | Enabled | No | lint | No | 3.0.0 Delegate protocols should be class-only so they can be weakly referenced. @@ -795,9 +796,9 @@ protocol FooDelegate: NSObjectProtocol {} ## Closing Brace Spacing -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`closing_brace` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`closing_brace` | Enabled | Yes | style | No | 3.0.0 Closing brace with closing parenthesis should not have any whitespaces in the middle. @@ -834,9 +835,9 @@ Closing brace with closing parenthesis should not have any whitespaces in the mi ## Closure Body Length -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`closure_body_length` | Disabled | No | metrics | 4.2.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`closure_body_length` | Disabled | No | metrics | No | 4.2.0 Closure bodies should not span too many lines. @@ -1387,9 +1388,9 @@ let foo: Bar = ↓{ toto in ## Closure End Indentation -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`closure_end_indentation` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`closure_end_indentation` | Disabled | Yes | style | No | 3.0.0 Closure end should have the same indentation as the line that started it. @@ -1500,9 +1501,9 @@ function( ## Closure Parameter Position -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`closure_parameter_position` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`closure_parameter_position` | Enabled | No | style | No | 3.0.0 Closure parameters should be on the same line as opening brace. @@ -1650,9 +1651,9 @@ let mediaView: UIView = { [weak self] index in ## Closure Spacing -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`closure_spacing` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`closure_spacing` | Disabled | Yes | style | No | 3.0.0 Closure expressions should have a single space inside each brace. @@ -1703,9 +1704,9 @@ filter ↓{ sorted ↓{ $0 < $1}} ## Colon -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`colon` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`colon` | Enabled | Yes | style | No | 3.0.0 Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. @@ -2114,9 +2115,9 @@ func foo() { let dict = [1↓ : 1] } ## Comma Spacing -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`comma` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`comma` | Enabled | Yes | style | No | 3.0.0 There should be no space before and one after any comma. @@ -2193,9 +2194,9 @@ let result = plus( ## Compiler Protocol Init -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`compiler_protocol_init` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`compiler_protocol_init` | Enabled | No | lint | No | 3.0.0 The initializers declared in compiler protocols such as `ExpressibleByArrayLiteral` shouldn't be called directly. @@ -2234,9 +2235,9 @@ let set = ↓Set.init(arrayLiteral: 1, 2) ## Conditional Returns on Newline -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`conditional_returns_on_newline` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`conditional_returns_on_newline` | Disabled | No | style | No | 3.0.0 Conditional statements should always return on the next line @@ -2313,9 +2314,9 @@ if true { // return } ## Contains over first not nil -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`contains_over_first_not_nil` | Disabled | No | performance | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`contains_over_first_not_nil` | Disabled | No | performance | No | 3.0.0 Prefer `contains` over `first(where:) != nil` @@ -2374,9 +2375,9 @@ let first = myList.first { $0 % 2 == 0 } ## Control Statement -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`control_statement` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`control_statement` | Enabled | No | style | No | 3.0.0 `if`, `for`, `guard`, `switch`, `while`, and `catch` statements shouldn't unnecessarily wrap their conditionals or arguments in parentheses. @@ -2586,9 +2587,9 @@ do { ## Convenience Type -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`convenience_type` | Disabled | No | idiomatic | 4.1.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`convenience_type` | Disabled | No | idiomatic | No | 4.1.0 Types used for hosting only static members should be implemented as a caseless enum to avoid instantiation. @@ -2656,9 +2657,9 @@ class DummyClass {} ## Custom Rules -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`custom_rules` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`custom_rules` | Enabled | No | style | No | 3.0.0 Create custom rules by providing a regex string. Optionally specify what syntax kinds to match against, the severity level, and what message to display. @@ -2666,9 +2667,9 @@ Create custom rules by providing a regex string. Optionally specify what syntax ## Cyclomatic Complexity -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`cyclomatic_complexity` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`cyclomatic_complexity` | Enabled | No | metrics | No | 3.0.0 Complexity of function bodies should be limited. @@ -2742,9 +2743,9 @@ if true {}; if true {}; if true {}; if true {}; if true {} ## Discarded Notification Center Observer -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`discarded_notification_center_observer` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`discarded_notification_center_observer` | Enabled | No | lint | No | 3.0.0 When registering for a notification using a block, the opaque observer that is returned should be stored so it can be removed later. @@ -2797,9 +2798,9 @@ func foo() -> Any { ## Discouraged Direct Initialization -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`discouraged_direct_init` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`discouraged_direct_init` | Enabled | No | lint | No | 3.0.0 Discouraged direct initialization of types that can be harmful. @@ -2882,9 +2883,9 @@ let foo = bar(bundle: ↓Bundle.init(), device: ↓UIDevice.init()) ## Discouraged Object Literal -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`discouraged_object_literal` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`discouraged_object_literal` | Disabled | No | idiomatic | No | 3.0.0 Prefer initializers over object literals. @@ -2935,9 +2936,9 @@ let color = ↓#colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200 ## Discouraged Optional Boolean -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`discouraged_optional_boolean` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`discouraged_optional_boolean` | Disabled | No | idiomatic | No | 3.0.0 Prefer non-optional booleans over optional booleans. @@ -3568,9 +3569,9 @@ enum Foo { ## Discouraged Optional Collection -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`discouraged_optional_collection` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`discouraged_optional_collection` | Disabled | No | idiomatic | No | 3.0.0 Prefer empty collection over optional collection. @@ -4483,9 +4484,9 @@ enum Foo { ## Dynamic Inline -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`dynamic_inline` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`dynamic_inline` | Enabled | No | lint | No | 3.0.0 Avoid using 'dynamic' and '@inline(__always)' together. @@ -4552,9 +4553,9 @@ dynamic ## Empty Count -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`empty_count` | Disabled | No | performance | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`empty_count` | Disabled | No | performance | No | 3.0.0 Prefer checking `isEmpty` over comparing `count` to zero. @@ -4623,9 +4624,9 @@ order.discount == 0 ## Empty Enum Arguments -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`empty_enum_arguments` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`empty_enum_arguments` | Enabled | Yes | style | No | 3.0.0 Arguments can be omitted when matching enums with associated types if they are not used. @@ -4725,9 +4726,9 @@ func example(foo: Foo) { ## Empty Parameters -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`empty_parameters` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`empty_parameters` | Enabled | Yes | style | No | 3.0.0 Prefer `() -> ` over `Void -> `. @@ -4796,9 +4797,9 @@ let foo: ↓(Void) -> () throws -> Void) ## Empty Parentheses with Trailing Closure -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`empty_parentheses_with_trailing_closure` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`empty_parentheses_with_trailing_closure` | Enabled | Yes | style | No | 3.0.0 When using trailing closures, empty parentheses should be avoided after the method call. @@ -4882,9 +4883,9 @@ func foo() -> [Int] { ## Empty String -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`empty_string` | Disabled | No | performance | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`empty_string` | Disabled | No | performance | No | 3.0.0 Prefer checking `isEmpty` over comparing `string` to an empty string literal. @@ -4919,9 +4920,9 @@ myString↓ != "" ## Empty XCTest Method -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`empty_xctest_method` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`empty_xctest_method` | Disabled | No | lint | No | 3.0.0 Empty XCTest method should be avoided. @@ -5079,9 +5080,9 @@ class BarTests: XCTestCase { ## Explicit ACL -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`explicit_acl` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`explicit_acl` | Disabled | No | idiomatic | No | 3.0.0 All declarations should specify Access Control Level keywords explicitly. @@ -5194,9 +5195,9 @@ func b() {} ## Explicit Enum Raw Value -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`explicit_enum_raw_value` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`explicit_enum_raw_value` | Disabled | No | idiomatic | No | 3.0.0 Enums should be explicitly assigned their raw values. @@ -5291,9 +5292,9 @@ enum Numbers: Decimal { ## Explicit Init -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`explicit_init` | Disabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`explicit_init` | Disabled | Yes | idiomatic | No | 3.0.0 Explicitly calling .init() should be avoided. @@ -5344,11 +5345,68 @@ func foo() -> [String] { +## Explicit Self + +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`explicit_self` | Disabled | Yes | style | Yes | 3.0.0 + +Instance variables and functions should be explicitly accessed with 'self.'. + +### Examples + +
+Non Triggering Examples + +```swift +struct A { + func f1() {} + func f2() { + self.f1() + } +} +``` + +```swift +struct A { + let p1: Int + func f1() { + _ = self.p1 + } +} +``` + +
+
+Triggering Examples + +```swift +struct A { + func f1() {} + func f2() { + ↓f1() + } +} +``` + +```swift +struct A { + let p1: Int + func f1() { + _ = ↓p1 + } +} +``` + +
+ + + ## Explicit Top Level ACL -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`explicit_top_level_acl` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`explicit_top_level_acl` | Disabled | No | idiomatic | No | 3.0.0 Top-level declarations should specify Access Control Level keywords explicitly. @@ -5428,9 +5486,9 @@ func b() {} ## Explicit Type Interface -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`explicit_type_interface` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`explicit_type_interface` | Disabled | No | idiomatic | No | 3.0.0 Properties should have a type interface @@ -5521,9 +5579,9 @@ class Foo { ## Extension Access Modifier -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`extension_access_modifier` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`extension_access_modifier` | Disabled | No | idiomatic | No | 3.0.0 Prefer to use extension access modifiers @@ -5611,9 +5669,9 @@ public extension Foo { ## Fallthrough -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`fallthrough` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`fallthrough` | Disabled | No | idiomatic | No | 3.0.0 Fallthrough should be avoided. @@ -5648,9 +5706,9 @@ case .bar2: ## Fatal Error Message -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`fatal_error_message` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`fatal_error_message` | Disabled | No | idiomatic | No | 3.0.0 A fatalError call should have a message. @@ -5697,9 +5755,9 @@ func foo() { ## File Header -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`file_header` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`file_header` | Disabled | No | style | No | 3.0.0 Header comments should be consistent with project patterns. The SWIFTLINT_CURRENT_FILENAME placeholder can optionally be used in the required and forbidden patterns. It will be replaced by the real file name. @@ -5751,9 +5809,9 @@ let foo = 2 ## File Line Length -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`file_length` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`file_length` | Enabled | No | metrics | No | 3.0.0 Files should not span too many lines. @@ -6986,9 +7044,9 @@ print("swiftlint") ## File Name -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`file_name` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`file_name` | Disabled | No | idiomatic | No | 3.0.0 File name should match a type or extension declared in the file (if any). @@ -6996,9 +7054,9 @@ File name should match a type or extension declared in the file (if any). ## First Where -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`first_where` | Disabled | No | performance | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`first_where` | Disabled | No | performance | No | 3.0.0 Prefer using `.first(where:)` over `.filter { }.first` in collections. @@ -7073,9 +7131,9 @@ match(pattern: pattern).filter { $0.first == .identifier } ## For Where -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`for_where` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`for_where` | Enabled | No | idiomatic | No | 3.0.0 `where` clauses are preferred over a single `if` inside a `for`. @@ -7175,9 +7233,9 @@ for user in users { ## Force Cast -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`force_cast` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`force_cast` | Enabled | No | idiomatic | No | 3.0.0 Force casts should be avoided. @@ -7206,9 +7264,9 @@ NSNumber() ↓as! Int ## Force Try -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`force_try` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`force_try` | Enabled | No | idiomatic | No | 3.0.0 Force tries should be avoided. @@ -7235,9 +7293,9 @@ func a() throws {}; ↓try! a() ## Force Unwrapping -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`force_unwrapping` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`force_unwrapping` | Disabled | No | idiomatic | No | 3.0.0 Force unwrapping should be avoided. @@ -7378,9 +7436,9 @@ open var computed: String { return foo.bar↓! } ## Function Body Length -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`function_body_length` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`function_body_length` | Enabled | No | metrics | No | 3.0.0 Functions bodies should not span too many lines. @@ -7388,9 +7446,9 @@ Functions bodies should not span too many lines. ## Function Default Parameter at End -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`function_default_parameter_at_end` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`function_default_parameter_at_end` | Disabled | No | idiomatic | No | 3.0.0 Prefer to locate parameters with defaults toward the end of the parameter list. @@ -7452,9 +7510,9 @@ func foo(a: Int, b: CGFloat = 0) { ## Function Parameter Count -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`function_parameter_count` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`function_parameter_count` | Enabled | No | metrics | No | 3.0.0 Number of function parameters should be low. @@ -7532,9 +7590,9 @@ init(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {} ## Generic Type Name -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`generic_type_name` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`generic_type_name` | Enabled | No | idiomatic | No | 3.0.0 Generic type name should only contain alphanumeric characters, start with an uppercase character and span between 1 and 20 characters in length. @@ -7733,9 +7791,9 @@ enum Foo<↓type> {} ## Identifier Name -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`identifier_name` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`identifier_name` | Enabled | No | style | No | 3.0.0 Identifier names should only contain alphanumeric characters and start with a lowercase character or should only contain capital letters. In an exception to the above, variable names may start with a capital letter when they are declared static and immutable. Variable names should not be too long or too short. @@ -7854,9 +7912,9 @@ enum Foo { case ↓MyEnum } ## Implicit Getter -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`implicit_getter` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`implicit_getter` | Enabled | No | style | No | 3.0.0 Computed read-only properties and subscripts should avoid using the get keyword. @@ -8057,9 +8115,9 @@ class Foo { ## Implicit Return -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`implicit_return` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`implicit_return` | Disabled | Yes | style | No | 3.0.0 Prefer implicit returns in closures. @@ -8128,9 +8186,9 @@ foo.map({ ↓return $0 + 1}) ## Implicitly Unwrapped Optional -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`implicitly_unwrapped_optional` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`implicitly_unwrapped_optional` | Disabled | No | idiomatic | No | 3.0.0 Implicitly unwrapped optionals should be avoided when possible. @@ -8217,9 +8275,9 @@ func foo(int: Int!) {} ## Inert Defer -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`inert_defer` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`inert_defer` | Enabled | No | lint | No | 3.0.0 If defer is at the end of its parent scope, it will be executed right where it is anyway. @@ -8277,9 +8335,9 @@ func example2() { ## Is Disjoint -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`is_disjoint` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`is_disjoint` | Enabled | No | idiomatic | No | 3.0.0 Prefer using `Set.isDisjoint(with:)` over `Set.intersection(_:).isEmpty`. @@ -8322,9 +8380,9 @@ let isObjc = !objcAttributes.↓intersection(dictionary.enclosedSwiftAttributes) ## Joined Default Parameter -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`joined_default_parameter` | Disabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`joined_default_parameter` | Disabled | Yes | idiomatic | No | 3.0.0 Discouraged explicit usage of the default separator. @@ -8370,9 +8428,9 @@ func foo() -> String { ## Large Tuple -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`large_tuple` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`large_tuple` | Enabled | No | metrics | No | 3.0.0 Tuples shouldn't have too many members. Create a custom type instead. @@ -8531,9 +8589,9 @@ func getDictionaryAndInt() -> (Dictionary, Int ## Leading Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`leading_whitespace` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`leading_whitespace` | Enabled | Yes | style | No | 3.0.0 Files should not contain leading whitespace. @@ -8567,9 +8625,9 @@ Files should not contain leading whitespace. ## Legacy CGGeometry Functions -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`legacy_cggeometry_functions` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`legacy_cggeometry_functions` | Enabled | Yes | idiomatic | No | 3.0.0 Struct extension properties and methods are preferred over legacy functions @@ -8748,9 +8806,9 @@ rect1.intersects(rect2) ## Legacy Constant -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`legacy_constant` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`legacy_constant` | Enabled | Yes | idiomatic | No | 3.0.0 Struct-scoped constants are preferred over legacy global constants. @@ -8849,9 +8907,9 @@ Float.pi ## Legacy Constructor -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`legacy_constructor` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`legacy_constructor` | Enabled | Yes | idiomatic | No | 3.0.0 Swift constructors are preferred over legacy convenience functions. @@ -9056,9 +9114,9 @@ UIOffset(horizontal: horizontal, vertical: vertical) ## Legacy NSGeometry Functions -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`legacy_nsgeometry_functions` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`legacy_nsgeometry_functions` | Enabled | Yes | idiomatic | No | 3.0.0 Struct extension properties and methods are preferred over legacy functions @@ -9229,9 +9287,9 @@ rect1.intersects(rect2) ## Variable Declaration Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`let_var_whitespace` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`let_var_whitespace` | Disabled | No | style | No | 3.0.0 Let and var should be separated from other statements by a blank line. @@ -9394,9 +9452,9 @@ var x = 0 ## Line Length -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`line_length` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`line_length` | Enabled | No | metrics | No | 3.0.0 Lines should not span too many characters. @@ -9445,9 +9503,9 @@ Lines should not span too many characters. ## Literal Expression End Indentation -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`literal_expression_end_indentation` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`literal_expression_end_indentation` | Disabled | Yes | style | No | 3.0.0 Array and dictionary literal end should have the same indentation as the line that started it. @@ -9534,9 +9592,9 @@ let x = [ ## Lower ACL than parent -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`lower_acl_than_parent` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`lower_acl_than_parent` | Disabled | No | lint | No | 3.0.0 Ensure definitions have a lower access control level than their enclosing parent @@ -9611,9 +9669,9 @@ class Foo { public private(set) var bar: String? } ## Mark -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`mark` | Enabled | Yes | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`mark` | Enabled | Yes | lint | No | 3.0.0 MARK comment should be in valid format. e.g. '// MARK: ...' or '// MARK: - ...' @@ -9752,9 +9810,9 @@ extension MarkTest {} ## Missing Docs -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`missing_docs` | Disabled | No | lint | 4.1.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`missing_docs` | Disabled | No | lint | No | 4.1.0 Declarations should be documented. @@ -9822,9 +9880,9 @@ public let b: Int ## Modifier Order -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`modifier_order` | Disabled | No | style | 4.1.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`modifier_order` | Disabled | No | style | No | 4.1.0 Modifier order should be consistent. @@ -10104,9 +10162,9 @@ class Foo { ## Multiline Arguments -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`multiline_arguments` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`multiline_arguments` | Disabled | No | style | No | 3.0.0 Arguments should be either on the same line, or one per line. @@ -10229,9 +10287,9 @@ foo( ## Multiline Function Chains -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`multiline_function_chains` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`multiline_function_chains` | Disabled | No | style | No | 3.0.0 Chained function calls should be either on the same line, or one per line. @@ -10345,9 +10403,9 @@ a.b { ## Multiline Parameters -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`multiline_parameters` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`multiline_parameters` | Disabled | No | style | No | 3.0.0 Functions and methods parameters should be either on the same line, or one per line. @@ -10825,9 +10883,9 @@ class Foo { ## Multiple Closures with Trailing Closure -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`multiple_closures_with_trailing_closure` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`multiple_closures_with_trailing_closure` | Enabled | No | style | No | 3.0.0 Trailing closure syntax should not be used when passing more than one closure argument. @@ -10886,9 +10944,9 @@ UIView.animate(withDuration: 1.0, animations: { ## Nesting -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`nesting` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`nesting` | Enabled | No | metrics | No | 3.0.0 Types should be nested at most 1 level deep, and statements should be nested at most 5 levels deep. @@ -11003,9 +11061,9 @@ func func4() { func func5() { ## Nimble Operator -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`nimble_operator` | Disabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`nimble_operator` | Disabled | Yes | idiomatic | No | 3.0.0 Prefer Nimble operator overloads over free matcher functions. @@ -11118,9 +11176,9 @@ expect(10) > 2 ## No Extension Access Modifier -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`no_extension_access_modifier` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`no_extension_access_modifier` | Disabled | No | idiomatic | No | 3.0.0 Prefer not to use extension access modifiers @@ -11170,9 +11228,9 @@ extension String {} ## No Fallthrough Only -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`no_fallthrough_only` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`no_fallthrough_only` | Enabled | No | idiomatic | No | 3.0.0 Fallthroughs can only be used if the `case` contains at least one other statement. @@ -11348,9 +11406,9 @@ case "abc": ## No Grouping Extension -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`no_grouping_extension` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`no_grouping_extension` | Disabled | No | idiomatic | No | 3.0.0 Extensions shouldn't be used to group code within the same source file. @@ -11405,9 +11463,9 @@ extension External { struct Gotcha {}} ## Notification Center Detachment -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`notification_center_detachment` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`notification_center_detachment` | Enabled | No | lint | No | 3.0.0 An object should only remove itself as an observer in `deinit`. @@ -11453,9 +11511,9 @@ class Foo { ## Number Separator -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`number_separator` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`number_separator` | Disabled | Yes | style | No | 3.0.0 Underscores should be used as thousand separator in large decimal numbers. @@ -11714,9 +11772,9 @@ let foo = ↓1000000.000000_1 ## Object Literal -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`object_literal` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`object_literal` | Disabled | No | idiomatic | No | 3.0.0 Prefer object literals over image and color inits. @@ -11831,9 +11889,9 @@ let color = ↓NSColor.init(white: 0.5, alpha: 1) ## Opening Brace Spacing -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`opening_brace` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`opening_brace` | Enabled | Yes | style | No | 3.0.0 Opening braces should be preceded by a single space and on the same line as the declaration. @@ -11996,9 +12054,9 @@ struct Parent { ## Operator Usage Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`operator_usage_whitespace` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`operator_usage_whitespace` | Disabled | Yes | style | No | 3.0.0 Operators should be surrounded by a single whitespace when they are being used. @@ -12191,9 +12249,9 @@ let v8 = 1↓ << (6) ## Operator Function Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`operator_whitespace` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`operator_whitespace` | Enabled | No | style | No | 3.0.0 Operators should be surrounded by a single whitespace when defining them. @@ -12257,9 +12315,9 @@ func abc(lhs: Int, rhs: Int) -> Int {} ## Overridden methods call super -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`overridden_super_call` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`overridden_super_call` | Disabled | No | lint | No | 3.0.0 Some overridden methods should always call super @@ -12354,9 +12412,9 @@ class VC: UIViewController { ## Override in Extension -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`override_in_extension` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`override_in_extension` | Disabled | No | lint | No | 3.0.0 Extensions shouldn't override declarations. @@ -12428,9 +12486,9 @@ extension Person { ## Pattern Matching Keywords -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`pattern_matching_keywords` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`pattern_matching_keywords` | Disabled | No | idiomatic | No | 3.0.0 Combine multiple pattern matching bindings by moving keywords out of tuples. @@ -12551,9 +12609,9 @@ switch foo { ## Prefixed Top-Level Constant -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`prefixed_toplevel_constant` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`prefixed_toplevel_constant` | Disabled | No | style | No | 3.0.0 Top-level constants should be prefixed by `k`. @@ -12670,9 +12728,9 @@ let ↓foo = { ## Private Actions -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`private_action` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`private_action` | Disabled | No | lint | No | 3.0.0 IBActions should be private. @@ -12810,9 +12868,9 @@ internal extension Foo { ## Private Outlets -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`private_outlet` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`private_outlet` | Disabled | No | lint | No | 3.0.0 IBOutlets should be private to avoid leaking UIKit to higher layers. @@ -12880,9 +12938,9 @@ class Foo { ## Private over fileprivate -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`private_over_fileprivate` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`private_over_fileprivate` | Enabled | Yes | idiomatic | No | 3.0.0 Prefer `private` over `fileprivate` declarations. @@ -12959,9 +13017,9 @@ fileprivate(set) var myInt = 4 ## Private Unit Test -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`private_unit_test` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`private_unit_test` | Enabled | No | lint | No | 3.0.0 Unit tests marked private are silently skipped. @@ -13052,9 +13110,9 @@ public class FooTest: XCTestCase { func test1() {} ## Prohibited Interface Builder -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`prohibited_interface_builder` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`prohibited_interface_builder` | Disabled | No | lint | No | 3.0.0 Creating views using Interface Builder should be avoided. @@ -13097,9 +13155,9 @@ class ViewController: UIViewController { ## Prohibited calls to super -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`prohibited_super_call` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`prohibited_super_call` | Disabled | No | lint | No | 3.0.0 Some methods should not call super @@ -13181,9 +13239,9 @@ class VC: NSView { ## Protocol Property Accessors Order -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`protocol_property_accessors_order` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`protocol_property_accessors_order` | Enabled | Yes | style | No | 3.0.0 When declaring properties in protocols, the order of accessors should be `get set`. @@ -13226,9 +13284,9 @@ protocol Foo { ## Quick Discouraged Call -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`quick_discouraged_call` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`quick_discouraged_call` | Disabled | No | lint | No | 3.0.0 Discouraged call inside 'describe' and/or 'context' block. @@ -13569,9 +13627,9 @@ class TotoTests: QuickSpec { ## Quick Discouraged Focused Test -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`quick_discouraged_focused_test` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`quick_discouraged_focused_test` | Disabled | No | lint | No | 3.0.0 Discouraged focused test. Other tests won't run while this one is focused. @@ -13677,9 +13735,9 @@ class TotoTests: QuickSpec { ## Quick Discouraged Pending Test -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`quick_discouraged_pending_test` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`quick_discouraged_pending_test` | Disabled | No | lint | No | 3.0.0 Discouraged pending test. This test won't run while it's marked as pending. @@ -13794,9 +13852,9 @@ class TotoTests: QuickSpec { ## Redundant Discardable Let -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_discardable_let` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_discardable_let` | Enabled | Yes | style | No | 3.0.0 Prefer `_ = foo()` over `let _ = foo()` when discarding a result from a function. @@ -13849,9 +13907,9 @@ if _ = foo() { ↓let _ = bar() } ## Redundant Nil Coalescing -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_nil_coalescing` | Disabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_nil_coalescing` | Disabled | Yes | idiomatic | No | 3.0.0 nil coalescing operator is only evaluated if the lhs is nil, coalescing operator with nil as rhs is redundant @@ -13885,9 +13943,9 @@ var myVar: Int? = nil; myVar↓??nil ## Redundant Optional Initialization -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_optional_initialization` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_optional_initialization` | Enabled | Yes | idiomatic | No | 3.0.0 Initializing an optional variable with nil is redundant. @@ -13999,9 +14057,9 @@ func funcName() { ## Redundant Set Access Control Rule -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_set_access_control` | Enabled | No | idiomatic | 4.1.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_set_access_control` | Enabled | No | idiomatic | No | 4.1.0 Property setter access level shouldn't be explicit if it's the same as the variable access level. @@ -14076,9 +14134,9 @@ fileprivate class A { ## Redundant String Enum Value -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_string_enum_value` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_string_enum_value` | Enabled | No | idiomatic | No | 3.0.0 String enum values can be omitted when they are equal to the enumcase name. @@ -14158,9 +14216,9 @@ enum Numbers: String { ## Redundant Type Annotation -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_type_annotation` | Disabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_type_annotation` | Disabled | Yes | idiomatic | No | 3.0.0 Variables should not have redundant type annotation @@ -14219,9 +14277,9 @@ class ViewController: UIViewController { ## Redundant Void Return -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`redundant_void_return` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`redundant_void_return` | Enabled | Yes | idiomatic | No | 3.0.0 Returning Void in a function declaration is redundant. @@ -14319,9 +14377,9 @@ protocol Foo { ## Required Enum Case -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`required_enum_case` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`required_enum_case` | Disabled | No | lint | No | 3.0.0 Enums conforming to a specified protocol must implement a specific case(s). @@ -14394,9 +14452,9 @@ enum MyNetworkResponse: String, NetworkResponsable { ## Returning Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`return_arrow_whitespace` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`return_arrow_whitespace` | Enabled | Yes | style | No | 3.0.0 Return arrow and return type should be separated by a single space or on a separate line. @@ -14487,9 +14545,9 @@ var abc = {(param: Int)↓->Bool in } ## Shorthand Operator -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`shorthand_operator` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`shorthand_operator` | Enabled | No | style | No | 3.0.0 Prefer shorthand operators (+=, -=, *=, /=) over doing the operation and assigning. @@ -14757,9 +14815,9 @@ n = n - i / outputLength ## Single Test Class -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`single_test_class` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`single_test_class` | Disabled | No | style | No | 3.0.0 Test files should contain a single QuickSpec or XCTestCase class. @@ -14832,9 +14890,9 @@ class TotoTests { } ## Min or Max over Sorted First or Last -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`sorted_first_last` | Disabled | No | performance | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`sorted_first_last` | Disabled | No | performance | No | 3.0.0 Prefer using `min()` or `max()` over `sorted().first` or `sorted().last` @@ -14943,9 +15001,9 @@ let min = myList.max(by: { $0 < $1 }) ## Sorted Imports -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`sorted_imports` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`sorted_imports` | Disabled | Yes | style | No | 3.0.0 Imports should be sorted. @@ -15050,9 +15108,9 @@ import BBB ## Statement Position -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`statement_position` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`statement_position` | Enabled | Yes | style | No | 3.0.0 Else and catch should be on the same line, one space after the previous declaration. @@ -15119,9 +15177,9 @@ catch { ## Strict fileprivate -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`strict_fileprivate` | Disabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`strict_fileprivate` | Disabled | No | idiomatic | No | 3.0.0 `fileprivate` should be avoided. @@ -15202,9 +15260,9 @@ struct Inter { ## Superfluous Disable Command -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`superfluous_disable_command` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`superfluous_disable_command` | Enabled | No | lint | No | 3.0.0 SwiftLint 'disable' commands are superfluous when the disabled rule would not have triggered a violation in the disabled region. @@ -15212,9 +15270,9 @@ SwiftLint 'disable' commands are superfluous when the disabled rule would not ha ## Switch and Case Statement Alignment -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`switch_case_alignment` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`switch_case_alignment` | Enabled | No | style | No | 3.0.0 Case statements should vertically align with their enclosing switch statement, or indented if configured otherwise. @@ -15325,9 +15383,9 @@ if aBool { ## Switch Case on Newline -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`switch_case_on_newline` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`switch_case_on_newline` | Disabled | No | style | No | 3.0.0 Cases inside a switch should always be on a newline @@ -15526,9 +15584,9 @@ switch foo { ## Syntactic Sugar -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`syntactic_sugar` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`syntactic_sugar` | Enabled | No | idiomatic | No | 3.0.0 Shorthand syntactic sugar should be used, i.e. [Int] instead of Array. @@ -15639,9 +15697,9 @@ let x: ↓Swift.Optional ## Todo -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`todo` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`todo` | Enabled | No | lint | No | 3.0.0 TODOs and FIXMEs should be resolved. @@ -15710,9 +15768,9 @@ TODOs and FIXMEs should be resolved. ## Toggle Bool -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`toggle_bool` | Disabled | No | idiomatic | 4.2.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`toggle_bool` | Disabled | No | idiomatic | No | 4.2.0 Prefer `someBool.toggle()` over `someBool = !someBool`. @@ -15759,9 +15817,9 @@ func foo() { ↓abc = !abc } ## Trailing Closure -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`trailing_closure` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`trailing_closure` | Disabled | No | style | No | 3.0.0 Trailing closure syntax should be used whenever possible. @@ -15830,9 +15888,9 @@ offsets.sorted { $0.offset < $1.offset } ## Trailing Comma -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`trailing_comma` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`trailing_comma` | Enabled | Yes | style | No | 3.0.0 Trailing commas in arrays and dictionaries should be avoided/enforced. @@ -15932,9 +15990,9 @@ let foo = ["אבג", "αβγ", "🇺🇸"↓,] ## Trailing Newline -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`trailing_newline` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`trailing_newline` | Enabled | Yes | style | No | 3.0.0 Files should have a single trailing newline. @@ -15968,9 +16026,9 @@ let a = 0 ## Trailing Semicolon -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`trailing_semicolon` | Enabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`trailing_semicolon` | Enabled | Yes | idiomatic | No | 3.0.0 Lines should not have trailing semicolons. @@ -16020,9 +16078,9 @@ let a = 0↓; ; ; ## Trailing Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`trailing_whitespace` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`trailing_whitespace` | Enabled | Yes | style | No | 3.0.0 Lines should not have trailing whitespace. @@ -16076,9 +16134,9 @@ let name: String ## Type Body Length -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`type_body_length` | Enabled | No | metrics | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`type_body_length` | Enabled | No | metrics | No | 3.0.0 Type bodies should not span too many lines. @@ -19202,9 +19260,9 @@ let abc = 0 ## Type Name -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`type_name` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`type_name` | Enabled | No | idiomatic | No | 3.0.0 Type name should only contain alphanumeric characters, start with an uppercase character and span between 3 and 40 characters in length. @@ -19379,9 +19437,9 @@ protocol Foo { ## Unavailable Function -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`unavailable_function` | Disabled | No | idiomatic | 4.1.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`unavailable_function` | Disabled | No | idiomatic | No | 4.1.0 Unimplemented functions should be marked as unavailable. @@ -19439,9 +19497,9 @@ class ViewController: UIViewController { ## Unneeded Break in Switch -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`unneeded_break_in_switch` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`unneeded_break_in_switch` | Enabled | No | idiomatic | No | 3.0.0 Avoid using unneeded break statements. @@ -19527,9 +19585,9 @@ case .foo, .foo2 where condition: ## Unneeded Parentheses in Closure Argument -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`unneeded_parentheses_in_closure_argument` | Disabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`unneeded_parentheses_in_closure_argument` | Disabled | Yes | style | No | 3.0.0 Parentheses are not needed when declaring closure arguments. @@ -19617,9 +19675,9 @@ foo.bar { [weak self] ↓(x, y) in } ## Untyped Error in Catch -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`untyped_error_in_catch` | Disabled | Yes | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`untyped_error_in_catch` | Disabled | Yes | idiomatic | No | 3.0.0 Catch statements should not declare error variables without type casting. @@ -19707,9 +19765,9 @@ do { ## Unused Closure Parameter -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`unused_closure_parameter` | Enabled | Yes | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`unused_closure_parameter` | Enabled | Yes | lint | No | 3.0.0 Unused parameter in a closure should be replaced with _. @@ -19873,9 +19931,9 @@ func foo () { ## Unused Enumerated -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`unused_enumerated` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`unused_enumerated` | Enabled | No | idiomatic | No | 3.0.0 When the index or the item is not used, `.enumerated()` can be removed. @@ -19959,9 +20017,9 @@ for (idx, ↓_) in bar.enumerated() { } ## Unused Optional Binding -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`unused_optional_binding` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`unused_optional_binding` | Enabled | No | style | No | 3.0.0 Prefer `!= nil` over `let _ =` @@ -20069,9 +20127,9 @@ if case .some(let ↓_) = self {} ## Valid IBInspectable -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`valid_ibinspectable` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`valid_ibinspectable` | Enabled | No | lint | No | 3.0.0 @IBInspectable should be applied to variables only, have its type explicit and be of a supported type @@ -20202,9 +20260,9 @@ class Foo { ## Vertical Parameter Alignment -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`vertical_parameter_alignment` | Enabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`vertical_parameter_alignment` | Enabled | No | style | No | 3.0.0 Function parameters should be aligned vertically if they're in multiple lines in a declaration. @@ -20306,9 +20364,9 @@ func validateFunction(_ file: File, ## Vertical Parameter Alignment On Call -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`vertical_parameter_alignment_on_call` | Disabled | No | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`vertical_parameter_alignment_on_call` | Disabled | No | style | No | 3.0.0 Function parameters should be aligned vertically if they're in multiple lines in a method call. @@ -20420,9 +20478,9 @@ foo(param1: 1, param2: { _ in }, ## Vertical Whitespace -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`vertical_whitespace` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`vertical_whitespace` | Enabled | Yes | style | No | 3.0.0 Limit vertical whitespace to a single empty line. @@ -20488,9 +20546,9 @@ class BBBB {} ## Void Return -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`void_return` | Enabled | Yes | style | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`void_return` | Enabled | Yes | style | No | 3.0.0 Prefer `-> Void` over `-> ()`. @@ -20579,9 +20637,9 @@ let foo: (ConfigurationTests) -> () throws -> ↓()) ## Weak Delegate -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`weak_delegate` | Enabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`weak_delegate` | Enabled | No | lint | No | 3.0.0 Delegates should be weak to avoid reference cycles. @@ -20680,9 +20738,9 @@ class Foo { ## XCTFail Message -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`xctfail_message` | Enabled | No | idiomatic | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`xctfail_message` | Enabled | No | idiomatic | No | 3.0.0 An XCTFail call should include a description of the assertion. @@ -20725,9 +20783,9 @@ func testFoo() { ## Yoda condition rule -Identifier | Enabled by default | Supports autocorrection | Kind | Minimum Swift Compiler Version ---- | --- | --- | --- | --- -`yoda_condition` | Disabled | No | lint | 3.0.0 +Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version +--- | --- | --- | --- | --- | --- +`yoda_condition` | Disabled | No | lint | No | 3.0.0 The variable should be placed on the left, the constant on the right of a comparison operator. diff --git a/Source/SwiftLintFramework/Extensions/Configuration+Parsing.swift b/Source/SwiftLintFramework/Extensions/Configuration+Parsing.swift index f73a2ec2fe..e8fe9c7f3d 100644 --- a/Source/SwiftLintFramework/Extensions/Configuration+Parsing.swift +++ b/Source/SwiftLintFramework/Extensions/Configuration+Parsing.swift @@ -14,6 +14,7 @@ extension Configuration { case warningThreshold = "warning_threshold" case whitelistRules = "whitelist_rules" case indentation = "indentation" + case analyzerRules = "analyzer_rules" } private static func validKeys(ruleList: RuleList) -> [String] { @@ -29,7 +30,8 @@ extension Configuration { .useNestedConfigs, .warningThreshold, .whitelistRules, - .indentation + .indentation, + .analyzerRules ].map({ $0.rawValue }) + ruleList.allValidIdentifiers() } @@ -65,6 +67,7 @@ extension Configuration { let disabledRules = defaultStringArray(dict[Key.disabledRules.rawValue]) let whitelistRules = defaultStringArray(dict[Key.whitelistRules.rawValue]) + let analyzerRules = defaultStringArray(dict[Key.analyzerRules.rawValue]) let included = defaultStringArray(dict[Key.included.rawValue]) let excluded = defaultStringArray(dict[Key.excluded.rawValue]) let indentation = Configuration.getIndentationLogIfInvalid(from: dict) @@ -88,6 +91,7 @@ extension Configuration { optInRules: optInRules, enableAllRules: enableAllRules, whitelistRules: whitelistRules, + analyzerRules: analyzerRules, included: included, excluded: excluded, warningThreshold: dict[Key.warningThreshold.rawValue] as? Int, @@ -103,6 +107,7 @@ extension Configuration { optInRules: [String], enableAllRules: Bool, whitelistRules: [String], + analyzerRules: [String], included: [String], excluded: [String], warningThreshold: Int?, @@ -112,7 +117,6 @@ extension Configuration { swiftlintVersion: String?, cachePath: String?, indentation: IndentationStyle) { - let rulesMode: RulesMode if enableAllRules { rulesMode = .allEnabled @@ -123,9 +127,9 @@ extension Configuration { "with '\(Key.whitelistRules.rawValue)'") return nil } - rulesMode = .whitelisted(whitelistRules) + rulesMode = .whitelisted(whitelistRules + analyzerRules) } else { - rulesMode = .default(disabled: disabledRules, optIn: optInRules) + rulesMode = .default(disabled: disabledRules, optIn: optInRules + analyzerRules) } self.init(rulesMode: rulesMode, diff --git a/Source/SwiftLintFramework/Models/Linter.swift b/Source/SwiftLintFramework/Models/Linter.swift index 3c168d77f1..6e85f46b1e 100644 --- a/Source/SwiftLintFramework/Models/Linter.swift +++ b/Source/SwiftLintFramework/Models/Linter.swift @@ -46,7 +46,8 @@ private extension Rule { } func lint(file: File, regions: [Region], benchmark: Bool, - superfluousDisableCommandRule: SuperfluousDisableCommandRule?) -> LintResult? { + superfluousDisableCommandRule: SuperfluousDisableCommandRule?, + compilerArguments: [String]) -> LintResult? { if !(self is SourceKitFreeRule) && file.sourcekitdFailed { return nil } @@ -57,10 +58,10 @@ private extension Rule { let ruleTime: (String, Double)? if benchmark { let start = Date() - violations = validate(file: file) + violations = validate(file: file, compilerArguments: compilerArguments) ruleTime = (ruleID, -start.timeIntervalSinceNow) } else { - violations = validate(file: file) + violations = validate(file: file, compilerArguments: compilerArguments) ruleTime = nil } @@ -104,6 +105,7 @@ public struct Linter { private let rules: [Rule] private let cache: LinterCache? private let configuration: Configuration + private let compilerArguments: [String] public var styleViolations: [StyleViolation] { return getStyleViolations().0 @@ -114,7 +116,6 @@ public struct Linter { } private func getStyleViolations(benchmark: Bool = false) -> ([StyleViolation], [(id: String, time: Double)]) { - if let cached = cachedStyleViolations(benchmark: benchmark) { return cached } @@ -128,7 +129,8 @@ public struct Linter { }) as? SuperfluousDisableCommandRule let validationResults = rules.parallelFlatMap { $0.lint(file: self.file, regions: regions, benchmark: benchmark, - superfluousDisableCommandRule: superfluousDisableCommandRule) + superfluousDisableCommandRule: superfluousDisableCommandRule, + compilerArguments: self.compilerArguments) } let violations = validationResults.flatMap { $0.violations } let ruleTimes = validationResults.compactMap { $0.ruleTime } @@ -170,11 +172,19 @@ public struct Linter { return (cachedViolations, ruleTimes) } - public init(file: File, configuration: Configuration = Configuration()!, cache: LinterCache? = nil) { + public init(file: File, configuration: Configuration = Configuration()!, cache: LinterCache? = nil, + compilerArguments: [String] = []) { self.file = file - self.cache = cache self.configuration = configuration - rules = configuration.rules + self.cache = cache + self.compilerArguments = compilerArguments + self.rules = configuration.rules.filter { rule in + if compilerArguments.isEmpty { + return !(rule is AnalyzerRule) + } else { + return rule is AnalyzerRule + } + } } public func correct() -> [Correction] { @@ -184,7 +194,7 @@ public struct Linter { var corrections = [Correction]() for rule in rules.compactMap({ $0 as? CorrectableRule }) { - let newCorrections = rule.correct(file: file) + let newCorrections = rule.correct(file: file, compilerArguments: compilerArguments) corrections += newCorrections if !newCorrections.isEmpty { file.invalidateCache() diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 3a5cdb894f..5ace761588 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -36,6 +36,7 @@ public let masterRuleList = RuleList(rules: [ ExplicitACLRule.self, ExplicitEnumRawValueRule.self, ExplicitInitRule.self, + ExplicitSelfRule.self, ExplicitTopLevelACLRule.self, ExplicitTypeInterfaceRule.self, ExtensionAccessModifierRule.self, diff --git a/Source/SwiftLintFramework/Models/RuleDescription.swift b/Source/SwiftLintFramework/Models/RuleDescription.swift index d5cb6de8fe..1c0c6b7b8f 100644 --- a/Source/SwiftLintFramework/Models/RuleDescription.swift +++ b/Source/SwiftLintFramework/Models/RuleDescription.swift @@ -8,6 +8,7 @@ public struct RuleDescription: Equatable { public let corrections: [String: String] public let deprecatedAliases: Set public let minSwiftVersion: SwiftVersion + public let requiresFileOnDisk: Bool public var consoleDescription: String { return "\(name) (\(identifier)): \(description)" } @@ -19,7 +20,8 @@ public struct RuleDescription: Equatable { minSwiftVersion: SwiftVersion = .three, nonTriggeringExamples: [String] = [], triggeringExamples: [String] = [], corrections: [String: String] = [:], - deprecatedAliases: Set = []) { + deprecatedAliases: Set = [], + requiresFileOnDisk: Bool = false) { self.identifier = identifier self.name = name self.description = description @@ -29,6 +31,7 @@ public struct RuleDescription: Equatable { self.corrections = corrections self.deprecatedAliases = deprecatedAliases self.minSwiftVersion = minSwiftVersion + self.requiresFileOnDisk = requiresFileOnDisk } } diff --git a/Source/SwiftLintFramework/Models/RuleList+Documentation.swift b/Source/SwiftLintFramework/Models/RuleList+Documentation.swift index 3a4179b378..19d1b509a1 100644 --- a/Source/SwiftLintFramework/Models/RuleList+Documentation.swift +++ b/Source/SwiftLintFramework/Models/RuleList+Documentation.swift @@ -60,15 +60,17 @@ extension RuleList { private func detailsSummary(_ rule: Rule) -> String { let columns = ["Identifier", "Enabled by default", "Supports autocorrection", - "Kind", "Minimum Swift Compiler Version"] + "Kind", "Analyzer", "Minimum Swift Compiler Version"] var content = columns.joined(separator: " | ") + "\n" content += columns.map { _ in "---" }.joined(separator: " | ") + "\n" let identifier = type(of: rule).description.identifier let defaultStatus = rule is OptInRule ? "Disabled" : "Enabled" let correctable = rule is CorrectableRule ? "Yes" : "No" let kind = type(of: rule).description.kind + let analyzer = rule is AnalyzerRule ? "Yes" : "No" let minSwiftVersion = type(of: rule).description.minSwiftVersion.rawValue - content += "`\(identifier)` | \(defaultStatus) | \(correctable) | \(kind) | \(minSwiftVersion) \n\n" + let rowMembers = ["`\(identifier)`", defaultStatus, correctable, "\(kind)", analyzer, minSwiftVersion] + content += rowMembers.joined(separator: " | ") + " \n\n" return content } diff --git a/Source/SwiftLintFramework/Protocols/Rule.swift b/Source/SwiftLintFramework/Protocols/Rule.swift index ed5a19dd93..9c183f3dd3 100644 --- a/Source/SwiftLintFramework/Protocols/Rule.swift +++ b/Source/SwiftLintFramework/Protocols/Rule.swift @@ -7,11 +7,16 @@ public protocol Rule { init() // Rules need to be able to be initialized with default values init(configuration: Any) throws + func validate(file: File, compilerArguments: [String]) -> [StyleViolation] func validate(file: File) -> [StyleViolation] func isEqualTo(_ rule: Rule) -> Bool } extension Rule { + public func validate(file: File, compilerArguments: [String]) -> [StyleViolation] { + return validate(file: file) + } + public func isEqualTo(_ rule: Rule) -> Bool { return type(of: self).description == type(of: rule).description } @@ -32,11 +37,32 @@ public protocol ConfigurationProviderRule: Rule { } public protocol CorrectableRule: Rule { + func correct(file: File, compilerArguments: [String]) -> [Correction] func correct(file: File) -> [Correction] } +public extension CorrectableRule { + func correct(file: File, compilerArguments: [String]) -> [Correction] { + return correct(file: file) + } +} + public protocol SourceKitFreeRule: Rule {} +public protocol AnalyzerRule: OptInRule {} + +public extension AnalyzerRule { + func validate(file: File) -> [StyleViolation] { + queuedFatalError("Must call `validate(file:compilerArguments:)` for AnalyzerRule") + } +} + +public extension AnalyzerRule where Self: CorrectableRule { + func correct(file: File) -> [Correction] { + queuedFatalError("Must call `correct(file:compilerArguments:)` for AnalyzerRule") + } +} + // MARK: - ConfigurationProviderRule conformance to Configurable public extension ConfigurationProviderRule { diff --git a/Source/SwiftLintFramework/Rules/Style/ExplicitSelfRule.swift b/Source/SwiftLintFramework/Rules/Style/ExplicitSelfRule.swift new file mode 100644 index 0000000000..472a5eaa76 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/Style/ExplicitSelfRule.swift @@ -0,0 +1,202 @@ +import Foundation +import SourceKittenFramework + +public struct ExplicitSelfRule: CorrectableRule, ConfigurationProviderRule, AnalyzerRule, AutomaticTestableRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "explicit_self", + name: "Explicit Self", + description: "Instance variables and functions should be explicitly accessed with 'self.'.", + kind: .style, + nonTriggeringExamples: [ + """ + struct A { + func f1() {} + func f2() { + self.f1() + } + } + """, + """ + struct A { + let p1: Int + func f1() { + _ = self.p1 + } + } + """ + ], + triggeringExamples: [ + """ + struct A { + func f1() {} + func f2() { + ↓f1() + } + } + """, + """ + struct A { + let p1: Int + func f1() { + _ = ↓p1 + } + } + """ + ], + corrections: [ + """ + struct A { + func f1() {} + func f2() { + ↓f1() + } + } + """: + """ + struct A { + func f1() {} + func f2() { + self.f1() + } + } + """, + """ + struct A { + let p1: Int + func f1() { + _ = ↓p1 + } + } + """: + """ + struct A { + let p1: Int + func f1() { + _ = self.p1 + } + } + """ + ], + requiresFileOnDisk: true + ) + + public func validate(file: File, compilerArguments: [String]) -> [StyleViolation] { + return violationRanges(in: file, compilerArguments: compilerArguments).map { + StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, characterOffset: $0.location)) + } + } + + public func correct(file: File, compilerArguments: [String]) -> [Correction] { + let violations = violationRanges(in: file, compilerArguments: compilerArguments) + let matches = file.ruleEnabled(violatingRanges: violations, for: self) + if matches.isEmpty { return [] } + + var contents = file.contents.bridge() + let description = type(of: self).description + var corrections = [Correction]() + for range in matches.reversed() { + contents = contents.replacingCharacters(in: range, with: "self.").bridge() + let location = Location(file: file, characterOffset: range.location) + corrections.append(Correction(ruleDescription: description, location: location)) + } + file.write(contents.bridge()) + return corrections + } + + private func violationRanges(in file: File, compilerArguments: [String]) -> [NSRange] { + guard !compilerArguments.isEmpty else { + queuedPrintError(""" + Attempted to lint file at path '\(file.path ?? "...")' with the \ + \(type(of: self).description.identifier) rule without any compiler arguments. + """) + return [] + } + + let allCursorInfo: [[String: SourceKitRepresentable]] + do { + let byteOffsets = try binaryOffsets(file: file, compilerArguments: compilerArguments) + allCursorInfo = try file.allCursorInfo(compilerArguments: compilerArguments, + atByteOffsets: byteOffsets) + } catch { + queuedPrintError(String(describing: error)) + return [] + } + + let cursorsMissingExplicitSelf = allCursorInfo.filter { cursorInfo in + guard let kindString = cursorInfo["key.kind"] as? String else { return false } + return kindsToFind.contains(kindString) + } + + guard !cursorsMissingExplicitSelf.isEmpty else { + return [] + } + + let contents = file.contents.bridge() + + return cursorsMissingExplicitSelf.compactMap { cursorInfo in + guard let byteOffset = cursorInfo["swiftlint.offset"] as? Int64 else { + queuedPrintError("couldn't convert offsets") + return nil + } + + return contents.byteRangeToNSRange(start: Int(byteOffset), length: 0) + } + } +} + +private let kindsToFind: Set = [ + "source.lang.swift.ref.function.method.instance", + "source.lang.swift.ref.var.instance" +] + +private extension File { + func allCursorInfo(compilerArguments: [String], atByteOffsets byteOffsets: [Int]) throws + -> [[String: SourceKitRepresentable]] { + return try byteOffsets.compactMap { offset in + if contents.bridge().substringWithByteRange(start: offset - 1, length: 1)! == "." { return nil } + var cursorInfo = try Request.cursorInfo(file: self.path!, offset: Int64(offset), + arguments: compilerArguments).send() + cursorInfo["swiftlint.offset"] = Int64(offset) + return cursorInfo + } + } +} + +private extension NSString { + func byteOffset(forLine line: Int, column: Int) -> Int { + var byteOffset = 0 + for line in lines()[..<(line - 1)] { + byteOffset += line.byteRange.length + } + return byteOffset + column - 1 + } + + func recursiveByteOffsets(_ dict: [String: Any]) -> [Int] { + let cur: [Int] + if let line = dict["key.line"] as? Int64, + let column = dict["key.column"] as? Int64, + let kindString = dict["key.kind"] as? String, + kindsToFind.contains(kindString) { + cur = [byteOffset(forLine: Int(line), column: Int(column))] + } else { + cur = [] + } + if let entities = dict["key.entities"] as? [[String: Any]] { + return entities.flatMap(recursiveByteOffsets) + cur + } + return cur + } +} + +private func binaryOffsets(file: File, compilerArguments: [String]) throws -> [Int] { + let absoluteFile = file.path!.bridge().absolutePathRepresentation() + let index = try Request.index(file: absoluteFile, arguments: compilerArguments).send() + let binaryOffsets = file.contents.bridge().recursiveByteOffsets(index) + return binaryOffsets.sorted() +} diff --git a/Source/swiftlint/Commands/AnalyzeCommand.swift b/Source/swiftlint/Commands/AnalyzeCommand.swift new file mode 100644 index 0000000000..a12f58d2bf --- /dev/null +++ b/Source/swiftlint/Commands/AnalyzeCommand.swift @@ -0,0 +1,91 @@ +import Commandant +import Result +import SwiftLintFramework + +struct AnalyzeCommand: CommandProtocol { + let verb = "analyze" + let function = "[Experimental] Run analysis rules" + + func run(_ options: AnalyzeOptions) -> Result<(), CommandantError<()>> { + let options = LintOrAnalyzeOptions(options) + if options.autocorrect { + return autocorrect(options) + } else { + return LintOrAnalyzeCommand.run(options) + } + } + + private func autocorrect(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> { + return Configuration(options: options).visitLintableFiles(options: options, cache: nil) { linter in + let corrections = linter.correct() + if !corrections.isEmpty && !options.quiet { + let correctionLogs = corrections.map({ $0.consoleDescription }) + queuedPrint(correctionLogs.joined(separator: "\n")) + } + }.flatMap { files in + if !options.quiet { + let pluralSuffix = { (collection: [Any]) -> String in + return collection.count != 1 ? "s" : "" + } + queuedPrintError("Done correcting \(files.count) file\(pluralSuffix(files))!") + } + return .success(()) + } + } +} + +struct AnalyzeOptions: OptionsProtocol { + let paths: [String] + let configurationFile: String + let strict: Bool + let lenient: Bool + let forceExclude: Bool + let useScriptInputFiles: Bool + let benchmark: Bool + let reporter: String + let quiet: Bool + let enableAllRules: Bool + let autocorrect: Bool + let compilerLogPath: String + + // swiftlint:disable line_length + static func create(_ path: String) -> (_ configurationFile: String) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ forceExclude: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ enableAllRules: Bool) -> (_ autocorrect: Bool) -> (_ compilerLogPath: String) -> (_ paths: [String]) -> AnalyzeOptions { + return { configurationFile in { strict in { lenient in { forceExclude in { useScriptInputFiles in { benchmark in { reporter in { quiet in { enableAllRules in { autocorrect in { compilerLogPath in { paths in + let allPaths: [String] + if !path.isEmpty { + allPaths = [path] + } else { + allPaths = paths + } + return self.init(paths: allPaths, configurationFile: configurationFile, strict: strict, lenient: lenient, forceExclude: forceExclude, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, enableAllRules: enableAllRules, autocorrect: autocorrect, compilerLogPath: compilerLogPath) + // swiftlint:enable line_length + }}}}}}}}}}}} + } + + static func evaluate(_ mode: CommandMode) -> Result>> { + return create + <*> mode <| pathOption(action: "analyze") + <*> mode <| configOption + <*> mode <| Option(key: "strict", defaultValue: false, + usage: "fail on warnings") + <*> mode <| Option(key: "lenient", defaultValue: false, + usage: "downgrades serious violations to warnings, warning threshold is disabled") + <*> mode <| Option(key: "force-exclude", defaultValue: false, + usage: "exclude files in config `excluded` even if their paths are explicitly specified") + <*> mode <| useScriptInputFilesOption + <*> mode <| Option(key: "benchmark", defaultValue: false, + usage: "save benchmarks to benchmark_files.txt " + + "and benchmark_rules.txt") + <*> mode <| Option(key: "reporter", defaultValue: "", + usage: "the reporter used to log errors and warnings") + <*> mode <| quietOption(action: "linting") + <*> mode <| Option(key: "enable-all-rules", defaultValue: false, + usage: "run all rules, even opt-in and disabled ones, ignoring `whitelist_rules`") + <*> mode <| Option(key: "autocorrect", defaultValue: false, + usage: "correct violations whenever possible") + <*> mode <| Option(key: "compiler-log-path", defaultValue: "", + usage: "the path of the full xcodebuild log to use when linting AnalyzerRules") + // This should go last to avoid eating other args + <*> mode <| pathsArgument(action: "analyze") + } +} diff --git a/Source/swiftlint/Commands/AutoCorrectCommand.swift b/Source/swiftlint/Commands/AutoCorrectCommand.swift index 9f139393bf..e593df14c7 100644 --- a/Source/swiftlint/Commands/AutoCorrectCommand.swift +++ b/Source/swiftlint/Commands/AutoCorrectCommand.swift @@ -8,33 +8,8 @@ struct AutoCorrectCommand: CommandProtocol { func run(_ options: AutoCorrectOptions) -> Result<(), CommandantError<()>> { let configuration = Configuration(options: options) - let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration) - let indentWidth: Int - var useTabs: Bool - - switch configuration.indentation { - case .tabs: - indentWidth = 4 - useTabs = true - case .spaces(let count): - indentWidth = count - useTabs = false - } - - return configuration.visitLintableFiles(paths: options.paths, action: "Correcting", - quiet: options.quiet, - useScriptInputFiles: options.useScriptInputFiles, - forceExclude: options.forceExclude, - cache: cache, parallel: true) { linter in - let corrections = linter.correct() - if !corrections.isEmpty && !options.quiet { - let correctionLogs = corrections.map({ $0.consoleDescription }) - queuedPrint(correctionLogs.joined(separator: "\n")) - } - if options.format { - linter.format(useTabs: useTabs, indentWidth: indentWidth) - } - }.flatMap { files in + let visitor = options.visitor(with: configuration) + return configuration.visitLintableFiles(with: visitor).flatMap { files in if !options.quiet { let pluralSuffix = { (collection: [Any]) -> String in return collection.count != 1 ? "s" : "" @@ -87,4 +62,25 @@ struct AutoCorrectOptions: OptionsProtocol { // This should go last to avoid eating other args <*> mode <| pathsArgument(action: "correct") } + + fileprivate func visitor(with configuration: Configuration) -> LintableFilesVisitor { + let cache = ignoreCache ? nil : LinterCache(configuration: configuration) + return LintableFilesVisitor(paths: paths, action: "Correcting", useSTDIN: false, quiet: quiet, + useScriptInputFiles: useScriptInputFiles, forceExclude: forceExclude, cache: cache, + parallel: true) { linter in + let corrections = linter.correct() + if !corrections.isEmpty && !self.quiet { + let correctionLogs = corrections.map({ $0.consoleDescription }) + queuedPrint(correctionLogs.joined(separator: "\n")) + } + if self.format { + switch configuration.indentation { + case .tabs: + linter.format(useTabs: true, indentWidth: 4) + case .spaces(let count): + linter.format(useTabs: false, indentWidth: count) + } + } + } + } } diff --git a/Source/swiftlint/Commands/LintCommand.swift b/Source/swiftlint/Commands/LintCommand.swift index 6d5fed8847..216d2a89bf 100644 --- a/Source/swiftlint/Commands/LintCommand.swift +++ b/Source/swiftlint/Commands/LintCommand.swift @@ -1,118 +1,12 @@ import Commandant -import Dispatch -import Foundation import Result -import SourceKittenFramework -import SwiftLintFramework struct LintCommand: CommandProtocol { let verb = "lint" let function = "Print lint warnings and errors (default command)" func run(_ options: LintOptions) -> Result<(), CommandantError<()>> { - var fileBenchmark = Benchmark(name: "files") - var ruleBenchmark = Benchmark(name: "rules") - var violations = [StyleViolation]() - let configuration = Configuration(options: options) - let reporter = reporterFrom(options: options, configuration: configuration) - let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration) - let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation") - return configuration.visitLintableFiles(options: options, cache: cache) { linter in - let currentViolations: [StyleViolation] - if options.benchmark { - let start = Date() - let (violationsBeforeLeniency, currentRuleTimes) = linter.styleViolationsAndRuleTimes - currentViolations = LintCommand.applyLeniency(options: options, violations: violationsBeforeLeniency) - visitorMutationQueue.sync { - fileBenchmark.record(file: linter.file, from: start) - currentRuleTimes.forEach { ruleBenchmark.record(id: $0, time: $1) } - violations += currentViolations - } - } else { - currentViolations = LintCommand.applyLeniency(options: options, violations: linter.styleViolations) - visitorMutationQueue.sync { - violations += currentViolations - } - } - linter.file.invalidateCache() - reporter.report(violations: currentViolations, realtimeCondition: true) - }.flatMap { files in - if LintCommand.isWarningThresholdBroken(configuration: configuration, violations: violations) - && !options.lenient { - violations.append(LintCommand.createThresholdViolation(threshold: configuration.warningThreshold!)) - reporter.report(violations: [violations.last!], realtimeCondition: true) - } - reporter.report(violations: violations, realtimeCondition: false) - let numberOfSeriousViolations = violations.filter({ $0.severity == .error }).count - if !options.quiet { - LintCommand.printStatus(violations: violations, files: files, - serious: numberOfSeriousViolations) - } - if options.benchmark { - fileBenchmark.save() - ruleBenchmark.save() - } - try? cache?.save() - return LintCommand.successOrExit(numberOfSeriousViolations: numberOfSeriousViolations, - strictWithViolations: options.strict && !violations.isEmpty) - } - } - - private static func successOrExit(numberOfSeriousViolations: Int, - strictWithViolations: Bool) -> Result<(), CommandantError<()>> { - if numberOfSeriousViolations > 0 { - exit(2) - } else if strictWithViolations { - exit(3) - } - return .success(()) - } - - private static func printStatus(violations: [StyleViolation], files: [File], serious: Int) { - let pluralSuffix = { (collection: [Any]) -> String in - return collection.count != 1 ? "s" : "" - } - queuedPrintError( - "Done linting! Found \(violations.count) violation\(pluralSuffix(violations)), " + - "\(serious) serious in \(files.count) file\(pluralSuffix(files))." - ) - } - - private static func isWarningThresholdBroken(configuration: Configuration, - violations: [StyleViolation]) -> Bool { - guard let warningThreshold = configuration.warningThreshold else { return false } - let numberOfWarningViolations = violations.filter({ $0.severity == .warning }).count - return numberOfWarningViolations >= warningThreshold - } - - private static func createThresholdViolation(threshold: Int) -> StyleViolation { - let description = RuleDescription( - identifier: "warning_threshold", - name: "Warning Threshold", - description: "Number of warnings thrown is above the threshold.", - kind: .lint - ) - return StyleViolation( - ruleDescription: description, - severity: .error, - location: Location(file: "", line: 0, character: 0), - reason: "Number of warnings exceeded threshold of \(threshold).") - } - - private static func applyLeniency(options: LintOptions, violations: [StyleViolation]) -> [StyleViolation] { - if !options.lenient { - return violations - } - return violations.map { - if $0.severity == .error { - return StyleViolation(ruleDescription: $0.ruleDescription, - severity: .warning, - location: $0.location, - reason: $0.reason) - } else { - return $0 - } - } + return LintOrAnalyzeCommand.run(LintOrAnalyzeOptions(options)) } } @@ -141,11 +35,11 @@ struct LintOptions: OptionsProtocol { allPaths = paths } return self.init(paths: allPaths, useSTDIN: useSTDIN, configurationFile: configurationFile, strict: strict, lenient: lenient, forceExclude: forceExclude, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules) + // swiftlint:enable line_length }}}}}}}}}}}}} } static func evaluate(_ mode: CommandMode) -> Result>> { - // swiftlint:enable line_length return create <*> mode <| pathOption(action: "lint") <*> mode <| Option(key: "use-stdin", defaultValue: false, diff --git a/Source/swiftlint/Commands/RulesCommand.swift b/Source/swiftlint/Commands/RulesCommand.swift index 88f0d650ea..d63c7745d4 100644 --- a/Source/swiftlint/Commands/RulesCommand.swift +++ b/Source/swiftlint/Commands/RulesCommand.swift @@ -112,6 +112,7 @@ extension TextTable { TextTableColumn(header: "correctable"), TextTableColumn(header: "enabled in your config"), TextTableColumn(header: "kind"), + TextTableColumn(header: "analyzer"), TextTableColumn(header: "configuration") ] self.init(columns: columns) @@ -141,6 +142,7 @@ extension TextTable { (rule is CorrectableRule) ? "yes" : "no", configuredRule != nil ? "yes" : "no", ruleType.description.kind.rawValue, + (rule is AnalyzerRule) ? "yes" : "no", truncate((configuredRule ?? rule).configurationDescription) ]) } diff --git a/Source/swiftlint/Extensions/Configuration+CommandLine.swift b/Source/swiftlint/Extensions/Configuration+CommandLine.swift index db9cab49bb..ad345f9222 100644 --- a/Source/swiftlint/Extensions/Configuration+CommandLine.swift +++ b/Source/swiftlint/Extensions/Configuration+CommandLine.swift @@ -5,6 +5,8 @@ import Result import SourceKittenFramework import SwiftLintFramework +private let indexIncrementerQueue = DispatchQueue(label: "io.realm.swiftlint.indexIncrementer") + private func scriptInputFiles() -> Result<[File], CommandantError<()>> { func getEnvironmentVariable(_ variable: String) -> Result> { let environment = ProcessInfo.processInfo.environment @@ -47,124 +49,135 @@ private func autoreleasepool(block: () -> Void) { block() } #endif extension Configuration { + func visitLintableFiles(with visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> { + return getFiles(with: visitor) + .flatMap { groupFiles($0, visitor: visitor) } + .flatMap { visit(filesPerConfiguration: $0, visitor: visitor) } + } - func visitLintableFiles(paths: [String], action: String, useSTDIN: Bool = false, - quiet: Bool = false, useScriptInputFiles: Bool, forceExclude: Bool, - cache: LinterCache? = nil, parallel: Bool = false, - visitorBlock: @escaping (Linter) -> Void) -> Result<[File], CommandantError<()>> { - return getFiles(paths: paths, action: action, useSTDIN: useSTDIN, quiet: quiet, forceExclude: forceExclude, - useScriptInputFiles: useScriptInputFiles) - .flatMap { files -> Result<[Configuration: [File]], CommandantError<()>> in - if files.isEmpty { - let errorMessage = "No lintable files found at paths: '\(paths.joined(separator: ", "))'" - return .failure(.usageError(description: errorMessage)) - } - return .success(Dictionary(grouping: files, by: configuration(for:))) - }.flatMap { filesPerConfiguration in - let queue = DispatchQueue(label: "io.realm.swiftlint.indexIncrementer") - var index = 0 - let fileCount = filesPerConfiguration.reduce(0) { $0 + $1.value.count } - let visit = { (file: File, config: Configuration) -> Void in - if !quiet, let path = file.path { - let increment = { - index += 1 - let filename = path.bridge().lastPathComponent - queuedPrintError("\(action) '\(filename)' (\(index)/\(fileCount))") - } - if parallel { - queue.sync(execute: increment) + private func groupFiles(_ files: [File], + visitor: LintableFilesVisitor) -> Result<[Configuration: [File]], CommandantError<()>> { + if files.isEmpty { + let errorMessage = "No lintable files found at paths: '\(visitor.paths.joined(separator: ", "))'" + return .failure(.usageError(description: errorMessage)) + } + return .success(Dictionary(grouping: files, by: configuration(for:))) + } + + private func visit(filesPerConfiguration: [Configuration: [File]], + visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> { + var index = 0 + let fileCount = filesPerConfiguration.reduce(0) { $0 + $1.value.count } + let visit = { (file: File, config: Configuration) -> Void in + let skipFile = visitor.shouldSkipFile(atPath: file.path) + if !visitor.quiet, let filename = file.path?.bridge().lastPathComponent { + let increment = { + index += 1 + if skipFile { + queuedPrintError(""" + Skipping '\(filename)' (\(index)/\(fileCount)) \ + because its compiler arguments could not be found + """) } else { - increment() + queuedPrintError("\(visitor.action) '\(filename)' (\(index)/\(fileCount))") } } - autoreleasepool { - visitorBlock(Linter(file: file, configuration: config, cache: cache)) - } - } - var filesAndConfigurations = [(File, Configuration)]() - filesAndConfigurations.reserveCapacity(fileCount) - for (config, files) in filesPerConfiguration { - let newConfig: Configuration - if cache != nil { - newConfig = config.withPrecomputedCacheDescription() + if visitor.parallel { + indexIncrementerQueue.sync(execute: increment) } else { - newConfig = config + increment() } - filesAndConfigurations += files.map { ($0, newConfig) } } - if parallel { - DispatchQueue.concurrentPerform(iterations: fileCount) { index in - let (file, config) = filesAndConfigurations[index] - visit(file, config) - } + + guard !skipFile else { + return + } + + autoreleasepool { + visitor.block(visitor.linter(forFile: file, configuration: config)) + } + } + var filesAndConfigurations = [(File, Configuration)]() + filesAndConfigurations.reserveCapacity(fileCount) + for (config, files) in filesPerConfiguration { + let newConfig: Configuration + if visitor.cache != nil { + newConfig = config.withPrecomputedCacheDescription() } else { - filesAndConfigurations.forEach(visit) + newConfig = config + } + filesAndConfigurations += files.map { ($0, newConfig) } + } + if visitor.parallel { + DispatchQueue.concurrentPerform(iterations: fileCount) { index in + let (file, config) = filesAndConfigurations[index] + visit(file, config) } - return .success(filesAndConfigurations.compactMap({ $0.0 })) + } else { + filesAndConfigurations.forEach(visit) } + return .success(filesAndConfigurations.compactMap({ $0.0 })) } - // swiftlint:disable function_parameter_count - fileprivate func getFiles(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, forceExclude: Bool, - useScriptInputFiles: Bool) -> Result<[File], CommandantError<()>> { - if useSTDIN { + fileprivate func getFiles(with visitor: LintableFilesVisitor) -> Result<[File], CommandantError<()>> { + if visitor.useSTDIN { let stdinData = FileHandle.standardInput.readDataToEndOfFile() if let stdinString = String(data: stdinData, encoding: .utf8) { return .success([File(contents: stdinString)]) } return .failure(.usageError(description: "stdin isn't a UTF8-encoded string")) - } else if useScriptInputFiles { + } else if visitor.useScriptInputFiles { return scriptInputFiles() } - if !quiet { - let filesInfo = paths.isEmpty ? "in current working directory" : "at paths \(paths.joined(separator: ", "))" - let message = "\(action) Swift files \(filesInfo)" - queuedPrintError(message) + if !visitor.quiet { + let filesInfo: String + if visitor.paths.isEmpty { + filesInfo = "in current working directory" + } else { + filesInfo = "at paths \(visitor.paths.joined(separator: ", "))" + } + + queuedPrintError("\(visitor.action) Swift files \(filesInfo)") } - return .success(paths.flatMap { - self.lintableFiles(inPath: $0, forceExclude: forceExclude) + return .success(visitor.paths.flatMap { + self.lintableFiles(inPath: $0, forceExclude: visitor.forceExclude) }) } - // swiftlint:enable function_parameter_count private static func rootPath(from paths: [String]) -> String? { // We don't know the root when more than one path is passed (i.e. not useful if the root of 2 paths is ~) return paths.count == 1 ? paths.first?.absolutePathStandardized() : nil } - // MARK: Lint Command + // MARK: LintOrAnalyze Command - init(options: LintOptions) { + init(options: LintOrAnalyzeOptions) { let cachePath = options.cachePath.isEmpty ? nil : options.cachePath - let optional = !CommandLine.arguments.contains("--config") - self.init(path: options.configurationFile, - rootPath: type(of: self).rootPath(from: options.paths), - optional: optional, quiet: options.quiet, - enableAllRules: options.enableAllRules, + self.init(path: options.configurationFile, rootPath: type(of: self).rootPath(from: options.paths), + optional: isConfigOptional(), quiet: options.quiet, enableAllRules: options.enableAllRules, cachePath: cachePath) } - func visitLintableFiles(options: LintOptions, cache: LinterCache? = nil, + func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, visitorBlock: @escaping (Linter) -> Void) -> Result<[File], CommandantError<()>> { - return visitLintableFiles(paths: options.paths, action: "Linting", useSTDIN: options.useSTDIN, - quiet: options.quiet, useScriptInputFiles: options.useScriptInputFiles, - forceExclude: options.forceExclude, cache: cache, parallel: true, - visitorBlock: visitorBlock) + return LintableFilesVisitor.create(options, cache: cache, block: visitorBlock).flatMap(visitLintableFiles) } // MARK: AutoCorrect Command init(options: AutoCorrectOptions) { let cachePath = options.cachePath.isEmpty ? nil : options.cachePath - let optional = !CommandLine.arguments.contains("--config") self.init(path: options.configurationFile, rootPath: type(of: self).rootPath(from: options.paths), - optional: optional, quiet: options.quiet, cachePath: cachePath) + optional: isConfigOptional(), quiet: options.quiet, cachePath: cachePath) } // MARK: Rules command init(options: RulesOptions) { - let optional = !CommandLine.arguments.contains("--config") - self.init(path: options.configurationFile, optional: optional) + self.init(path: options.configurationFile, optional: isConfigOptional()) } } + +private func isConfigOptional() -> Bool { + return !CommandLine.arguments.contains("--config") +} diff --git a/Source/swiftlint/Extensions/Reporter+CommandLine.swift b/Source/swiftlint/Extensions/Reporter+CommandLine.swift index 597c2c6c56..efd2067928 100644 --- a/Source/swiftlint/Extensions/Reporter+CommandLine.swift +++ b/Source/swiftlint/Extensions/Reporter+CommandLine.swift @@ -11,7 +11,7 @@ extension Reporter { } } -func reporterFrom(options: LintOptions, configuration: Configuration) -> Reporter.Type { - let string = options.reporter.isEmpty ? configuration.reporter : options.reporter +func reporterFrom(optionsReporter: String, configuration: Configuration) -> Reporter.Type { + let string = optionsReporter.isEmpty ? configuration.reporter : optionsReporter return reporterFrom(identifier: string) } diff --git a/Source/swiftlint/Helpers/CompilerArgumentsExtractor.swift b/Source/swiftlint/Helpers/CompilerArgumentsExtractor.swift new file mode 100644 index 0000000000..5956883d74 --- /dev/null +++ b/Source/swiftlint/Helpers/CompilerArgumentsExtractor.swift @@ -0,0 +1,120 @@ +import Foundation +import SourceKittenFramework + +struct CompilerArgumentsExtractor { + static func allCompilerInvocations(compilerLogs: String) -> [String] { + var compilerInvocations = [String]() + compilerLogs.enumerateLines { line, _ in + if let swiftcIndex = line.range(of: "swiftc ")?.upperBound, line.contains(" -module-name ") { + compilerInvocations.append(String(line[swiftcIndex...])) + } + } + + return compilerInvocations + } + + static func compilerArgumentsForFile(_ sourceFile: String, compilerInvocations: [String]) -> [String]? { + let escapedSourceFile = sourceFile.replacingOccurrences(of: " ", with: "\\ ") + guard let compilerInvocation = compilerInvocations.first(where: { $0.contains(escapedSourceFile) }) else { + return nil + } + + return parseCLIArguments(compilerInvocation) + } +} + +// MARK: - Private + +#if !os(Linux) +private extension Scanner { + func scanUpToString(_ string: String) -> String? { + var result: NSString? + let success = scanUpTo(string, into: &result) + if success { + return result?.bridge() + } + return nil + } + + func scanString(_ string: String) -> String? { + var result: NSString? + let success = scanString(string, into: &result) + if success { + return result?.bridge() + } + return nil + } +} +#endif + +private func parseCLIArguments(_ string: String) -> [String] { + let escapedSpacePlaceholder = "\u{0}" + let scanner = Scanner(string: string) + var str = "" + var didStart = false + while let result = scanner.scanUpToString("\"") { + if didStart { + str += result.replacingOccurrences(of: " ", with: escapedSpacePlaceholder) + str += " " + } else { + str += result + } + _ = scanner.scanString("\"") + didStart = !didStart + } + return filter(arguments: + str.trimmingCharacters(in: .whitespaces) + .replacingOccurrences(of: "\\ ", with: escapedSpacePlaceholder) + .components(separatedBy: " ") + .map { $0.replacingOccurrences(of: escapedSpacePlaceholder, with: " ") } + ) +} + +/** + Partially filters compiler arguments from `xcodebuild` to something that SourceKit/Clang will accept. + + - parameter args: Compiler arguments, as parsed from `xcodebuild`. + + - returns: A tuple of partially filtered compiler arguments in `.0`, and whether or not there are + more flags to remove in `.1`. + */ +private func partiallyFilter(arguments args: [String]) -> ([String], Bool) { + guard let indexOfFlagToRemove = args.index(of: "-output-file-map") else { + return (args, false) + } + var args = args + args.remove(at: args.index(after: indexOfFlagToRemove)) + args.remove(at: indexOfFlagToRemove) + return (args, true) +} + +/** + Filters compiler arguments from `xcodebuild` to something that SourceKit/Clang will accept. + + - parameter args: Compiler arguments, as parsed from `xcodebuild`. + + - returns: Filtered compiler arguments. + */ +private func filter(arguments args: [String]) -> [String] { + var args = args + args.append(contentsOf: ["-D", "DEBUG"]) + var shouldContinueToFilterArguments = true + while shouldContinueToFilterArguments { + (args, shouldContinueToFilterArguments) = partiallyFilter(arguments: args) + } + return args.filter { + ![ + "-parseable-output", + "-incremental", + "-serialize-diagnostics", + "-emit-dependencies" + ].contains($0) + }.map { + if $0 == "-O" { + return "-Onone" + } else if $0 == "-DNDEBUG=1" { + return "-DDEBUG=1" + } + return $0 + } +} diff --git a/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift b/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift new file mode 100644 index 0000000000..be32e3527b --- /dev/null +++ b/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift @@ -0,0 +1,192 @@ +import Commandant +import Dispatch +import Foundation +import Result +import SourceKittenFramework +import SwiftLintFramework + +enum LintOrAnalyzeMode { + case lint, analyze + + var verb: String { + switch self { + case .lint: + return "linting" + case .analyze: + return "analyzing" + } + } +} + +struct LintOrAnalyzeCommand { + static func run(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> { + var fileBenchmark = Benchmark(name: "files") + var ruleBenchmark = Benchmark(name: "rules") + var violations = [StyleViolation]() + let configuration = Configuration(options: options) + let reporter = reporterFrom(optionsReporter: options.reporter, configuration: configuration) + let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration) + let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation") + return configuration.visitLintableFiles(options: options, cache: cache) { linter in + let currentViolations: [StyleViolation] + if options.benchmark { + let start = Date() + let (violationsBeforeLeniency, currentRuleTimes) = linter.styleViolationsAndRuleTimes + currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency) + visitorMutationQueue.sync { + fileBenchmark.record(file: linter.file, from: start) + currentRuleTimes.forEach { ruleBenchmark.record(id: $0, time: $1) } + violations += currentViolations + } + } else { + currentViolations = applyLeniency(options: options, violations: linter.styleViolations) + visitorMutationQueue.sync { + violations += currentViolations + } + } + linter.file.invalidateCache() + reporter.report(violations: currentViolations, realtimeCondition: true) + }.flatMap { files in + if isWarningThresholdBroken(configuration: configuration, violations: violations) + && !options.lenient { + violations.append(createThresholdViolation(threshold: configuration.warningThreshold!)) + reporter.report(violations: [violations.last!], realtimeCondition: true) + } + reporter.report(violations: violations, realtimeCondition: false) + let numberOfSeriousViolations = violations.filter({ $0.severity == .error }).count + if !options.quiet { + printStatus(violations: violations, files: files, serious: numberOfSeriousViolations, + verb: options.verb) + } + if options.benchmark { + fileBenchmark.save() + ruleBenchmark.save() + } + try? cache?.save() + return successOrExit(numberOfSeriousViolations: numberOfSeriousViolations, + strictWithViolations: options.strict && !violations.isEmpty) + } + } + + private static func successOrExit(numberOfSeriousViolations: Int, + strictWithViolations: Bool) -> Result<(), CommandantError<()>> { + if numberOfSeriousViolations > 0 { + exit(2) + } else if strictWithViolations { + exit(3) + } + return .success(()) + } + + private static func printStatus(violations: [StyleViolation], files: [File], serious: Int, verb: String) { + let pluralSuffix = { (collection: [Any]) -> String in + return collection.count != 1 ? "s" : "" + } + queuedPrintError( + "Done \(verb)! Found \(violations.count) violation\(pluralSuffix(violations)), " + + "\(serious) serious in \(files.count) file\(pluralSuffix(files))." + ) + } + + private static func isWarningThresholdBroken(configuration: Configuration, + violations: [StyleViolation]) -> Bool { + guard let warningThreshold = configuration.warningThreshold else { return false } + let numberOfWarningViolations = violations.filter({ $0.severity == .warning }).count + return numberOfWarningViolations >= warningThreshold + } + + private static func createThresholdViolation(threshold: Int) -> StyleViolation { + let description = RuleDescription( + identifier: "warning_threshold", + name: "Warning Threshold", + description: "Number of warnings thrown is above the threshold.", + kind: .lint + ) + return StyleViolation( + ruleDescription: description, + severity: .error, + location: Location(file: "", line: 0, character: 0), + reason: "Number of warnings exceeded threshold of \(threshold).") + } + + private static func applyLeniency(options: LintOrAnalyzeOptions, violations: [StyleViolation]) -> [StyleViolation] { + if !options.lenient { + return violations + } + return violations.map { + if $0.severity == .error { + return StyleViolation(ruleDescription: $0.ruleDescription, + severity: .warning, + location: $0.location, + reason: $0.reason) + } else { + return $0 + } + } + } +} + +struct LintOrAnalyzeOptions { + let mode: LintOrAnalyzeMode + let paths: [String] + let useSTDIN: Bool + let configurationFile: String + let strict: Bool + let lenient: Bool + let forceExclude: Bool + let useScriptInputFiles: Bool + let benchmark: Bool + let reporter: String + let quiet: Bool + let cachePath: String + let ignoreCache: Bool + let enableAllRules: Bool + let autocorrect: Bool + let compilerLogPath: String + + init(_ options: LintOptions) { + mode = .lint + paths = options.paths + useSTDIN = options.useSTDIN + configurationFile = options.configurationFile + strict = options.strict + lenient = options.lenient + forceExclude = options.forceExclude + useScriptInputFiles = options.useScriptInputFiles + benchmark = options.benchmark + reporter = options.reporter + quiet = options.quiet + cachePath = options.cachePath + ignoreCache = options.ignoreCache + enableAllRules = options.enableAllRules + autocorrect = false + compilerLogPath = "" + } + + init(_ options: AnalyzeOptions) { + mode = .analyze + paths = options.paths + useSTDIN = false + configurationFile = options.configurationFile + strict = options.strict + lenient = options.lenient + forceExclude = options.forceExclude + useScriptInputFiles = options.useScriptInputFiles + benchmark = options.benchmark + reporter = options.reporter + quiet = options.quiet + cachePath = "" + ignoreCache = true + enableAllRules = options.enableAllRules + autocorrect = options.autocorrect + compilerLogPath = options.compilerLogPath + } + + var verb: String { + if autocorrect { + return "correcting" + } else { + return mode.verb + } + } +} diff --git a/Source/swiftlint/Helpers/LintableFilesVisitor.swift b/Source/swiftlint/Helpers/LintableFilesVisitor.swift new file mode 100644 index 0000000000..73aa2da014 --- /dev/null +++ b/Source/swiftlint/Helpers/LintableFilesVisitor.swift @@ -0,0 +1,118 @@ +import Commandant +import Foundation +import Result +import SourceKittenFramework +import SwiftLintFramework + +enum LintOrAnalyzeModeWithCompilerArguments { + case lint + case analyze(allCompilerInvocations: [String]) +} + +struct LintableFilesVisitor { + let paths: [String] + let action: String + let useSTDIN: Bool + let quiet: Bool + let useScriptInputFiles: Bool + let forceExclude: Bool + let cache: LinterCache? + let parallel: Bool + let mode: LintOrAnalyzeModeWithCompilerArguments + let block: (Linter) -> Void + + init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, useScriptInputFiles: Bool, forceExclude: Bool, + cache: LinterCache?, parallel: Bool, block: @escaping (Linter) -> Void) { + self.paths = paths + self.action = action + self.useSTDIN = useSTDIN + self.quiet = quiet + self.useScriptInputFiles = useScriptInputFiles + self.forceExclude = forceExclude + self.cache = cache + self.parallel = parallel + self.mode = .lint + self.block = block + } + + private init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, useScriptInputFiles: Bool, + forceExclude: Bool, cache: LinterCache?, compilerLogContents: String, + block: @escaping (Linter) -> Void) { + self.paths = paths + self.action = action + self.useSTDIN = useSTDIN + self.quiet = quiet + self.useScriptInputFiles = useScriptInputFiles + self.forceExclude = forceExclude + self.cache = cache + self.parallel = true + if compilerLogContents.isEmpty { + self.mode = .lint + } else { + let allCompilerInvocations = CompilerArgumentsExtractor + .allCompilerInvocations(compilerLogs: compilerLogContents) + self.mode = .analyze(allCompilerInvocations: allCompilerInvocations) + } + self.block = block + } + + static func create(_ options: LintOrAnalyzeOptions, cache: LinterCache?, block: @escaping (Linter) -> Void) + -> Result> { + let compilerLogContents: String + if options.mode == .lint { + compilerLogContents = "" + } else if let logContents = LintableFilesVisitor.compilerLogContents(logPath: options.compilerLogPath), + !logContents.isEmpty { + compilerLogContents = logContents + } else { + return .failure( + .usageError(description: "Could not read compiler log at path: '\(options.compilerLogPath)'") + ) + } + + let visitor = LintableFilesVisitor(paths: options.paths, action: options.verb.bridge().capitalized, + useSTDIN: options.useSTDIN, quiet: options.quiet, + useScriptInputFiles: options.useScriptInputFiles, + forceExclude: options.forceExclude, cache: cache, + compilerLogContents: compilerLogContents, block: block) + return .success(visitor) + } + + func shouldSkipFile(atPath path: String?) -> Bool { + switch self.mode { + case .lint: + return false + case let .analyze(allCompilerInvocations): + let compilerArguments = path.flatMap { + CompilerArgumentsExtractor.compilerArgumentsForFile($0, compilerInvocations: allCompilerInvocations) + } ?? [] + return compilerArguments.isEmpty + } + } + + func linter(forFile file: File, configuration: Configuration) -> Linter { + switch self.mode { + case .lint: + return Linter(file: file, configuration: configuration, cache: cache) + case let .analyze(allCompilerInvocations): + let compilerArguments = file.path.flatMap { + CompilerArgumentsExtractor.compilerArgumentsForFile($0, compilerInvocations: allCompilerInvocations) + } ?? [] + return Linter(file: file, configuration: configuration, compilerArguments: compilerArguments) + } + } + + private static func compilerLogContents(logPath: String) -> String? { + if logPath.isEmpty { + return nil + } + + if let data = FileManager.default.contents(atPath: logPath), + let logContents = String(data: data, encoding: .utf8) { + return logContents + } + + print("couldn't read log file at path '\(logPath)'") + return nil + } +} diff --git a/Source/swiftlint/main.swift b/Source/swiftlint/main.swift index d736397b37..7dd46b32c4 100644 --- a/Source/swiftlint/main.swift +++ b/Source/swiftlint/main.swift @@ -7,6 +7,7 @@ DispatchQueue.global().async { let registry = CommandRegistry>() registry.register(LintCommand()) registry.register(AutoCorrectCommand()) + registry.register(AnalyzeCommand()) registry.register(VersionCommand()) registry.register(RulesCommand()) registry.register(GenerateDocsCommand()) diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index fac8bcfef3..30f7de505b 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -145,9 +145,14 @@ 8B01E50220A4349100C9233E /* FunctionParameterCountRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B01E4FF20A4340A00C9233E /* FunctionParameterCountRuleTests.swift */; }; 8F2CC1CB20A6A070006ED34F /* FileNameConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CC1CA20A6A070006ED34F /* FileNameConfiguration.swift */; }; 8F2CC1CD20A6A189006ED34F /* FileNameRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2CC1CC20A6A189006ED34F /* FileNameRuleTests.swift */; }; + 8F6AA75B211905B8009BA28A /* LintableFilesVisitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */; }; + 8F6AA75D21190830009BA28A /* CompilerArgumentsExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6AA75C21190830009BA28A /* CompilerArgumentsExtractor.swift */; }; 8F8050821FFE0CBB006F5B93 /* Configuration+IndentationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8050811FFE0CBB006F5B93 /* Configuration+IndentationStyle.swift */; }; + 8FC8523B2117BDDE0015269B /* ExplicitSelfRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC8523A2117BDDE0015269B /* ExplicitSelfRule.swift */; }; 8FC9F5111F4B8E48006826C1 /* IsDisjointRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC9F5101F4B8E48006826C1 /* IsDisjointRule.swift */; }; 8FD216CC205584AF008ED13F /* CharacterSet+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD216CB205584AF008ED13F /* CharacterSet+SwiftLint.swift */; }; + 8FDF482C2122476D00521605 /* AnalyzeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDF482B2122476D00521605 /* AnalyzeCommand.swift */; }; + 8FDF482E21234BFF00521605 /* LintOrAnalyzeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */; }; 92CCB2D71E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */; }; 93E0C3CE1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift */; }; A1A6F3F21EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */; }; @@ -551,9 +556,14 @@ 8B01E4FF20A4340A00C9233E /* FunctionParameterCountRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionParameterCountRuleTests.swift; sourceTree = ""; }; 8F2CC1CA20A6A070006ED34F /* FileNameConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileNameConfiguration.swift; sourceTree = ""; }; 8F2CC1CC20A6A189006ED34F /* FileNameRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileNameRuleTests.swift; sourceTree = ""; }; + 8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintableFilesVisitor.swift; sourceTree = ""; }; + 8F6AA75C21190830009BA28A /* CompilerArgumentsExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilerArgumentsExtractor.swift; sourceTree = ""; }; 8F8050811FFE0CBB006F5B93 /* Configuration+IndentationStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Configuration+IndentationStyle.swift"; sourceTree = ""; }; + 8FC8523A2117BDDE0015269B /* ExplicitSelfRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplicitSelfRule.swift; sourceTree = ""; }; 8FC9F5101F4B8E48006826C1 /* IsDisjointRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsDisjointRule.swift; sourceTree = ""; }; 8FD216CB205584AF008ED13F /* CharacterSet+SwiftLint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CharacterSet+SwiftLint.swift"; sourceTree = ""; }; + 8FDF482B2122476D00521605 /* AnalyzeCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyzeCommand.swift; sourceTree = ""; }; + 8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintOrAnalyzeCommand.swift; sourceTree = ""; }; 92CCB2D61E1EEFA300C8E5A3 /* UnusedOptionalBindingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedOptionalBindingRule.swift; sourceTree = ""; }; 93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineRule.swift; sourceTree = ""; }; A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectLiteralConfiguration.swift; sourceTree = ""; }; @@ -1002,6 +1012,7 @@ D4470D581EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift */, D47079AC1DFE2FA700027086 /* EmptyParametersRule.swift */, D47079A61DFCEB2D00027086 /* EmptyParenthesesWithTrailingClosureRule.swift */, + 8FC8523A2117BDDE0015269B /* ExplicitSelfRule.swift */, D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */, E88DEA931B099C0900A66CB0 /* IdentifierNameRule.swift */, D4130D961E16183F00242361 /* IdentifierNameRuleExamples.swift */, @@ -1324,6 +1335,9 @@ children = ( E802ECFF1C56A56000A35AE1 /* Benchmark.swift */, E81FB3E31C6D507B00DC988F /* CommonOptions.swift */, + 8F6AA75C21190830009BA28A /* CompilerArgumentsExtractor.swift */, + 8F6AA75A211905B8009BA28A /* LintableFilesVisitor.swift */, + 8FDF482D21234BFF00521605 /* LintOrAnalyzeCommand.swift */, ); path = Helpers; sourceTree = ""; @@ -1331,10 +1345,11 @@ E85FF9921C13E35400714267 /* Commands */ = { isa = PBXGroup; children = ( + 8FDF482B2122476D00521605 /* AnalyzeCommand.swift */, E84E07461C13F95300F11122 /* AutoCorrectCommand.swift */, + D4DA1DFB1E19CD300037413D /* GenerateDocsCommand.swift */, E861519A1B0573B900C54AC0 /* LintCommand.swift */, 83894F211B0C928A006214E1 /* RulesCommand.swift */, - D4DA1DFB1E19CD300037413D /* GenerateDocsCommand.swift */, E83A0B341A5D382B0041A60A /* VersionCommand.swift */, ); path = Commands; @@ -1953,6 +1968,7 @@ B89F3BCF1FD5EE1400931E59 /* RequiredEnumCaseRuleConfiguration.swift in Sources */, D48B51211F4F5DEF0068AB98 /* RuleList+Documentation.swift in Sources */, 8FC9F5111F4B8E48006826C1 /* IsDisjointRule.swift in Sources */, + 8FC8523B2117BDDE0015269B /* ExplicitSelfRule.swift in Sources */, 4DCB8E7F1CBE494E0070FCF0 /* RegexHelpers.swift in Sources */, E86396C21BADAAE5002C9E88 /* Reporter.swift in Sources */, A1A6F3F21EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift in Sources */, @@ -2041,6 +2057,8 @@ buildActionMask = 2147483647; files = ( E86E2B2E1E17443B001E823C /* Reporter+CommandLine.swift in Sources */, + 8F6AA75D21190830009BA28A /* CompilerArgumentsExtractor.swift in Sources */, + 8FDF482C2122476D00521605 /* AnalyzeCommand.swift in Sources */, E8B067811C13E49600E9E13F /* Configuration+CommandLine.swift in Sources */, E802ED001C56A56000A35AE1 /* Benchmark.swift in Sources */, E83A0B351A5D382B0041A60A /* VersionCommand.swift in Sources */, @@ -2051,6 +2069,8 @@ 6C032EF12027F79F00CD7E8D /* shim.swift in Sources */, D4DA1DFC1E19CD300037413D /* GenerateDocsCommand.swift in Sources */, E84E07471C13F95300F11122 /* AutoCorrectCommand.swift in Sources */, + 8FDF482E21234BFF00521605 /* LintOrAnalyzeCommand.swift in Sources */, + 8F6AA75B211905B8009BA28A /* LintableFilesVisitor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index a21fab8370..ebeca67d74 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -332,6 +332,12 @@ extension ExplicitInitRuleTests { ] } +extension ExplicitSelfRuleTests { + static var allTests: [(String, (ExplicitSelfRuleTests) -> () throws -> Void)] = [ + ("testWithDefaultConfiguration", testWithDefaultConfiguration) + ] +} + extension ExplicitTopLevelACLRuleTests { static var allTests: [(String, (ExplicitTopLevelACLRuleTests) -> () throws -> Void)] = [ ("testWithDefaultConfiguration", testWithDefaultConfiguration) @@ -1293,6 +1299,7 @@ XCTMain([ testCase(ExplicitACLRuleTests.allTests), testCase(ExplicitEnumRawValueRuleTests.allTests), testCase(ExplicitInitRuleTests.allTests), + testCase(ExplicitSelfRuleTests.allTests), testCase(ExplicitTopLevelACLRuleTests.allTests), testCase(ExplicitTypeInterfaceConfigurationTests.allTests), testCase(ExplicitTypeInterfaceRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift index 7b4c196986..37735cc6e0 100644 --- a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift +++ b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift @@ -174,6 +174,12 @@ class ExplicitInitRuleTests: XCTestCase { } } +class ExplicitSelfRuleTests: XCTestCase { + func testWithDefaultConfiguration() { + verifyRule(ExplicitSelfRule.description) + } +} + class ExplicitTopLevelACLRuleTests: XCTestCase { func testWithDefaultConfiguration() { verifyRule(ExplicitTopLevelACLRule.description) diff --git a/Tests/SwiftLintFrameworkTests/TestHelpers.swift b/Tests/SwiftLintFrameworkTests/TestHelpers.swift index 35db6dfec3..b5d1400896 100644 --- a/Tests/SwiftLintFrameworkTests/TestHelpers.swift +++ b/Tests/SwiftLintFrameworkTests/TestHelpers.swift @@ -5,6 +5,20 @@ import XCTest private let violationMarker = "↓" +private extension File { + static func temporary(withContents contents: String) -> File { + let url = URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent(UUID().uuidString) + .appendingPathExtension("swift") + _ = try? contents.data(using: .utf8)!.write(to: url) + return File(path: url.path)! + } + + func makeCompilerArguments() -> [String] { + return ["-sdk", sdkPath(), "-j4", path!] + } +} + extension String { func stringByAppendingPathComponent(_ pathComponent: String) -> String { return bridge().appendingPathComponent(pathComponent) @@ -13,23 +27,24 @@ extension String { let allRuleIdentifiers = Array(masterRuleList.list.keys) -func violations(_ string: String, config: Configuration = Configuration()!) -> [StyleViolation] { +func violations(_ string: String, config: Configuration = Configuration()!, + requiresFileOnDisk: Bool = false) -> [StyleViolation] { File.clearCaches() let stringStrippingMarkers = string.replacingOccurrences(of: violationMarker, with: "") - let file = File(contents: stringStrippingMarkers) - return Linter(file: file, configuration: config).styleViolations -} - -private func temporaryFile(contents: String) -> File { - let url = temporaryFileURL()! - _ = try? contents.data(using: .utf8)!.write(to: url) - return File(path: url.path)! -} + guard requiresFileOnDisk else { + let file = File(contents: stringStrippingMarkers) + let linter = Linter(file: file, configuration: config) + return linter.styleViolations + } -private func temporaryFileURL() -> URL? { - return URL(fileURLWithPath: NSTemporaryDirectory()) - .appendingPathComponent(UUID().uuidString) - .appendingPathExtension("swift") + let file = File.temporary(withContents: stringStrippingMarkers) + let linter = Linter(file: file, configuration: config, compilerArguments: file.makeCompilerArguments()) + return linter.styleViolations.map { violation in + let locationWithoutFile = Location(file: nil, line: violation.location.line, + character: violation.location.character) + return StyleViolation(ruleDescription: violation.ruleDescription, severity: violation.severity, + location: locationWithoutFile, reason: violation.reason) + } } private func cleanedContentsAndMarkerOffsets(from contents: String) -> (String, [Int]) { @@ -77,26 +92,14 @@ private func render(locations: [Location], in contents: String) -> String { private extension Configuration { func assertCorrection(_ before: String, expected: String) { - guard let path = temporaryFileURL()?.path else { - XCTFail("couldn't generate temporary path for assertCorrection()") - return - } let (cleanedBefore, markerOffsets) = cleanedContentsAndMarkerOffsets(from: before) - do { - try cleanedBefore.write(toFile: path, atomically: true, encoding: .utf8) - } catch { - XCTFail("couldn't write to file for assertCorrection() with error: \(error)") - return - } - guard let file = File(path: path) else { - XCTFail("couldn't read file at path '\(path)' for assertCorrection()") - return - } + let file = File.temporary(withContents: cleanedBefore) // expectedLocations are needed to create before call `correct()` let expectedLocations = markerOffsets.map { Location(file: file, characterOffset: $0) } - let corrections = Linter(file: file, configuration: self).correct().sorted { - $0.location < $1.location - } + let includeCompilerArguments = self.rules.contains(where: { $0 is AnalyzerRule }) + let compilerArguments = includeCompilerArguments ? file.makeCompilerArguments() : [] + let linter = Linter(file: file, configuration: self, compilerArguments: compilerArguments) + let corrections = linter.correct().sorted { $0.location < $1.location } if expectedLocations.isEmpty { XCTAssertEqual(corrections.count, before != expected ? 1 : 0) } else { @@ -106,6 +109,7 @@ private extension Configuration { } } XCTAssertEqual(file.contents, expected) + let path = file.path! do { let corrected = try String(contentsOfFile: path, encoding: .utf8) XCTAssertEqual(corrected, expected) @@ -199,7 +203,8 @@ extension XCTestCase { testMultiByteOffsets: Bool = true, testShebang: Bool = true) { func verify(triggers: [String], nonTriggers: [String]) { - verifyExamples(triggers: triggers, nonTriggers: nonTriggers, configuration: config) + verifyExamples(triggers: triggers, nonTriggers: nonTriggers, configuration: config, + requiresFileOnDisk: ruleDescription.requiresFileOnDisk) } let triggers = ruleDescription.triggeringExamples @@ -215,7 +220,7 @@ extension XCTestCase { } func makeViolations(_ string: String) -> [StyleViolation] { - return violations(string, config: config) + return violations(string, config: config, requiresFileOnDisk: ruleDescription.requiresFileOnDisk) } // Comment doesn't violate @@ -261,10 +266,12 @@ extension XCTestCase { } } - private func verifyExamples(triggers: [String], nonTriggers: [String], configuration config: Configuration) { + private func verifyExamples(triggers: [String], nonTriggers: [String], + configuration config: Configuration, requiresFileOnDisk: Bool) { // Non-triggering examples don't violate for nonTrigger in nonTriggers { - let unexpectedViolations = violations(nonTrigger, config: config) + let unexpectedViolations = violations(nonTrigger, config: config, + requiresFileOnDisk: requiresFileOnDisk) if unexpectedViolations.isEmpty { continue } let nonTriggerWithViolations = render(violations: unexpectedViolations, in: nonTrigger) XCTFail("nonTriggeringExample violated: \n\(nonTriggerWithViolations)") @@ -272,7 +279,8 @@ extension XCTestCase { // Triggering examples violate for trigger in triggers { - let triggerViolations = violations(trigger, config: config) + let triggerViolations = violations(trigger, config: config, + requiresFileOnDisk: requiresFileOnDisk) // Triggering examples with violation markers violate at the marker's location let (cleanTrigger, markerOffsets) = cleanedContentsAndMarkerOffsets(from: trigger)