Skip to content

Commit

Permalink
Merge pull request #723 from daniel-beard/dbeard_fix_force_unwrap_fal…
Browse files Browse the repository at this point in the history
…se_positive

Fix force unwrap false positive. Fixes #721
  • Loading branch information
jpsim authored Jul 12, 2016
2 parents 02a7fb1 + b30a63b commit cccfef7
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
[Sarr Blaise](https://github.com/bsarr007)
[#646](https://github.com/realm/SwiftLint/issues/646)

* Fix force_unwrapping false positive inside strings.
[Daniel Beard](https://github.com/daniel-beard)
[#721](https://github.com/realm/SwiftLint/issues/721)

## 0.11.1: Cuddles... Or Else!

##### Breaking
Expand Down
50 changes: 32 additions & 18 deletions Source/SwiftLintFramework/Rules/ForceUnwrappingRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public struct ForceUnwrappingRule: OptInRule, ConfigurationProviderRule {
"navigationBarHidden, animated: true)",
"if addedToPlaylist && (!self.selectedFilters.isEmpty || " +
"self.searchBar?.text?.isEmpty == false) {}",
"print(\"\\(xVar)!\")"
],
triggeringExamples: [
"let url = NSURL(string: query)↓!",
Expand All @@ -51,15 +52,18 @@ public struct ForceUnwrappingRule: OptInRule, ConfigurationProviderRule {

// capture previous and next of "!"
// http://userguide.icu-project.org/strings/regexp
private static let pattern = "(\\S)!(.?)"
private static let pattern = "(\\S)(!)(.?)"

// swiftlint:disable:next force_try
private static let regularExpression = try! NSRegularExpression(pattern: pattern,
options: [.DotMatchesLineSeparators])
private static let excludingSyntaxKindsForFirstCapture = SyntaxKind
.commentKeywordStringAndTypeidentifierKinds().map { $0.rawValue }
private static let excludingSyntaxKindsForSecondCapture = [SyntaxKind.Identifier.rawValue]
private static let excludingSyntaxKindsForSecondCapture = SyntaxKind
.commentAndStringKinds().map { $0.rawValue }
private static let excludingSyntaxKindsForThirdCapture = [SyntaxKind.Identifier.rawValue]

// swiftlint:disable:next function_body_length
private func violationRangesInFile(file: File) -> [NSRange] {
let contents = file.contents
let nsstring = contents as NSString
Expand All @@ -68,49 +72,59 @@ public struct ForceUnwrappingRule: OptInRule, ConfigurationProviderRule {
return ForceUnwrappingRule.regularExpression
.matchesInString(contents, options: [], range: range)
.flatMap { match -> NSRange? in
if match.numberOfRanges < 2 { return nil }
if match.numberOfRanges < 3 { return nil }

// check first captured range
let firstRange = match.rangeAtIndex(1)
let secondRange = match.rangeAtIndex(2)

let violationRange = NSRange(location: NSMaxRange(firstRange), length: 0)

guard let matchByteFirstRange = contents
.NSRangeToByteRange(start: firstRange.location, length: firstRange.length)
.NSRangeToByteRange(start: firstRange.location, length: firstRange.length),
matchByteSecondRange = contents
.NSRangeToByteRange(start: secondRange.location, length: secondRange.length)
else { return nil }

let tokensInFirstRange = syntaxMap.tokensIn(matchByteFirstRange)
let tokensInSecondRange = syntaxMap.tokensIn(matchByteSecondRange)

// check first captured range
// If not empty, first captured range is comment, string, keyword or typeidentifier.
// We checks "not empty" because tokens may empty without filtering.
let tokensInFirstRangeExcludingSyntaxKindsOnly = tokensInFirstRange.filter({
guard tokensInFirstRange.filter({
ForceUnwrappingRule.excludingSyntaxKindsForFirstCapture.contains($0.type)
})
if !tokensInFirstRangeExcludingSyntaxKindsOnly.isEmpty { return nil }
}).isEmpty else { return nil }

// if first captured range is identifier, generate violation
if tokensInFirstRange.map({ $0.type }).contains(SyntaxKind.Identifier.rawValue) {
return violationRange
}

// check firstCapturedString is ")"
// check second capture '!'
let forceUnwrapNotInCommentOrString = tokensInSecondRange.filter({
ForceUnwrappingRule.excludingSyntaxKindsForSecondCapture.contains($0.type)
}).isEmpty

// check firstCapturedString is ")" and '!' is not within comment or string
let firstCapturedString = nsstring.substringWithRange(firstRange)
if firstCapturedString == ")" { return violationRange }
if firstCapturedString == ")" &&
forceUnwrapNotInCommentOrString { return violationRange }

// check second capture
// check third capture
if match.numberOfRanges == 3 {

// check second captured range
let secondRange = match.rangeAtIndex(2)
guard let matchByteSecondRange = contents
// check third captured range
let secondRange = match.rangeAtIndex(3)
guard let matchByteThirdRange = contents
.NSRangeToByteRange(start: secondRange.location, length: secondRange.length)
else { return nil }

let tokensInSecondRange = syntaxMap.tokensIn(matchByteSecondRange).filter {
ForceUnwrappingRule.excludingSyntaxKindsForSecondCapture.contains($0.type)
let tokensInThirdRange = syntaxMap.tokensIn(matchByteThirdRange).filter {
ForceUnwrappingRule.excludingSyntaxKindsForThirdCapture.contains($0.type)
}
// If not empty, second captured range is identifier.
// If not empty, third captured range is identifier.
// "!" is "operator prefix !".
if !tokensInSecondRange.isEmpty { return nil }
if !tokensInThirdRange.isEmpty { return nil }
}

// check structure
Expand Down

0 comments on commit cccfef7

Please sign in to comment.