Skip to content

Commit

Permalink
Add option that allows skipping aligned constants (realm#3391)
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulTaykalo authored Nov 8, 2020
1 parent 67165f7 commit ba58d57
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@
[Christian Mitteldorf](https://github.com/christiankm)
[#3169](https://github.com/realm/SwiftLint/issues/3169)

* Add `skip_aligned_constants` (defaults to `true`) and
`lines_look_around` (defaults to `2`) configuration parameters to the
`operator_usage_whitespace` rule.
[Paul Taykalo](https://github.com/PaulTaykalo)
[#3388](https://github.com/realm/SwiftLint/issues/3388)

#### Bug Fixes

* Fix parsing of Xcode 12 compiler logs for analyzer rules.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
public struct OperatorUsageWhitespaceConfiguration: RuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration(.warning)
private(set) var linesLookAround = 2
private(set) var skipAlignedConstants = true

public var consoleDescription: String {
return severityConfiguration.consoleDescription
+ ", lines_look_around: \(linesLookAround)"
+ ", skip_aligned_constants: \(skipAlignedConstants)"
}

public mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw ConfigurationError.unknownConfiguration
}

linesLookAround = configuration["lines_look_around"] as? Int ?? 2
skipAlignedConstants = configuration["skip_aligned_constants"] as? Bool ?? true

if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SourceKittenFramework

public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, ConfigurationProviderRule,
AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)
public var configuration = OperatorUsageWhitespaceConfiguration()

public init() {}

Expand Down Expand Up @@ -33,7 +33,13 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
Example("let foo = GenericType<(UIViewController) -> Void>()\n"),
Example("let foo = Foo<Bar<T>, Baz>()\n"),
Example("let foo = SignalProducer<Signal<Value, Error>, Error>([ self.signal, next ]).flatten(.concat)\n"),
Example("\"let foo = 1\"")
Example("\"let foo = 1\""),
Example("""
enum Enum {
case hello = 1
case hello2 = 1
}
""")
],
triggeringExamples: [
Example("let foo = 1↓+2\n"),
Expand All @@ -53,7 +59,25 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
Example("let v8 = 1↓ << (6)\n let foo = 1 > 2\n"),
Example("let foo↓ = [1]\n"),
Example("let foo↓ = \"1\"\n"),
Example("let foo↓ = \"1\"\n")
Example("let foo↓ = \"1\"\n"),
Example("""
enum Enum {
case one↓ = 1
case two = 1
}
"""),
Example("""
enum Enum {
case one = 1
case two↓ = 1
}
"""),
Example("""
enum Enum {
case one↓ = 1
case two↓ = 1
}
""")
],
corrections: [
Example("let foo = 1↓+2\n"): Example("let foo = 1 + 2\n"),
Expand All @@ -79,7 +103,7 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
public func validate(file: SwiftLintFile) -> [StyleViolation] {
return violationRanges(file: file).map { range, _ in
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
severity: configuration.severityConfiguration.severity,
location: Location(file: file, characterOffset: range.location))
}
}
Expand All @@ -94,19 +118,18 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
let oneSpace = "[^\\S\\r\\n]" // to allow lines ending with operators to be valid
let zeroSpaces = oneSpace + "{0}"
let manySpaces = oneSpace + "{2,}"
let leadingVariableOrNumber = "(?:\\b|\\)|\\]|\")"
let trailingVariableOrNumber = "(?:\\b|\\(|\\[|\")"
let leadingExpression = "(?:\\b|\\)|\\]|\")"
let trailingExpression = "(?:\\b|\\(|\\[|\")"

let spaces = [(zeroSpaces, zeroSpaces), (oneSpace, manySpaces),
(manySpaces, oneSpace), (manySpaces, manySpaces)]
let patterns = spaces.map { first, second in
leadingVariableOrNumber + first + operators + second + trailingVariableOrNumber
leadingExpression + first + operators + second + trailingExpression
}
let pattern = "(?:\(patterns.joined(separator: "|")))"

let genericPattern = "<(?:\(oneSpace)|\\S)*>" // not using dot to avoid matching new line
let validRangePattern = leadingVariableOrNumber + zeroSpaces + rangePattern +
zeroSpaces + trailingVariableOrNumber
let validRangePattern = leadingExpression + zeroSpaces + rangePattern + zeroSpaces + trailingExpression
let excludingPattern = "(?:\(genericPattern)|\(validRangePattern))"

let excludingKinds = SyntaxKind.commentKinds.union([.objectLiteral])
Expand All @@ -131,8 +154,7 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
return (matchRange, correction)
}

let pattern = spacesPattern + operators + spacesPattern
let operatorsRegex = regex(pattern)
let operatorsRegex = regex(spacesPattern + operators + spacesPattern)

guard let matchRange = operatorsRegex.firstMatch(in: file.contents,
options: [], range: range)?.range else {
Expand All @@ -147,6 +169,10 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
let operatorContent = operatorInRange(file: file, range: matchRange)
let correction = " " + operatorContent + " "

if configuration.skipAlignedConstants && isAlignedConstant(in: matchRange, file: file) {
return nil
}

return (matchRange, correction)
}
}
Expand All @@ -164,6 +190,55 @@ public struct OperatorUsageWhitespaceRule: OptInRule, CorrectableRule, Configura
return file.stringView.substring(with: range).trimmingCharacters(in: .whitespaces)
}

private func isAlignedConstant(in range: NSRange, file: SwiftLintFile) -> Bool {
/// Make sure we have match with assignment operator and with spaces before it
let matchedString = file.stringView.substring(with: range)
let equalityOperatorRegex = regex("\\s+=\\s")

let fullRange = NSRange(location: 0, length: range.length)
guard let match = equalityOperatorRegex.firstMatch(
in: matchedString,
options: [],
range: fullRange),
match.range == fullRange
else {
return false
}

guard let (lineNumber, _) = file.stringView.lineAndCharacter(forCharacterOffset: NSMaxRange(range)),
case let lineIndex = lineNumber - 1, lineIndex >= 0 else {
return false
}

// Find lines above and below with the same location of =
let currentLine = file.stringView.lines[lineIndex].content
let index = currentLine.firstIndex(of: "=")
guard let offset = index.map({ currentLine.distance(from: currentLine.startIndex, to: $0) }) else {
return false
}

// Look around for assignment operator in lines around
let lineIndexesAround = (1...configuration.linesLookAround)
.flatMap { [lineIndex + $0, lineIndex - $0] }

func isValidIndex(_ idx: Int) -> Bool {
return idx != lineIndex && idx >= 0 && idx < file.stringView.lines.count
}

for lineIndex in lineIndexesAround where isValidIndex(lineIndex) {
let line = file.stringView.lines[lineIndex].content
guard !line.isEmpty else { continue }
let index = line.index(line.startIndex,
offsetBy: offset,
limitedBy: line.index(line.endIndex, offsetBy: -1))
if index.map({ line[$0] }) == "=" {
return true
}
}

return false
}

public func correct(file: SwiftLintFile) -> [Correction] {
let violatingRanges = violationRanges(file: file).filter { range, _ in
return file.ruleEnabled(violatingRanges: [range], for: self).isNotEmpty
Expand Down
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
6CCFCF2F1CFEF73E003239EB /* SwiftyTextTable.framework in Embed Frameworks into SwiftLintFramework.framework */ = {isa = PBXBuildFile; fileRef = 3BBF2F9C1C640A0F006CD775 /* SwiftyTextTable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
7250948A1D0859260039B353 /* StatementModeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725094881D0855760039B353 /* StatementModeConfiguration.swift */; };
72EA17B61FD31F10009D5CE6 /* ExplicitACLRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72EA17B51FD31F10009D5CE6 /* ExplicitACLRule.swift */; };
739AC74925423681009CF533 /* OperatorUsageWhitespaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739AC74825423681009CF533 /* OperatorUsageWhitespaceConfiguration.swift */; };
740DF1B1203F62BB0081F694 /* EmptyStringRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740DF1AF203F5AFC0081F694 /* EmptyStringRule.swift */; };
750BBD0B214180AF007EC437 /* CollectionAlignmentRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7578C915214173BE0080FEC9 /* CollectionAlignmentRuleTests.swift */; };
75161FDF213B9D73009DE767 /* CollectionAlignmentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75161FDD213B9D67009DE767 /* CollectionAlignmentConfiguration.swift */; };
Expand Down Expand Up @@ -691,6 +692,7 @@
6C7045431C6ADA450003F15A /* SourceKitCrashTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceKitCrashTests.swift; sourceTree = "<group>"; };
725094881D0855760039B353 /* StatementModeConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementModeConfiguration.swift; sourceTree = "<group>"; };
72EA17B51FD31F10009D5CE6 /* ExplicitACLRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplicitACLRule.swift; sourceTree = "<group>"; };
739AC74825423681009CF533 /* OperatorUsageWhitespaceConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorUsageWhitespaceConfiguration.swift; sourceTree = "<group>"; };
740DF1AF203F5AFC0081F694 /* EmptyStringRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyStringRule.swift; sourceTree = "<group>"; };
75161FDD213B9D67009DE767 /* CollectionAlignmentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionAlignmentConfiguration.swift; sourceTree = "<group>"; };
7551DF6C21382C9A00AA1F4D /* ToggleBoolRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleBoolRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1138,6 +1140,7 @@
D4DA1DFD1E1A10DB0037413D /* NumberSeparatorConfiguration.swift */,
A1A6F3F11EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift */,
6A14DB9123E7292000C17847 /* OpeningBraceConfiguration.swift */,
739AC74825423681009CF533 /* OperatorUsageWhitespaceConfiguration.swift */,
78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */,
C28B2B3B2106DF210009A0FE /* PrefixedConstantRuleConfiguration.swift */,
DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */,
Expand Down Expand Up @@ -2306,6 +2309,7 @@
D4C27BFE1E12D53F00DF713E /* Version.swift in Sources */,
B2902A0E1D6681F700BFCCF7 /* PrivateUnitTestConfiguration.swift in Sources */,
D4DE9133207B4750000FFAA8 /* UnavailableFunctionRule.swift in Sources */,
739AC74925423681009CF533 /* OperatorUsageWhitespaceConfiguration.swift in Sources */,
D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */,
D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */,
D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */,
Expand Down

0 comments on commit ba58d57

Please sign in to comment.