diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f6848f991..28f0d09f50e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ [Marcelo Fabri](https://github.com/marcelofabri) [#2159](https://github.com/realm/SwiftLint/issues/2159) +* Fix false positives on `array_init` rule when using prefix operators. + [Marcelo Fabri](https://github.com/marcelofabri) + [#1877](https://github.com/realm/SwiftLint/issues/1877) + ## 0.30.0: A New Washer and Dryer Set #### Breaking diff --git a/Rules.md b/Rules.md index da5a4e5a596..9ce1125194e 100644 --- a/Rules.md +++ b/Rules.md @@ -276,6 +276,16 @@ foo.something { RouteMapper.map($0) } ``` +```swift +foo.map { !$0 } + +``` + +```swift +foo.map { /* a comment */ !$0 } + +``` +
Triggering Examples @@ -328,6 +338,11 @@ foo.something { RouteMapper.map($0) } ``` +```swift +↓foo.map { /* a comment */ $0 } + +``` +
diff --git a/Source/SwiftLintFramework/Rules/Lint/ArrayInitRule.swift b/Source/SwiftLintFramework/Rules/Lint/ArrayInitRule.swift index b1f337f3fad..eabd7e117b2 100644 --- a/Source/SwiftLintFramework/Rules/Lint/ArrayInitRule.swift +++ b/Source/SwiftLintFramework/Rules/Lint/ArrayInitRule.swift @@ -19,7 +19,9 @@ public struct ArrayInitRule: ASTRule, ConfigurationProviderRule, OptInRule, Auto "foo.map { ((), $0) }\n", "foo.map { $0! }\n", "foo.map { $0! /* force unwrap */ }\n", - "foo.something { RouteMapper.map($0) }\n" + "foo.something { RouteMapper.map($0) }\n", + "foo.map { !$0 }\n", + "foo.map { /* a comment */ !$0 }\n" ], triggeringExamples: [ "↓foo.map({ $0 })\n", @@ -37,7 +39,8 @@ public struct ArrayInitRule: ASTRule, ConfigurationProviderRule, OptInRule, Auto "↓foo.map { elem -> String in\n" + " elem\n" + "}\n", - "↓foo.map { $0 /* a comment */ }\n" + "↓foo.map { $0 /* a comment */ }\n", + "↓foo.map { /* a comment */ $0 }\n" ] ) @@ -68,7 +71,8 @@ public struct ArrayInitRule: ASTRule, ConfigurationProviderRule, OptInRule, Auto isParameterStyleViolation(file: file, dictionary: dictionary, tokens: tokens), let lastToken = tokens.last, case let bodyEndPosition = bodyOffset + bodyLength, - !containsTrailingContent(lastToken: lastToken, bodyEndPosition: bodyEndPosition, file: file) else { + !containsTrailingContent(lastToken: lastToken, bodyEndPosition: bodyEndPosition, file: file), + !containsLeadingContent(tokens: tokens, bodyStartPosition: bodyOffset, file: file) else { return [] } @@ -98,10 +102,38 @@ public struct ArrayInitRule: ASTRule, ConfigurationProviderRule, OptInRule, Auto file: File) -> Bool { let lastTokenEnd = lastToken.offset + lastToken.length let remainingLength = bodyEndPosition - lastTokenEnd - let nsstring = file.contents.bridge() let remainingRange = NSRange(location: lastTokenEnd, length: remainingLength) - let remainingTokens = file.syntaxMap.tokens(inByteRange: remainingRange) - let ranges = NSMutableIndexSet(indexesIn: remainingRange) + return containsContent(inByteRange: remainingRange, file: file) + } + + private func containsLeadingContent(tokens: [SyntaxToken], + bodyStartPosition: Int, + file: File) -> Bool { + let inTokenPosition = tokens.firstIndex(where: { token in + SyntaxKind(rawValue: token.type) == .keyword && file.contents(for: token) == "in" + }) + + let firstToken: SyntaxToken + let start: Int + if let position = inTokenPosition { + let index = tokens.index(after: position) + firstToken = tokens[index] + let inToken = tokens[position] + start = inToken.offset + inToken.length + } else { + firstToken = tokens[0] + start = bodyStartPosition + } + + let length = firstToken.offset - start + let remainingRange = NSRange(location: start, length: length) + return containsContent(inByteRange: remainingRange, file: file) + } + + private func containsContent(inByteRange byteRange: NSRange, file: File) -> Bool { + let nsstring = file.contents.bridge() + let remainingTokens = file.syntaxMap.tokens(inByteRange: byteRange) + let ranges = NSMutableIndexSet(indexesIn: byteRange) for token in remainingTokens { ranges.remove(in: NSRange(location: token.offset, length: token.length))