Skip to content

Commit

Permalink
Merge pull request realm#807 from masters3d/closure-spacing
Browse files Browse the repository at this point in the history
added closure-spacing-rule
  • Loading branch information
norio-nomura authored Oct 24, 2016
2 parents 5f33d02 + 8bed233 commit c16efa1
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
[Daniel Beard](https://github.com/daniel-beard)
[#764](https://github.com/realm/SwiftLint/issues/764)

* Added opt-In rule to makes closure expressions spacing consistent.
[J. Cheyo Jimenez](https://github.com/masters3d)
[#770](https://github.com/realm/SwiftLint/issues/770)

* Adds `allow_private_set` configuration for the `private_outlet` rule.
[Rohan Dhaimade](https://github.com/HaloZero)

Expand Down
18 changes: 16 additions & 2 deletions Source/SwiftLintFramework/Extensions/File+Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ private var structureCache = Cache({file -> Structure? in
}
return nil
})
private var syntaxMapCache = Cache({file in responseCache.get(file).map(SyntaxMap.init)})
private var syntaxKindsByLinesCache = Cache({file in file.syntaxKindsByLine()})
private var syntaxMapCache = Cache({ file in responseCache.get(file).map(SyntaxMap.init) })
private var syntaxKindsByLinesCache = Cache({ file in file.syntaxKindsByLine() })
private var syntaxTokensByLinesCache = Cache({ file in file.syntaxTokensByLine() })

private typealias AssertHandler = () -> ()
private var assertHandlers = [String: AssertHandler?]()
Expand Down Expand Up @@ -115,6 +116,17 @@ extension File {
return syntaxMap
}

internal var syntaxTokensByLines: [[SyntaxToken]] {
guard let syntaxTokensByLines = syntaxTokensByLinesCache.get(self) else {
if let handler = assertHandler {
handler()
return []
}
fatalError("Never call this for file that sourcekitd fails.")
}
return syntaxTokensByLines
}

internal var syntaxKindsByLines: [[SyntaxKind]] {
guard let syntaxKindsByLines = syntaxKindsByLinesCache.get(self) else {
if let handler = assertHandler {
Expand All @@ -131,6 +143,7 @@ extension File {
assertHandlers.removeValueForKey(cacheKey)
structureCache.invalidate(self)
syntaxMapCache.invalidate(self)
syntaxTokensByLinesCache.invalidate(self)
syntaxKindsByLinesCache.invalidate(self)
}

Expand All @@ -141,6 +154,7 @@ extension File {
assertHandlers = [:]
structureCache.clear()
syntaxMapCache.clear()
syntaxTokensByLinesCache.clear()
syntaxKindsByLinesCache.clear()
}

Expand Down
19 changes: 16 additions & 3 deletions Source/SwiftLintFramework/Extensions/File+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,11 @@ extension File {
}
}

internal func syntaxKindsByLine() -> [[SyntaxKind]]? {
internal func syntaxTokensByLine() -> [[SyntaxToken]]? {
if sourcekitdFailed {
return nil
}
var results = [[SyntaxKind]](count: lines.count + 1, repeatedValue: [])
var results = [[SyntaxToken]](count: lines.count + 1, repeatedValue: [])
var tokenGenerator = syntaxMap.tokens.generate()
var lineGenerator = lines.generate()
var maybeLine = lineGenerator.next()
Expand All @@ -116,7 +116,7 @@ extension File {
let tokenRange = NSRange(location: token.offset, length: token.length)
if NSLocationInRange(token.offset, line.byteRange) ||
NSLocationInRange(line.byteRange.location, tokenRange) {
results[line.index].append(SyntaxKind(rawValue: token.type)!)
results[line.index].append(token)
}
let tokenEnd = NSMaxRange(tokenRange)
let lineEnd = NSMaxRange(line.byteRange)
Expand All @@ -132,6 +132,19 @@ extension File {
return results
}

internal func syntaxKindsByLine() -> [[SyntaxKind]]? {

if sourcekitdFailed {
return nil
}
guard let tokens = syntaxTokensByLine() else {
return nil
}

return tokens.map { $0.flatMap { SyntaxKind.init(rawValue: $0.type) } }

}

//Added by S2dent
/**
This function returns only matches that are not contained in a syntax kind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension Structure {
guard let
offset = (dictionary["key.offset"] as? Int64).map({ Int($0) }),
byteRange = (dictionary["key.length"] as? Int64).map({ Int($0) })
.map({NSRange(location: offset, length: $0)})
.map({ NSRange(location: offset, length: $0) })
where NSLocationInRange(byteOffset, byteRange) else {
return
}
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Models/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public struct Configuration: Equatable {
$0.dynamicType.description.identifier
}

let validDisabledRules = disabledRules.filter({ validRuleIdentifiers.contains($0)})
let validDisabledRules = disabledRules.filter({ validRuleIdentifiers.contains($0) })
let invalidRules = disabledRules.filter({ !validRuleIdentifiers.contains($0) })
if !invalidRules.isEmpty {
for invalidRule in invalidRules {
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 @@ -42,6 +42,7 @@ public struct RuleList {

public let masterRuleList = RuleList(rules:
ClosingBraceRule.self,
ClosureSpacingRule.self,
ColonRule.self,
CommaRule.self,
ConditionalReturnsOnNewline.self,
Expand Down
109 changes: 109 additions & 0 deletions Source/SwiftLintFramework/Rules/ClosureSpacingRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// ClosureSpacingRule.swift
// SwiftLint
//
// Created by J. Cheyo Jimenez on 2016-08-26.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct ClosureSpacingRule: Rule, ConfigurationProviderRule, OptInRule {

public var configuration = SeverityConfiguration(.Warning)

public init() {}

public static let description = RuleDescription(
identifier: "closure_spacing",
name: "Closure Spacing",
description: "Closure expressions should have a single space inside each brace.",
nonTriggeringExamples: [
"[].map ({ $0.description })",
"[].filter { $0.contains(location) }"
],
triggeringExamples: [
"[].filter({$0.contains(location)})",
"[].map({$0})"
]
)

// this helps cut down the time to search true a file by
// skipping lines that do not have at least one { and one } brace
func lineContainsBracesIn(range: NSRange, content: NSString) -> NSRange? {
let start = content.rangeOfString("{", options: [.LiteralSearch], range: range)
guard start.length != 0 else { return nil }
let end = content.rangeOfString("}",
options: [.LiteralSearch, .BackwardsSearch], range: range)
guard end.length != 0 else { return nil }
guard start.location < end.location else { return nil }
return NSRange(location: start.location,
length: end.location - start.location + 1)
}

// returns ranges of braces { or } in the same line
func validBraces(file: File) -> [NSRange] {
let nsstring = (file.contents as NSString)
let bracePattern = regex("\\{|\\}")
let linesTokens = file.syntaxTokensByLines
let kindsToExclude = SyntaxKind.commentAndStringKinds().map { $0.rawValue }

// find all lines and accurences of open { and closed } braces
var linesWithBraces = [[NSRange]]()
for eachLine in file.lines {
guard let nsrange = lineContainsBracesIn(eachLine.range, content: nsstring)
else { continue }

let braces = bracePattern.matchesInString(file.contents, options: [],
range: nsrange).map { $0.range }
// filter out braces in comments and strings
let tokens = linesTokens[eachLine.index].filter { kindsToExclude.contains($0.type) }
let tokenRanges = tokens.flatMap {
file.contents.byteRangeToNSRange(start: $0.offset, length: $0.length) }
linesWithBraces.append(braces.filter { !$0.intersectsRanges(tokenRanges) })
}
return linesWithBraces.flatMap { $0 }
}

public func validateFile(file: File) -> [StyleViolation] {

// match open braces to corresponding closing braces
func matchBraces(validBraceLocations: [NSRange]) -> [NSRange] {
if validBraceLocations.isEmpty { return [] }
var validBraces = validBraceLocations
var ranges = [NSRange]()
var bracesAsString = validBraces.map {
file.contents.substring($0.location, length: $0.length) }.joinWithSeparator("")
while let foundRange = bracesAsString.rangeOfString("{}") {
let startIndex = bracesAsString.startIndex.distanceTo(foundRange.startIndex)
let location = validBraces[startIndex].location
let length = validBraces[startIndex + 1 ].location + 1 - location
ranges.append(NSRange(location:location, length: length))
bracesAsString.replaceRange(foundRange, with: "")
validBraces.removeRange(startIndex...startIndex + 1)
}
return ranges
}

// matching ranges of {}
let matchedUpBraces = matchBraces(validBraces(file))

var violationRanges = matchedUpBraces.filter {
//removes enclosing brances to just content
let content = file.contents.substring($0.location + 1, length: $0.length - 2)
if content.isEmpty { return false } // case when {} is not a closure
let cleaned = content.stringByTrimmingCharactersInSet(.whitespaceCharacterSet())
return content != " " + cleaned + " "
}

//filter out ranges where rule is disabled
violationRanges = file.ruleEnabledViolatingRanges(violationRanges, forRule: self)

return violationRanges.flatMap { StyleViolation(
ruleDescription: self.dynamicType.description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location)
)}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public struct ConditionalReturnsOnNewline: ConfigurationProviderRule, Rule, OptI
public let configurationDescription = "N/A"
public var configuration = SeverityConfiguration(.Warning)

public init() { }
public init() {}

public static let description = RuleDescription(
identifier: "conditional_returns_on_newline",
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/LineLengthRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public struct LineLengthRule: ConfigurationProviderRule, SourceKitFreeRule {
)

public func validateFile(file: File) -> [StyleViolation] {
let minValue = configuration.params.map({$0.value}).minElement(<)
let minValue = configuration.params.map({ $0.value }).minElement(<)
return file.lines.flatMap { line in
// `line.content.characters.count` <= `line.range.length` is true.
// So, `check line.range.length` is larger than minimum parameter value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public struct OperatorFunctionWhitespaceRule: ConfigurationProviderRule {
)

public func validateFile(file: File) -> [StyleViolation] {
let operators = ["/", "=", "-", "+", "!", "*", "|", "^", "~", "?", "."].map({"\\\($0)"}) +
let operators = ["/", "=", "-", "+", "!", "*", "|", "^", "~", "?", "."].map({ "\\\($0)" }) +
["%", "<", ">", "&"]
let zeroOrManySpaces = "(\\s{0}|\\s{2,})"
let pattern1 = "func\\s+[" + operators.joinWithSeparator("") +
Expand Down
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */; };
02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */; };
094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094385021D5D4F78009168CF /* PrivateOutletRule.swift */; };
1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */; };
1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */; };
1F11B3CF1C252F23002E8FA8 /* ClosingBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */; };
24B4DF0D1D6DFDE90097803B /* RedundantNilCoalesingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B4DF0B1D6DFA370097803B /* RedundantNilCoalesingRule.swift */; };
Expand Down Expand Up @@ -185,6 +186,7 @@
006ECFC31C44E99E00EF6364 /* LegacyConstantRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyConstantRule.swift; sourceTree = "<group>"; };
02FD8AEE1BFC18D60014BFFB /* ExtendedNSStringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedNSStringTests.swift; sourceTree = "<group>"; };
094385021D5D4F78009168CF /* PrivateOutletRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRule.swift; sourceTree = "<group>"; };
1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureSpacingRule.swift; sourceTree = "<group>"; };
1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceRule.swift; sourceTree = "<group>"; };
1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosingBraceRule.swift; sourceTree = "<group>"; };
24B4DF0B1D6DFA370097803B /* RedundantNilCoalesingRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilCoalesingRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -604,6 +606,7 @@
isa = PBXGroup;
children = (
1F11B3CE1C252F23002E8FA8 /* ClosingBraceRule.swift */,
1E82D5581D7775C7009553D7 /* ClosureSpacingRule.swift */,
E88DEA831B0990F500A66CB0 /* ColonRule.swift */,
695BE9CE1BDFD92B0071E985 /* CommaRule.swift */,
93E0C3CD1D67BD7F007FA25D /* ConditionalReturnsOnNewline.swift */,
Expand Down Expand Up @@ -975,6 +978,7 @@
3BD9CD3D1C37175B009A5D25 /* YamlParser.swift in Sources */,
F22314B01D4FA4D7009AD165 /* LegacyNSGeometryFunctionsRule.swift in Sources */,
E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */,
1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */,
094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */,
E88DEA6B1B0983FE00A66CB0 /* StyleViolation.swift in Sources */,
3BB47D831C514E8100AE6A10 /* RegexConfiguration.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftLintFramework/IntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class IntegrationTests: XCTestCase {
func testSwiftLintLints() {
// This is as close as we're ever going to get to a self-hosting linter.
let swiftFiles = config.lintableFilesForPath("")
XCTAssert(swiftFiles.map({$0.path!}).contains(#file), "current file should be included")
XCTAssert(swiftFiles.map({ $0.path! }).contains(#file), "current file should be included")

let violations = swiftFiles.flatMap {
Linter(file: $0, configuration: config).styleViolations
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFramework/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class RulesTests: XCTestCase {
verifyRule(CommaRule.description)
}

func testClosureSpacingRule() {
verifyRule(ClosureSpacingRule.description)
}

func testConditionalReturnsOnNewline() {
verifyRule(ConditionalReturnsOnNewline.description)
}
Expand Down
10 changes: 10 additions & 0 deletions Tests/SwiftLintFramework/SourceKitCrashTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class SourceKitCrashTests: XCTestCase {
_ = file.syntaxKindsByLines
XCTAssertFalse(assertHandlerCalled,
"Expects assert handler was not called on accessing File.syntaxKindsByLines")

assertHandlerCalled = false
_ = file.syntaxTokensByLines
XCTAssertFalse(assertHandlerCalled,
"Expects assert handler was not called on accessing File.syntaxTokensByLines")
}

func testAssertHandlerIsCalledOnFileThatCrashedSourceKitService() {
Expand All @@ -55,6 +60,11 @@ class SourceKitCrashTests: XCTestCase {
_ = file.syntaxKindsByLines
XCTAssertTrue(assertHandlerCalled,
"Expects assert handler was called on accessing File.syntaxKindsByLines")

assertHandlerCalled = false
_ = file.syntaxTokensByLines
XCTAssertTrue(assertHandlerCalled,
"Expects assert handler was not called on accessing File.syntaxTokensByLines")
}

func testRulesWithFileThatCrashedSourceKitService() {
Expand Down

0 comments on commit c16efa1

Please sign in to comment.