Skip to content

Commit

Permalink
Update UnusedDeclarationRule to use SourceKittenDictionary
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulTaykalo committed Nov 7, 2019
1 parent 18a3896 commit 796cac6
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 72 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

#### Breaking

* None.
* Use SourceKittenDictionary wrapper over dictionaries
returned from SourceKitten
[PaulTaykalo](https://github.com/PaulTaykalo)
[#2922](https://github.com/realm/SwiftLint/issues/2922)

#### Experimental

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public struct DiscardedNotificationCenterObserverRule: ASTRule, ConfigurationPro

if let lastMatch = file.match(pattern: "\\breturn\\s+", with: [.keyword], range: range).last,
lastMatch.location == range.length - lastMatch.length,
let lastFunction = functions(forByteOffset: offset, in: file.structureDictionary).last,
let lastFunction = file.structureDictionary.functions(forByteOffset: offset).last,
!lastFunction.enclosedSwiftAttributes.contains(.discardableResult) {
return []
}
Expand All @@ -82,23 +82,24 @@ public struct DiscardedNotificationCenterObserverRule: ASTRule, ConfigurationPro
}
}

private func functions(forByteOffset byteOffset: Int, in dictionary: SourceKittenDictionary)
-> [SourceKittenDictionary] {
var results = [SourceKittenDictionary]()
private extension SourceKittenDictionary {
func functions(forByteOffset byteOffset: Int) -> [SourceKittenDictionary] {
var results = [SourceKittenDictionary]()

func parse(_ dictionary: SourceKittenDictionary) {
guard let offset = dictionary.offset,
let byteRange = dictionary.length.map({ NSRange(location: offset, length: $0) }),
NSLocationInRange(byteOffset, byteRange) else {
return
}
func parse(_ dictionary: SourceKittenDictionary) {
guard let offset = dictionary.offset,
let byteRange = dictionary.length.map({ NSRange(location: offset, length: $0) }),
NSLocationInRange(byteOffset, byteRange) else {
return
}

if let kind = dictionary.kind.flatMap(SwiftDeclarationKind.init),
SwiftDeclarationKind.functionKinds.contains(kind) {
results.append(dictionary)
if let kind = dictionary.kind.flatMap(SwiftDeclarationKind.init),
SwiftDeclarationKind.functionKinds.contains(kind) {
results.append(dictionary)
}
dictionary.substructure.forEach(parse)
}
dictionary.substructure.forEach(parse)
parse(self)
return results
}
parse(dictionary)
return results
}
105 changes: 57 additions & 48 deletions Source/SwiftLintFramework/Rules/Lint/UnusedDeclarationRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,82 +126,86 @@ public struct UnusedDeclarationRule: AutomaticTestableRule, ConfigurationProvide
// MARK: - File Extensions

private extension File {
func allCursorInfo(compilerArguments: [String]) -> [[String: SourceKitRepresentable]] {
guard let path = path, let editorOpen = try? Request.editorOpen(file: self).sendIfNotDisabled() else {
func allCursorInfo(compilerArguments: [String]) -> [SourceKittenDictionary] {
guard let path = path,
let editorOpen = (try? Request.editorOpen(file: self).sendIfNotDisabled())
.map(SourceKittenDictionary.init) else {
return []
}

return syntaxMap.tokens.compactMap { token in
guard let kind = SyntaxKind(rawValue: token.type), !syntaxKindsToSkip.contains(kind) else {
return nil
}
return syntaxMap.tokens
.compactMap { token in
guard let kind = SyntaxKind(rawValue: token.type), !syntaxKindsToSkip.contains(kind) else {
return nil
}

let offset = Int64(token.offset)
let request = Request.cursorInfo(file: path, offset: offset, arguments: compilerArguments)
guard var cursorInfo = try? request.sendIfNotDisabled() else {
return nil
}
let offset = Int64(token.offset)
let request = Request.cursorInfo(file: path, offset: offset, arguments: compilerArguments)
guard var cursorInfo = try? request.sendIfNotDisabled() else {
return nil
}

if let acl = File.aclAtOffset(offset, substructureElement: editorOpen) {
cursorInfo["key.accessibility"] = acl
if let acl = File.aclAtOffset(offset, substructureElement: editorOpen) {
cursorInfo["key.accessibility"] = acl
}
cursorInfo["swiftlint.offset"] = offset
return cursorInfo
}
cursorInfo["swiftlint.offset"] = offset
return cursorInfo
}
.map(SourceKittenDictionary.init)
}

static func declaredUSRs(allCursorInfo: [[String: SourceKitRepresentable]], includePublicAndOpen: Bool)
static func declaredUSRs(allCursorInfo: [SourceKittenDictionary], includePublicAndOpen: Bool)
-> [(usr: String, nameOffset: Int)] {
return allCursorInfo.compactMap { cursorInfo in
return declaredUSRAndOffset(cursorInfo: cursorInfo, includePublicAndOpen: includePublicAndOpen)
}
}

static func referencedUSRs(allCursorInfo: [[String: SourceKitRepresentable]]) -> [String] {
static func referencedUSRs(allCursorInfo: [SourceKittenDictionary]) -> [String] {
return allCursorInfo.compactMap(referencedUSR)
}

static func testCaseUSRs(allCursorInfo: [[String: SourceKitRepresentable]]) -> Set<String> {
static func testCaseUSRs(allCursorInfo: [SourceKittenDictionary]) -> Set<String> {
return Set(allCursorInfo.compactMap(testCaseUSR))
}

private static func declaredUSRAndOffset(cursorInfo: [String: SourceKitRepresentable], includePublicAndOpen: Bool)
private static func declaredUSRAndOffset(cursorInfo: SourceKittenDictionary, includePublicAndOpen: Bool)
-> (usr: String, nameOffset: Int)? {
if let offset = cursorInfo["swiftlint.offset"] as? Int64,
let usr = cursorInfo["key.usr"] as? String,
let kind = (cursorInfo["key.kind"] as? String).flatMap(SwiftDeclarationKind.init(rawValue:)),
if let offset = cursorInfo.swiftlintOffset,
let usr = cursorInfo.usr,
let kind = cursorInfo.kind.flatMap(SwiftDeclarationKind.init(rawValue:)),
!declarationKindsToSkip.contains(kind),
let acl = (cursorInfo["key.accessibility"] as? String).flatMap(AccessControlLevel.init(rawValue:)),
let acl = cursorInfo.accessibility.flatMap(AccessControlLevel.init(rawValue:)),
includePublicAndOpen || [.internal, .private, .fileprivate].contains(acl) {
// Skip declarations marked as @IBOutlet, @IBAction or @objc
// since those might not be referenced in code, but only dynamically (e.g. Interface Builder)
if let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
if let annotatedDecl = cursorInfo.annotatedDeclaration,
["@IBOutlet", "@IBAction", "@objc", "@IBInspectable"].contains(where: annotatedDecl.contains) {
return nil
}

// Classes marked as @UIApplicationMain are used by the operating system as the entry point into the app.
if let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
if let annotatedDecl = cursorInfo.annotatedDeclaration,
annotatedDecl.contains("@UIApplicationMain") {
return nil
}

// Skip declarations that override another. This works for both subclass overrides &
// protocol extension overrides.
if cursorInfo["key.overrides"] != nil {
if cursorInfo.value["key.overrides"] != nil {
return nil
}

// Sometimes default protocol implementations don't have `key.overrides` set but they do have
// `key.related_decls`.
if cursorInfo["key.related_decls"] != nil {
if cursorInfo.value["key.related_decls"] != nil {
return nil
}

// Skip CodingKeys as they are used
if kind == .enum,
cursorInfo.name == "CodingKeys",
let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
let annotatedDecl = cursorInfo.annotatedDeclaration,
annotatedDecl.contains("usr=\"s:s9CodingKeyP\">CodingKey<") {
return nil
}
Expand All @@ -212,49 +216,54 @@ private extension File {
return nil
}

private static func referencedUSR(cursorInfo: [String: SourceKitRepresentable]) -> String? {
if let usr = cursorInfo["key.usr"] as? String,
let kind = cursorInfo["key.kind"] as? String,
private static func referencedUSR(cursorInfo: SourceKittenDictionary) -> String? {
if let usr = cursorInfo.usr,
let kind = cursorInfo.kind,
kind.contains("source.lang.swift.ref") {
return usr
}

return nil
}

private static func testCaseUSR(cursorInfo: [String: SourceKitRepresentable]) -> String? {
if let kind = (cursorInfo["key.kind"] as? String).flatMap(SwiftDeclarationKind.init(rawValue:)),
private static func testCaseUSR(cursorInfo: SourceKittenDictionary) -> String? {
if let kind = (cursorInfo.kind).flatMap(SwiftDeclarationKind.init(rawValue:)),
kind == .class,
let annotatedDecl = cursorInfo["key.annotated_decl"] as? String,
let annotatedDecl = cursorInfo.annotatedDeclaration,
annotatedDecl.contains("<Type usr=\"c:objc(cs)XCTestCase\">XCTestCase</Type>"),
let usr = cursorInfo["key.usr"] as? String {
let usr = cursorInfo.usr {
return usr
}

return nil
}

private static func aclAtOffset(_ offset: Int64, substructureElement: [String: SourceKitRepresentable]) -> String? {
if let nameOffset = substructureElement["key.nameoffset"] as? Int64,
private static func aclAtOffset(_ offset: Int64, substructureElement: SourceKittenDictionary) -> String? {
if let nameOffset = substructureElement.nameOffset,
nameOffset == offset,
let acl = substructureElement["key.accessibility"] as? String {
let acl = substructureElement.accessibility {
return acl
}
if let substructure = substructureElement[SwiftDocKey.substructure.rawValue] as? [SourceKitRepresentable] {
let nestedSubstructure = substructure.compactMap({ $0 as? [String: SourceKitRepresentable] })
for child in nestedSubstructure {
if let acl = File.aclAtOffset(offset, substructureElement: child) {
return acl
}
for child in substructureElement.substructure {
if let acl = File.aclAtOffset(offset, substructureElement: child) {
return acl
}
}
return nil
}
}

private extension Dictionary where Value == SourceKitRepresentable, Key == String {
var name: String? {
return self["key.name"] as? String
private extension SourceKittenDictionary {
var swiftlintOffset: Int64? {
return value["swiftlint.offset"] as? Int64
}

var usr: String? {
return value["key.usr"] as? String
}

var annotatedDeclaration: String? {
return value["key.annotated_decl"] as? String
}
}

Expand Down
20 changes: 14 additions & 6 deletions Source/SwiftLintFramework/Rules/Lint/UnusedImportRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,13 @@ private extension File {
}
let cursorInfoRequest = Request.cursorInfo(file: path!, offset: Int64(token.offset),
arguments: compilerArguments)
guard let cursorInfo = try? cursorInfoRequest.sendIfNotDisabled() else {
guard let cursorInfo = (try? cursorInfoRequest.sendIfNotDisabled()).map(SourceKittenDictionary.init) else {
queuedPrintError("Could not get cursor info")
continue
}
if nextIsModuleImport {
if let importedModule = cursorInfo["key.modulename"] as? String,
cursorInfo["key.kind"] as? String == "source.lang.swift.ref.module" {
if let importedModule = cursorInfo.moduleName,
cursorInfo.kind == "source.lang.swift.ref.module" {
imports.insert(importedModule)
nextIsModuleImport = false
continue
Expand Down Expand Up @@ -260,7 +260,8 @@ private extension File {
guard !processedTokenOffsets.contains(Int(offset)) else { continue }

let cursorInfoRequest = Request.cursorInfo(file: path!, offset: offset, arguments: arguments)
guard let cursorInfo = try? cursorInfoRequest.sendIfNotDisabled() else {
guard let cursorInfo = (try? cursorInfoRequest.sendIfNotDisabled())
.map(SourceKittenDictionary.init) else {
queuedPrintError("Could not get cursor info")
continue
}
Expand Down Expand Up @@ -306,8 +307,8 @@ private extension File {
].contains { kind.hasPrefix($0) }
}

func appendUsedImports(cursorInfo: [String: SourceKitRepresentable], usrFragments: inout Set<String>) {
if let usr = cursorInfo["key.modulename"] as? String {
func appendUsedImports(cursorInfo: SourceKittenDictionary, usrFragments: inout Set<String>) {
if let usr = cursorInfo.moduleName {
usrFragments.formUnion(usr.split(separator: ".").map(String.init))
}
}
Expand All @@ -329,6 +330,13 @@ private extension File {
}
}

private extension SourceKittenDictionary {
/// Module name in @import expressions
var moduleName: String? {
return value["key.modulename"] as? String
}
}

private extension Dictionary where Value == SourceKitRepresentable, Key == String {
var entities: [[String: SourceKitRepresentable]] {
let entities = self["key.entities"] as? [SourceKitRepresentable] ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public struct MultilineFunctionChainsRule: ASTRule, OptInRule, ConfigurationProv
}
}

fileprivate extension SourceKittenDictionary {
private extension SourceKittenDictionary {
var subcalls: [SourceKittenDictionary] {
return substructure.compactMap { dictionary -> SourceKittenDictionary? in
guard dictionary.kind.flatMap(SwiftExpressionKind.init(rawValue:)) == .call else {
Expand Down

0 comments on commit 796cac6

Please sign in to comment.