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)