-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add unused_import config options to require imports for each module used #3123
Conversation
For example, if `CGFloat` is used in a file where only `UIKit` is imported but not `CoreGraphics`, this will be a violation even if the file previously compiled. This is because Swift allows referencing some declarations that are only transitively imported. Enabling the `require_explicit_imports` configuration option will require that the module of every declaration referenced in a source file be explicitly imported. This will add significant noise to the imports list, but has a few advantages: 1. It will be easier to understand all the dependencies explicitly referenced in a source file. 2. Correcting the `unused_import` rule will no longer introduce compilation errors in files that compiled prior to the correction. Missing imports will always be added to the top of the file, which may cause `sorted_imports` violations. You may run SwiftLint autocorrect with the `sorted_imports` rule enabled to re-sort those import statements afterwards.
I want to add tests and a changelog entry before merging this. |
Generated by 🚫 Danger |
Oh Swift on Linux 🤦♂
will fix tomorrow |
@ZevEisenberg @marcelofabri @PaulTaykalo I thought you all might be interested in the enhancements to We can now use the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
typealias Foo = CFData | ||
""", configuration: [ | ||
"require_explicit_imports": true | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already wrote it somewhere. there' s an idea to add a comment to the Example structure so that
it will explain why trigger should be triggered :) Totally out of the scope of this PR
Example("""
↓import Foundation
typealias Foo = CFData
""", configuration: [
"require_explicit_imports": true
],comment: "Using transitive type 'CFData' from CoreFoundation should require CoreFoundation import")
This example is pretty obvious, but there are some examples that will help understanding why one or another rule should or should not be triggered.
also, this comment part can be shown in violation. I think additional PR will be needed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, that would be great for documentation purposes, but also to have nicer descriptions when tests fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added. as an issue here.
#3125
/// Defaults to the file where this initializer is called. | ||
/// - line: The line in the file where the example is located. | ||
/// Defaults to the line where this initializer is called. | ||
init(_ code: String, configuration: Any? = nil, testMultiByteOffsets: Bool = true, testOnLinux: Bool = true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great to add a test asserting that the two new parameters default to true unless otherwise specified.
|
||
/// Returns whether or not the file contains any attributes that require the Foundation module. | ||
func containsAttributesRequiringFoundation() -> Bool { | ||
guard contents.contains("@objc") else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this catch @objc
in a comment? i.e. should this trigger?
// We don't need @objc because we're cool
@nonobjc func foo() {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this is just a fast path that skips more expensive checks further down.
} | ||
A.dispatchMain() | ||
↓import Dispatch | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra newline intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's a limitation of the rule right now that the import statement can't be at the very end of the file. If the file has a trailing newline, that's fine.
typealias Foo = CFData | ||
""", configuration: [ | ||
"require_explicit_imports": true | ||
], testMultiByteOffsets: false, testOnLinux: false): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it just a coincidence that testMultibyteOffsets
and testOnLinux
are always paired in this PR? Is there a scenario where you would want one but not the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a coincidence.
static func dispatchMain() {} | ||
} | ||
A.dispatchMain() | ||
"""): Example(""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you put the values (as opposed to the keys) in this dictionary on their own line, they get indented, which to my eye is easier to read.
@@ -0,0 +1,53 @@ | |||
public struct TransitiveModuleConfiguration: Equatable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs on this type would be nice
Source/SwiftLintFramework/Extensions/Dictionary+SwiftLint.swift
Outdated
Show resolved
Hide resolved
.objc, | ||
.objcName, | ||
.objcMembers, | ||
.objcNonLazyRealization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in scope for this PR, but I wanted to call it out: the lack of trailing commas on the last line of collection literals made #3040 harder, because I had to special-case a bunch of search and replace. I strongly recommend enforcing trailing commas in any collection-heavy codebase. Just my 2¢
@@ -3,22 +3,36 @@ | |||
public struct Example { | |||
/// The contents of the example | |||
public private(set) var code: String | |||
/// The untyped configuration to apply to the rule, if deviating from the default configuration | |||
public private(set) var configuration: Any? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the configuration untyped? Might be nice to include something in the comma, so others understand the intended usage.
/// The untyped configuration to apply to the rule, if deviating from the default configuration | ||
public private(set) var configuration: Any? | ||
/// Whether the example should be tested by prepending multibyte grapheme clusters | ||
public private(set) var testMultiByteOffsets: Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not very clear from the comment what is actually meant. After reading it, I have several questions that I'd prefer not to have to go digging through the source code to answer:
- why do multibyte grapheme clusters matter?
- why might I want to disable them?
- which multibyte clusters are tested? Are they specific or random?
- where and when are the multibyte characters prepended, and what effect does that have?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do multibyte grapheme clusters matter?
In the past, some rules were written with some assumptions that each character was one byte.
Characters that extend beyond a single byte encoded in UTF-8 need to be handled correctly.
The testMultiByteOffsets
setting prepends some multi-byte grapheme clusters to the code in the example (specifically /* 👨👩👧👦👨👩👧👦👨👩👧👦 */
) to ensure that the locations where violations are reported are correct, and that corrections are properly applied.
why might I want to disable them?
Some examples aren't resilient to prepending this content to its code and need to disable this part of the test. For example, this unused import code:
import Foundation
typealias Foo = CFData
Gets corrected by replacing import Foundation
with import CoreFoundation
:
import CoreFoundation
typealias Foo = CFData
However, when the violating example gets translated to this by the testMultiByteOffsets
setting:
/* 👨👩👧👦👨👩👧👦👨👩👧👦 */
import Foundation
typealias Foo = CFData
then the correction becomes:
import CoreFoundation
/* 👨👩👧👦👨👩👧👦👨👩👧👦 */
typealias Foo = CFData
Which fails the test. There's nothing wrong with the rule's behavior, it just doesn't match what the simplistic auto-generated rule expects.
which multibyte clusters are tested? Are they specific or random?
Specific: /* 👨👩👧👦👨👩👧👦👨👩👧👦 */
where and when are the multibyte characters prepended, and what effect does that have?
See above.
static func dispatchMain() {} | ||
} | ||
A.dispatchMain() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intentional whitespace? Here and above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intentional, yes: #3123 (comment)
Co-Authored-By: Zev Eisenberg <zev@zeveisenberg.com>
Co-Authored-By: Zev Eisenberg <zev@zeveisenberg.com>
To refer the reader to what the effect of that setting is.
Awesome addition! Thank you for adding this. |
* master: (101 commits) JUnit reporter for GitLab artifact:report:junit (realm#3177) Add empty changelog section release 0.39.2 Update CI to run jobs with Xcode 11.0 to 11.4 (realm#3168) Fix false positives in valid_ibinspectable rule when using Swift 5.2 (realm#3155) Fix attributes rule false positive with Swift 5.2 (realm#3154) Fix CHANGELOG link Fix false positives in redundant_objc_attribute with Swift 5.2 (realm#3152) Fix false positives on implicit_getter with Swift 5.2+ (realm#3151) Simplify regex (realm#3145) fix links about configuration rules (realm#3142) Add unused_import config options to require imports for each module used (realm#3123) Add empty changelog section release 0.39.1 Temporarily remove all SwiftSyntax rules and support (realm#3107) Fix unused_import rule reported locations and corrections (realm#3106) release 0.39.0 Fix false positive in `empty_string` rule with multiline literals (realm#3101) Fix PrivateActionRule in Swift 5.2 (realm#3092) Fix false positive in implicit_getter with Swift 5.2 (realm#3099) ... # Conflicts: # Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift
(two years later) I used this today on a large project. Thanks for this addition! |
I’m glad you found it useful! We run it daily on Lyft’s 1M+ lines of Swift. |
Continued from #3117
For example, if
CGFloat
is used in a file where onlyUIKit
is imported but notCoreGraphics
, this will be a violation even if the file previously compiled.This is because Swift allows referencing some declarations that are only transitively imported.
Enabling the
require_explicit_imports
configuration option will require that the module of every declaration referenced in a source file be explicitly imported.This will add significant noise to the imports list, but has a few advantages:
unused_import
rule will no longer introduce compilation errors in files that compiled prior to the correction.If missing imports are added to a file when correcting it, the
sorted_imports
rule will be automatically run on that file.If you with to allow some imports to be implicitly importable transitively, you may specify the
allowed_transitive_imports
configuration: