mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
@@ -103,6 +103,11 @@
|
||||
[Javier Hernández](https://github.com/jaherhi)
|
||||
[#384](https://github.com/realm/SwiftLint/issues/384)
|
||||
|
||||
* Add `shorthand_operator` rule that validates that shorthand operators should
|
||||
be used when possible.
|
||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
||||
[#902](https://github.com/realm/SwiftLint/issues/902)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* Ignore close parentheses on `vertical_parameter_alignment` rule.
|
||||
|
||||
@@ -122,6 +122,7 @@ public let masterRuleList = RuleList(rules:
|
||||
RedundantStringEnumValueRule.self,
|
||||
RedundantVoidReturnRule.self,
|
||||
ReturnArrowWhitespaceRule.self,
|
||||
ShorthandOperatorRule.self,
|
||||
SortedImportsRule.self,
|
||||
StatementPositionRule.self,
|
||||
SwitchCaseOnNewlineRule.self,
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// ShorthandOperatorRule.swift
|
||||
// SwiftLint
|
||||
//
|
||||
// Created by Marcelo Fabri on 01/06/17.
|
||||
// Copyright © 2017 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct ShorthandOperatorRule: ConfigurationProviderRule {
|
||||
|
||||
public var configuration = SeverityConfiguration(.error)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "shorthand_operator",
|
||||
name: "Shorthand Operator",
|
||||
description: "Prefer shorhand operators (+=, -=, *=, /=) over doing the operation and assigning.",
|
||||
nonTriggeringExamples: allOperators.flatMap { operation in
|
||||
[
|
||||
"foo \(operation)= 1",
|
||||
"foo \(operation)= variable",
|
||||
"foo \(operation)= bar.method()"
|
||||
]
|
||||
},
|
||||
triggeringExamples: allOperators.flatMap { operation in
|
||||
[
|
||||
"↓foo = foo \(operation) 1\n",
|
||||
"↓foo = foo \(operation) aVariable\n",
|
||||
"↓foo = foo \(operation) bar.method()\n",
|
||||
"↓foo = 1 \(operation) foo\n",
|
||||
"↓foo = aVariable \(operation) foo\n",
|
||||
"↓foo = bar.method() \(operation) foo\n",
|
||||
"↓foo = bar.method(param: 1, otherParam: 2) \(operation) foo\n"
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
private static let allOperators = ["+", "-", "/", "*"]
|
||||
|
||||
private static let pattern: String = {
|
||||
let escapedOperators = allOperators.map { "\\\($0)" }.joined()
|
||||
let operators = "[\(escapedOperators)]"
|
||||
let operand = "[\\w\\d\\.]+?"
|
||||
let spaces = "[^\\S\\r\\n]*?"
|
||||
let otherOperand = "\(spaces).+?\(spaces)"
|
||||
|
||||
let pattern1 = "\\b(\(operand))\(spaces)=\(spaces)(\\1)\(spaces)\(operators)"
|
||||
let pattern2 = "\\b(\(operand))\(spaces)=\(otherOperand)\(operators)\(spaces)(\\3)"
|
||||
|
||||
return "\(pattern1)|\(pattern2)"
|
||||
}()
|
||||
|
||||
// swiftlint:disable:next force_try
|
||||
private static let regex = try! NSRegularExpression(pattern: pattern, options: [])
|
||||
|
||||
public func validateFile(_ file: File) -> [StyleViolation] {
|
||||
let contents = file.contents.bridge()
|
||||
let range = NSRange(location: 0, length: contents.length)
|
||||
|
||||
let matches = ShorthandOperatorRule.regex.matches(in: file.contents, options: [], range: range)
|
||||
return matches.flatMap { match -> StyleViolation? in
|
||||
|
||||
// byteRanges will have the ranges of captured groups
|
||||
let byteRanges: [NSRange?] = (1..<match.numberOfRanges).map { rangeIdx in
|
||||
let range = match.rangeAt(rangeIdx)
|
||||
guard range.location != NSNotFound else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return contents.NSRangeToByteRange(start: range.location, length: range.length)
|
||||
}
|
||||
|
||||
guard byteRanges[0] != nil || byteRanges[2] != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let kindsInCaptureGroups = byteRanges.map { range in
|
||||
range.flatMap { file.syntaxMap.tokensIn($0).flatMap { SyntaxKind(rawValue: $0.type) } } ?? []
|
||||
}
|
||||
|
||||
let groupIndexes: [Int]
|
||||
if byteRanges[0] != nil {
|
||||
// it's a match from pattern1
|
||||
groupIndexes = [0, 1]
|
||||
} else {
|
||||
// it's a match from pattern2
|
||||
groupIndexes = [2, 3]
|
||||
}
|
||||
|
||||
for idx in groupIndexes where kindsInCaptureGroups[idx] != [.identifier] {
|
||||
return nil
|
||||
}
|
||||
|
||||
return StyleViolation(ruleDescription: type(of: self).description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, characterOffset: match.range.location))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,7 @@
|
||||
D4C4A34C1DEA4FF000E0E04C /* AttributesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A34A1DEA4FD700E0E04C /* AttributesConfiguration.swift */; };
|
||||
D4C4A34E1DEA877200E0E04C /* FileHeaderRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */; };
|
||||
D4C4A3521DEFBBB700E0E04C /* FileHeaderConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */; };
|
||||
D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */; };
|
||||
D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */; };
|
||||
D4DA1DFA1E18D6200037413D /* LargeTupleRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */; };
|
||||
D4DA1DFE1E1A10DB0037413D /* NumberSeparatorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DA1DFD1E1A10DB0037413D /* NumberSeparatorConfiguration.swift */; };
|
||||
@@ -389,6 +390,7 @@
|
||||
D4C4A34A1DEA4FD700E0E04C /* AttributesConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesConfiguration.swift; sourceTree = "<group>"; };
|
||||
D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderRule.swift; sourceTree = "<group>"; };
|
||||
D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderConfiguration.swift; sourceTree = "<group>"; };
|
||||
D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShorthandOperatorRule.swift; sourceTree = "<group>"; };
|
||||
D4DA1DF31E17511D0037413D /* CompilerProtocolInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompilerProtocolInitRule.swift; sourceTree = "<group>"; };
|
||||
D4DA1DF91E18D6200037413D /* LargeTupleRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LargeTupleRule.swift; sourceTree = "<group>"; };
|
||||
D4DA1DFD1E1A10DB0037413D /* NumberSeparatorConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorConfiguration.swift; sourceTree = "<group>"; };
|
||||
@@ -820,6 +822,7 @@
|
||||
D4B022B11E10B613007E5297 /* RedundantVoidReturnRule.swift */,
|
||||
E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */,
|
||||
3BCC04CE1C4F56D3006073C3 /* RuleConfigurations */,
|
||||
D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */,
|
||||
D286EC001E02DA190003CF72 /* SortedImportsRule.swift */,
|
||||
692B60AB1BD8F2E700C7AA22 /* StatementPositionRule.swift */,
|
||||
D47A510D1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift */,
|
||||
@@ -1238,6 +1241,7 @@
|
||||
E832F10B1B17E2F5003F265F /* NSFileManager+SwiftLint.swift in Sources */,
|
||||
E816194C1BFBF35D00946723 /* SwiftDeclarationKind+SwiftLint.swift in Sources */,
|
||||
3BA79C9B1C4767910057E705 /* NSRange+SwiftLint.swift in Sources */,
|
||||
D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */,
|
||||
4DCB8E7F1CBE494E0070FCF0 /* RegexHelpers.swift in Sources */,
|
||||
E86396C21BADAAE5002C9E88 /* Reporter.swift in Sources */,
|
||||
D4B0228E1E0CC608007E5297 /* ClassDelegateProtocolRule.swift in Sources */,
|
||||
|
||||
@@ -217,6 +217,10 @@ class RulesTests: XCTestCase {
|
||||
verifyRule(ReturnArrowWhitespaceRule.description)
|
||||
}
|
||||
|
||||
func testShorthandOperator() {
|
||||
verifyRule(ShorthandOperatorRule.description)
|
||||
}
|
||||
|
||||
func testSortedImports() {
|
||||
verifyRule(SortedImportsRule.description)
|
||||
}
|
||||
@@ -381,6 +385,7 @@ extension RulesTests {
|
||||
("testRedundantStringEnumValue", testRedundantStringEnumValue),
|
||||
("testRedundantVoidReturn", testRedundantVoidReturn),
|
||||
("testReturnArrowWhitespace", testReturnArrowWhitespace),
|
||||
("testShorthandOperator", testShorthandOperator),
|
||||
("testSortedImports", testSortedImports),
|
||||
("testStatementPosition", testStatementPosition),
|
||||
("testStatementPositionUncuddled", testStatementPositionUncuddled),
|
||||
|
||||
Reference in New Issue
Block a user