Interpret strings in `excluded` option of `*_name` rules as regex (#4655)
This commit is contained in:
parent
74dbd52add
commit
5ec6112ba1
|
@ -25,6 +25,11 @@
|
||||||
* Separate analyzer rules as an independent section in the rule directory of the reference.
|
* Separate analyzer rules as an independent section in the rule directory of the reference.
|
||||||
[Ethan Wong](https://github.com/GetToSet)
|
[Ethan Wong](https://github.com/GetToSet)
|
||||||
[#4664](https://github.com/realm/SwiftLint/pull/4664)
|
[#4664](https://github.com/realm/SwiftLint/pull/4664)
|
||||||
|
|
||||||
|
* Interpret strings in `excluded` option of `identifier_name`,
|
||||||
|
`type_name` and `generic_type_name` rules as regex.
|
||||||
|
[Moly](https://github.com/kyounh12)
|
||||||
|
[#4655](https://github.com/realm/SwiftLint/pull/4655)
|
||||||
|
|
||||||
#### Bug Fixes
|
#### Bug Fixes
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents regex from `exclude` option in `NameConfiguration`.
|
||||||
|
/// Using NSRegularExpression causes failure on equality check between two `NameConfiguration`s.
|
||||||
|
/// This class compares pattern only when checking its equality
|
||||||
|
public final class ExcludedRegexExpression: NSObject {
|
||||||
|
/// NSRegularExpression built from given pattern
|
||||||
|
public let regex: NSRegularExpression
|
||||||
|
|
||||||
|
/// Creates an `ExcludedRegexExpression` with a pattern.
|
||||||
|
///
|
||||||
|
/// - parameter pattern: The pattern string to build regex
|
||||||
|
init?(pattern: String) {
|
||||||
|
guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }
|
||||||
|
self.regex = regex
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Equality Check
|
||||||
|
|
||||||
|
/// Compares regex pattern to check equality
|
||||||
|
override public func isEqual(_ object: Any?) -> Bool {
|
||||||
|
if let object = object as? ExcludedRegexExpression {
|
||||||
|
return regex.pattern == object.regex.pattern
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uses regex pattern as hash
|
||||||
|
override public var hash: Int {
|
||||||
|
return regex.pattern.hashValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,9 +66,7 @@ private extension GenericTypeNameRule {
|
||||||
|
|
||||||
override func visitPost(_ node: GenericParameterSyntax) {
|
override func visitPost(_ node: GenericParameterSyntax) {
|
||||||
let name = node.name.text
|
let name = node.name.text
|
||||||
guard !configuration.excluded.contains(name) else {
|
guard !configuration.shouldExclude(name: name) else { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let allowedSymbols = configuration.allowedSymbols.union(.alphanumerics)
|
let allowedSymbols = configuration.allowedSymbols.union(.alphanumerics)
|
||||||
if !allowedSymbols.isSuperset(of: CharacterSet(charactersIn: name)) {
|
if !allowedSymbols.isSuperset(of: CharacterSet(charactersIn: name)) {
|
||||||
|
|
|
@ -88,9 +88,7 @@ private extension TypeNameRule {
|
||||||
let originalName = identifier.text
|
let originalName = identifier.text
|
||||||
let nameConfiguration = configuration.nameConfiguration
|
let nameConfiguration = configuration.nameConfiguration
|
||||||
|
|
||||||
guard !nameConfiguration.excluded.contains(originalName) else {
|
guard !nameConfiguration.shouldExclude(name: originalName) else { return nil }
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = originalName
|
let name = originalName
|
||||||
.strippingBackticks()
|
.strippingBackticks()
|
||||||
|
|
|
@ -4,14 +4,14 @@ struct NameConfiguration: RuleConfiguration, Equatable {
|
||||||
var consoleDescription: String {
|
var consoleDescription: String {
|
||||||
return "(min_length) \(minLength.shortConsoleDescription), " +
|
return "(min_length) \(minLength.shortConsoleDescription), " +
|
||||||
"(max_length) \(maxLength.shortConsoleDescription), " +
|
"(max_length) \(maxLength.shortConsoleDescription), " +
|
||||||
"excluded: \(excluded.sorted()), " +
|
"excluded: \(excludedRegularExpressions.map { $0.regex.pattern }.sorted()), " +
|
||||||
"allowed_symbols: \(allowedSymbolsSet.sorted()), " +
|
"allowed_symbols: \(allowedSymbolsSet.sorted()), " +
|
||||||
"validates_start_with_lowercase: \(validatesStartWithLowercase)"
|
"validates_start_with_lowercase: \(validatesStartWithLowercase)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var minLength: SeverityLevelsConfiguration
|
var minLength: SeverityLevelsConfiguration
|
||||||
var maxLength: SeverityLevelsConfiguration
|
var maxLength: SeverityLevelsConfiguration
|
||||||
var excluded: Set<String>
|
var excludedRegularExpressions: Set<ExcludedRegexExpression>
|
||||||
private var allowedSymbolsSet: Set<String>
|
private var allowedSymbolsSet: Set<String>
|
||||||
var validatesStartWithLowercase: Bool
|
var validatesStartWithLowercase: Bool
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ struct NameConfiguration: RuleConfiguration, Equatable {
|
||||||
validatesStartWithLowercase: Bool = true) {
|
validatesStartWithLowercase: Bool = true) {
|
||||||
minLength = SeverityLevelsConfiguration(warning: minLengthWarning, error: minLengthError)
|
minLength = SeverityLevelsConfiguration(warning: minLengthWarning, error: minLengthError)
|
||||||
maxLength = SeverityLevelsConfiguration(warning: maxLengthWarning, error: maxLengthError)
|
maxLength = SeverityLevelsConfiguration(warning: maxLengthWarning, error: maxLengthError)
|
||||||
self.excluded = Set(excluded)
|
self.excludedRegularExpressions = Set(excluded.compactMap { ExcludedRegexExpression(pattern: "^\($0)$") })
|
||||||
self.allowedSymbolsSet = Set(allowedSymbols)
|
self.allowedSymbolsSet = Set(allowedSymbols)
|
||||||
self.validatesStartWithLowercase = validatesStartWithLowercase
|
self.validatesStartWithLowercase = validatesStartWithLowercase
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ struct NameConfiguration: RuleConfiguration, Equatable {
|
||||||
try maxLength.apply(configuration: maxLengthConfiguration)
|
try maxLength.apply(configuration: maxLengthConfiguration)
|
||||||
}
|
}
|
||||||
if let excluded = [String].array(of: configurationDict["excluded"]) {
|
if let excluded = [String].array(of: configurationDict["excluded"]) {
|
||||||
self.excluded = Set(excluded)
|
self.excludedRegularExpressions = Set(excluded.compactMap { ExcludedRegexExpression(pattern: "^\($0)$") })
|
||||||
}
|
}
|
||||||
if let allowedSymbols = [String].array(of: configurationDict["allowed_symbols"]) {
|
if let allowedSymbols = [String].array(of: configurationDict["allowed_symbols"]) {
|
||||||
self.allowedSymbolsSet = Set(allowedSymbols)
|
self.allowedSymbolsSet = Set(allowedSymbols)
|
||||||
|
@ -90,3 +90,13 @@ extension NameConfiguration {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - `exclude` option extensions
|
||||||
|
|
||||||
|
extension NameConfiguration {
|
||||||
|
func shouldExclude(name: String) -> Bool {
|
||||||
|
return excludedRegularExpressions.contains(where: {
|
||||||
|
return !$0.regex.matches(in: name, options: [], range: NSRange(name.startIndex..., in: name)).isEmpty
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,9 +34,9 @@ struct IdentifierNameRule: ASTRule, ConfigurationProviderRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateName(dictionary: dictionary, kind: kind).map { name, offset in
|
return validateName(dictionary: dictionary, kind: kind).map { name, offset in
|
||||||
guard !configuration.excluded.contains(name), let firstCharacter = name.first else {
|
guard let firstCharacter = name.first else { return [] }
|
||||||
return []
|
|
||||||
}
|
guard !configuration.shouldExclude(name: name) else { return [] }
|
||||||
|
|
||||||
let isFunction = SwiftDeclarationKind.functionKinds.contains(kind)
|
let isFunction = SwiftDeclarationKind.functionKinds.contains(kind)
|
||||||
let description = Self.description
|
let description = Self.description
|
||||||
|
|
|
@ -2,6 +2,22 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class GenericTypeNameRuleTests: XCTestCase {
|
class GenericTypeNameRuleTests: XCTestCase {
|
||||||
|
func testGenericTypeNameWithExcluded() {
|
||||||
|
let baseDescription = GenericTypeNameRule.description
|
||||||
|
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
||||||
|
Example("func foo<apple> {}\n"),
|
||||||
|
Example("func foo<some_apple> {}\n"),
|
||||||
|
Example("func foo<test123> {}\n")
|
||||||
|
]
|
||||||
|
let triggeringExamples = baseDescription.triggeringExamples + [
|
||||||
|
Example("func foo<ap_ple> {}\n"),
|
||||||
|
Example("func foo<appleJuice> {}\n")
|
||||||
|
]
|
||||||
|
let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples,
|
||||||
|
triggeringExamples: triggeringExamples)
|
||||||
|
verifyRule(description, ruleConfiguration: ["excluded": ["apple", "some.*", ".*st\\d+.*"]])
|
||||||
|
}
|
||||||
|
|
||||||
func testGenericTypeNameWithAllowedSymbols() {
|
func testGenericTypeNameWithAllowedSymbols() {
|
||||||
let baseDescription = GenericTypeNameRule.description
|
let baseDescription = GenericTypeNameRule.description
|
||||||
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
||||||
|
|
|
@ -2,6 +2,22 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class IdentifierNameRuleTests: XCTestCase {
|
class IdentifierNameRuleTests: XCTestCase {
|
||||||
|
func testIdentifierNameWithExcluded() {
|
||||||
|
let baseDescription = IdentifierNameRule.description
|
||||||
|
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
||||||
|
Example("let Apple = 0"),
|
||||||
|
Example("let some_apple = 0"),
|
||||||
|
Example("let Test123 = 0")
|
||||||
|
]
|
||||||
|
let triggeringExamples = baseDescription.triggeringExamples + [
|
||||||
|
Example("let ap_ple = 0"),
|
||||||
|
Example("let AppleJuice = 0")
|
||||||
|
]
|
||||||
|
let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples,
|
||||||
|
triggeringExamples: triggeringExamples)
|
||||||
|
verifyRule(description, ruleConfiguration: ["excluded": ["Apple", "some.*", ".*\\d+.*"]])
|
||||||
|
}
|
||||||
|
|
||||||
func testIdentifierNameWithAllowedSymbols() {
|
func testIdentifierNameWithAllowedSymbols() {
|
||||||
let baseDescription = IdentifierNameRule.description
|
let baseDescription = IdentifierNameRule.description
|
||||||
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
||||||
|
|
|
@ -2,6 +2,22 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class TypeNameRuleTests: XCTestCase {
|
class TypeNameRuleTests: XCTestCase {
|
||||||
|
func testTypeNameWithExcluded() {
|
||||||
|
let baseDescription = TypeNameRule.description
|
||||||
|
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
||||||
|
Example("class apple {}"),
|
||||||
|
Example("struct some_apple {}"),
|
||||||
|
Example("protocol test123 {}")
|
||||||
|
]
|
||||||
|
let triggeringExamples = baseDescription.triggeringExamples + [
|
||||||
|
Example("enum ap_ple {}"),
|
||||||
|
Example("typealias appleJuice = Void")
|
||||||
|
]
|
||||||
|
let description = baseDescription.with(nonTriggeringExamples: nonTriggeringExamples,
|
||||||
|
triggeringExamples: triggeringExamples)
|
||||||
|
verifyRule(description, ruleConfiguration: ["excluded": ["apple", "some.*", ".*st\\d+.*"]])
|
||||||
|
}
|
||||||
|
|
||||||
func testTypeNameWithAllowedSymbols() {
|
func testTypeNameWithAllowedSymbols() {
|
||||||
let baseDescription = TypeNameRule.description
|
let baseDescription = TypeNameRule.description
|
||||||
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
let nonTriggeringExamples = baseDescription.nonTriggeringExamples + [
|
||||||
|
|
Loading…
Reference in New Issue