Skip to content

Commit

Permalink
realm#2737 - Fix false positive violation for the module where only o…
Browse files Browse the repository at this point in the history
…perators are invoked
  • Loading branch information
abdulowork committed Aug 19, 2019
1 parent 40457a7 commit 48fb5ab
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 7 deletions.
7 changes: 7 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -23808,6 +23808,13 @@ import UnknownModule
func foo(error: Swift.Error) {}
```

```swift
import Foundation
import ObjectiveC
let 👨‍👩‍👧‍👦 = #selector(NSArray.contains(_:))
👨‍👩‍👧‍👦 == 👨‍👩‍👧‍👦
```

</details>
<details>
<summary>Triggering Examples</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ extension Dictionary where Key: ExpressibleByStringLiteral {
return elements.compactMap { $0 as? [String: SourceKitRepresentable] }
}

var entities: [[String: SourceKitRepresentable]] {
let entities = self["key.entities"] as? [SourceKitRepresentable] ?? []
return entities.compactMap { $0 as? [String: SourceKitRepresentable] }
}

var enclosedVarParameters: [[String: SourceKitRepresentable]] {
return substructure.flatMap { subDict -> [[String: SourceKitRepresentable]] in
guard let kindString = subDict.kind else {
Expand Down
110 changes: 103 additions & 7 deletions Source/SwiftLintFramework/Rules/Lint/UnusedImportRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public struct UnusedImportRule: CorrectableRule, ConfigurationProviderRule, Anal
"""
import UnknownModule
func foo(error: Swift.Error) {}
""",
"""
import Foundation
import ObjectiveC
let 👨‍👩‍👧‍👦 = #selector(NSArray.contains(_:))
👨‍👩‍👧‍👦 == 👨‍👩‍👧‍👦
"""
],
triggeringExamples: [
Expand Down Expand Up @@ -165,7 +171,8 @@ private extension File {
var imports = Set<String>()
var usrFragments = Set<String>()
var nextIsModuleImport = false
for token in syntaxMap.tokens {
let tokens = syntaxMap.tokens
for token in tokens {
guard let tokenKind = SyntaxKind(rawValue: token.type) else {
continue
}
Expand Down Expand Up @@ -195,30 +202,119 @@ private extension File {
}
}

if let usr = cursorInfo["key.modulename"] as? String {
usrFragments.formUnion(usr.split(separator: ".").map(String.init))
}
appendUsedImports(cursorInfo: cursorInfo, usrFragments: &usrFragments)
}

// Always disallow 'import Swift' because it's available without importing.
usrFragments.remove("Swift")
var unusedImports = imports.subtracting(usrFragments)
// Certain Swift attributes requires importing Foundation.
if unusedImports.contains("Foundation") && containsAttributesRequiringFoundation() {
unusedImports.remove("Foundation")
}

if !unusedImports.isEmpty {
unusedImports.subtract(
operatorImports(
arguments: compilerArguments,
processedTokenOffsets: Set(tokens.map { $0.offset })
)
)
}

return rangedAndSortedUnusedImports(of: Array(unusedImports), contents: contentsNSString)
}

func rangedAndSortedUnusedImports(of unusedImports: [String], contents: NSString) -> [(String, NSRange)] {
return unusedImports
.map { module in
let testableImportRange = contentsNSString.range(of: "@testable import \(module)\n")
let testableImportRange = contents.range(of: "@testable import \(module)\n")
if testableImportRange.location != NSNotFound {
return (module, testableImportRange)
}

return (module, contentsNSString.range(of: "import \(module)\n"))
return (module, contents.range(of: "import \(module)\n"))
}
.sorted(by: { $0.1.location < $1.1.location })
}

private func containsAttributesRequiringFoundation() -> Bool {
// Operators are omitted in the editor.open request and thus have to be looked up by the indexsource request
func operatorImports(arguments: [String], processedTokenOffsets: Set<Int>) -> Set<String> {
guard let index = try? Request.index(file: path!, arguments: arguments).sendIfNotDisabled() else {
queuedPrintError("Could not get index")
return []
}

let operatorEntities = flatEntities(entity: index).filter { mightBeOperator(kind: $0["key.kind"] as? String) }
let offsetPerLine = self.offsetPerLine()
var imports = Set<String>()

for entity in operatorEntities {
if
let line = entity["key.line"] as? Int64,
let column = entity["key.column"] as? Int64,
let lineOffset = offsetPerLine[Int(line) - 1] {
let offset = lineOffset + column - 1

// Filter already processed tokens such as static methods that are not operators
guard !processedTokenOffsets.contains(Int(offset)) else { continue }

let cursorInfoRequest = Request.cursorInfo(file: path!, offset: offset, arguments: arguments)
guard let cursorInfo = try? cursorInfoRequest.sendIfNotDisabled() else {
queuedPrintError("Could not get cursor info")
continue
}

appendUsedImports(cursorInfo: cursorInfo, usrFragments: &imports)
}
}

return imports
}

typealias Entity = [String: SourceKitRepresentable]
func flatEntities(entity: Entity) -> [Entity] {
let entities = entity.entities
if entities.isEmpty {
return [entity]
} else {
return [entity] + entities.flatMap { flatEntities(entity: $0) }
}
}

func offsetPerLine() -> [Int: Int64] {
return [Int: Int64](
uniqueKeysWithValues: contents.bridge()
.components(separatedBy: "\n")
.map {
Int64($0.bridge().lengthOfBytes(using: .utf8))
}
.reduce([Int64(0)]) { result, length in
let newLineCharacterLength = Int64(1)
let lineLength = length + newLineCharacterLength
return result + [(result.last ?? 0) + lineLength]
}
.enumerated()
.map { ($0.offset, $0.element) }
)
}

// Operators that are a part of some body are reported as method.static
func mightBeOperator(kind: String?) -> Bool {
guard let kind = kind else { return false }
return [
"source.lang.swift.ref.function.operator",
"source.lang.swift.ref.function.method.static"
].contains { kind.hasPrefix($0) }
}

func appendUsedImports(cursorInfo: [String: SourceKitRepresentable], usrFragments: inout Set<String>) {
if let usr = cursorInfo["key.modulename"] as? String {
usrFragments.formUnion(usr.split(separator: ".").map(String.init))
}
}

func containsAttributesRequiringFoundation() -> Bool {
guard contents.contains("@objc") else {
return false
}
Expand Down

0 comments on commit 48fb5ab

Please sign in to comment.