Skip to content

Commit

Permalink
Merge pull request #998 from marcelofabri/unused-enumerated
Browse files Browse the repository at this point in the history
Add unused_enumerated rule
  • Loading branch information
marcelofabri authored Dec 18, 2016
2 parents 5e24a89 + 86fad3c commit 619f5d4
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@
[JP Simard](https://github.com/jpsim)
[#982](https://github.com/realm/SwiftLint/issues/982)

* Add `unused_enumerated` rule that warns against unused indexes when using
`.enumerated()` on a for loop, e.g. `for (_, foo) in bar.enumerated()`.
[Marcelo Fabri](https://github.com/marcelofabri)
[#619](https://github.com/realm/SwiftLint/issues/619)

##### Bug Fixes

* Fix `weak_delegate` rule reporting a violation for variables containing
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public let masterRuleList = RuleList(rules:
TypeBodyLengthRule.self,
TypeNameRule.self,
UnusedClosureParameterRule.self,
UnusedEnumeratedRule.self,
ValidDocsRule.self,
ValidIBInspectableRule.self,
VariableNameRule.self,
Expand Down
99 changes: 99 additions & 0 deletions Source/SwiftLintFramework/Rules/UnusedEnumeratedRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// UnusedEnumeratedRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 12/17/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct UnusedEnumeratedRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "unused_enumerated",
name: "Unused Enumerated",
description: "When the index is not used, .enumerated() can be removed.",
nonTriggeringExamples: [
"for (idx, foo) in bar.enumerated() { }\n",
"for (_, foo) in bar.enumerated().something() { }\n",
"for (_, foo) in bar.something() { }\n",
"for foo in bar.enumerated() { }\n",
"for foo in bar { }\n",
"for (idx, _) in bar.enumerated() { }\n"
],
triggeringExamples: [
"for (↓_, foo) in bar.enumerated() { }\n",
"for (↓_, foo) in abc.bar.enumerated() { }\n",
"for (↓_, foo) in abc.something().enumerated() { }\n"
]
)

public func validateFile(_ file: File,
kind: StatementKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {

guard kind == .forEach,
isEnumeratedCall(dictionary),
let byteRange = byteRangeForVariables(dictionary),
let firstToken = file.syntaxMap.tokensIn(byteRange).first,
firstToken.length == 1,
SyntaxKind(rawValue: firstToken.type) == .keyword,
isUnderscore(file: file, token: firstToken) else {
return []
}

return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: firstToken.offset))
]
}

private func isEnumeratedCall(_ dictionary: [String: SourceKitRepresentable]) -> Bool {
let substructure = dictionary["key.substructure"] as? [SourceKitRepresentable] ?? []
for subItem in substructure {
guard let subDict = subItem as? [String: SourceKitRepresentable],
let kindString = subDict["key.kind"] as? String,
SwiftExpressionKind(rawValue: kindString) == .call,
let name = subDict["key.name"] as? String else {
continue
}

if name.hasSuffix(".enumerated") {
return true
}
}

return false
}

private func byteRangeForVariables(_ dictionary: [String: SourceKitRepresentable]) -> NSRange? {
guard let elements = dictionary["key.elements"] as? [SourceKitRepresentable] else {
return nil
}

let expectedKind = "source.lang.swift.structure.elem.id"
for element in elements {
guard let subDict = element as? [String: SourceKitRepresentable],
subDict["key.kind"] as? String == expectedKind,
let offset = (subDict["key.offset"] as? Int64).map({ Int($0) }),
let length = (subDict["key.length"] as? Int64).map({ Int($0) }) else {
continue
}

return NSRange(location: offset, length: length)
}

return nil
}

private func isUnderscore(file: File, token: SyntaxToken) -> Bool {
let contents = file.contents.bridge()
return contents.substringWithByteRange(start: token.offset, length: token.length) == "_"
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
D40F83881DE9179200524C62 /* TrailingCommaConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40F83871DE9179200524C62 /* TrailingCommaConfiguration.swift */; };
D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */; };
D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */; };
D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */; };
D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */; };
D44254201DB87CA200492EA4 /* ValidIBInspectableRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */; };
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */; };
Expand Down Expand Up @@ -318,6 +319,7 @@
D40F83871DE9179200524C62 /* TrailingCommaConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommaConfiguration.swift; sourceTree = "<group>"; };
D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStringEnumValueRule.swift; sourceTree = "<group>"; };
D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRuleTests.swift; sourceTree = "<group>"; };
D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedEnumeratedRule.swift; sourceTree = "<group>"; };
D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitGetterRule.swift; sourceTree = "<group>"; };
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidIBInspectableRule.swift; sourceTree = "<group>"; };
D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntacticSugarRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -753,6 +755,7 @@
E88DEA8D1B0999CD00A66CB0 /* TypeBodyLengthRule.swift */,
E88DEA911B099B1F00A66CB0 /* TypeNameRule.swift */,
D40AD0891E032F9700F48C30 /* UnusedClosureParameterRule.swift */,
D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */,
E81CDE701C00FEAA00B430F6 /* ValidDocsRule.swift */,
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */,
E88DEA931B099C0900A66CB0 /* VariableNameRule.swift */,
Expand Down Expand Up @@ -1130,6 +1133,7 @@
4A9A3A3A1DC1D75F00DF5183 /* HTMLReporter.swift in Sources */,
D40F83881DE9179200524C62 /* TrailingCommaConfiguration.swift in Sources */,
3B5B9FE11C444DA20009AD27 /* Array+SwiftLint.swift in Sources */,
D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */,
E881985D1BEA97EB00333A11 /* TrailingWhitespaceRule.swift in Sources */,
E832F10B1B17E2F5003F265F /* NSFileManager+SwiftLint.swift in Sources */,
E816194C1BFBF35D00946723 /* SwiftDeclarationKind+SwiftLint.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ class RulesTests: XCTestCase {
verifyRule(UnusedClosureParameterRule.description)
}

func testUnusedEnumerated() {
verifyRule(UnusedEnumeratedRule.description)
}

// swiftlint:disable:next todo
// FIXME: https://github.com/jpsim/SourceKitten/issues/269
// func testValidDocs() {
Expand Down Expand Up @@ -458,6 +462,7 @@ extension RulesTests {
("testTypeBodyLength", testTypeBodyLength),
// ("testTypeName", testTypeName),
("testUnusedClosureParameter", testUnusedClosureParameter),
("testUnusedEnumerated", testUnusedEnumerated),
("testValidIBInspectable", testValidIBInspectable),
// ("testVariableName", testVariableName),
("testVoidReturn", testVoidReturn),
Expand Down

0 comments on commit 619f5d4

Please sign in to comment.