Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support arrays for included & excluded in custom rules #4006

Merged
merged 2 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
[#3668](https://github.com/realm/SwiftLint/issues/3668)
[#2728](https://github.com/realm/SwiftLint/issues/2728)

* Support arrays for the `included` and `excluded` options when defining
a custom rule.
[Marcelo Fabri](https://github.com/marcelofabri)

#### Bug Fixes

* Ignore array types in `syntactic_sugar` rule if their associated `Index` is
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,10 @@ following syntax:
```yaml
custom_rules:
pirates_beat_ninjas: # rule identifier
included: ".*\\.swift" # regex that defines paths to include during linting. optional.
excluded: ".*Test\\.swift" # regex that defines paths to exclude during linting. optional
included:
- ".*\\.swift" # regex that defines paths to include during linting. optional.
excluded:
- ".*Test\\.swift" # regex that defines paths to exclude during linting. optional
name: "Pirates Beat Ninjas" # rule name. optional.
regex: "([nN]inja)" # matching pattern
capture_group: 0 # number of regex capture group to highlight the rule violation at. optional.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ public struct RegexConfiguration: RuleConfiguration, Hashable, CacheDescriptionP
public var name: String?
public var message = "Regex matched."
public var regex: NSRegularExpression!
public var included: NSRegularExpression?
public var excluded: NSRegularExpression?
public var included: [NSRegularExpression] = []
public var excluded: [NSRegularExpression] = []
public var excludedMatchKinds = Set<SyntaxKind>()
public var severityConfiguration = SeverityConfiguration(.warning)
public var captureGroup: Int = 0
Expand All @@ -26,8 +26,8 @@ public struct RegexConfiguration: RuleConfiguration, Hashable, CacheDescriptionP
name ?? "",
message,
regex.pattern,
included?.pattern ?? "",
excluded?.pattern ?? "",
included.map(\.pattern).joined(separator: ","),
excluded.map(\.pattern).joined(separator: ","),
SyntaxKind.allKinds.subtracting(excludedMatchKinds)
.map({ $0.rawValue }).sorted(by: <).joined(separator: ","),
severityConfiguration.consoleDescription
Expand Down Expand Up @@ -57,11 +57,19 @@ public struct RegexConfiguration: RuleConfiguration, Hashable, CacheDescriptionP
regex = try .cached(pattern: regexString)

if let includedString = configurationDict["included"] as? String {
included = try .cached(pattern: includedString)
included = [try .cached(pattern: includedString)]
} else if let includedArray = configurationDict["included"] as? [String] {
included = try includedArray.map { pattern in
try .cached(pattern: pattern)
}
}

if let excludedString = configurationDict["excluded"] as? String {
excluded = try .cached(pattern: excludedString)
excluded = [try .cached(pattern: excludedString)]
} else if let excludedArray = configurationDict["excluded"] as? [String] {
excluded = try excludedArray.map { pattern in
try .cached(pattern: pattern)
}
}

if let name = configurationDict["name"] as? String {
Expand All @@ -87,6 +95,21 @@ public struct RegexConfiguration: RuleConfiguration, Hashable, CacheDescriptionP
hasher.combine(identifier)
}

func shouldValidate(filePath: String) -> Bool {
let pathRange = filePath.fullNSRange
let isIncluded = included.isEmpty || included.contains { regex in
regex.firstMatch(in: filePath, range: pathRange) != nil
}

guard isIncluded else {
return false
}

return excluded.allSatisfy { regex in
regex.firstMatch(in: filePath, range: pathRange) == nil
}
}

private func excludedMatchKinds(from configurationDict: [String: Any]) throws -> Set<SyntaxKind> {
let matchKinds = [String].array(of: configurationDict["match_kinds"])
let excludedMatchKinds = [String].array(of: configurationDict["excluded_match_kinds"])
Expand Down
15 changes: 1 addition & 14 deletions Source/SwiftLintFramework/Rules/Style/CustomRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,8 @@ public struct CustomRules: Rule, ConfigurationProviderRule, CacheDescriptionProv
}

if let path = file.path {
let pathRange = path.fullNSRange
configurations = configurations.filter { config in
let included: Bool
if let includedRegex = config.included {
included = includedRegex.matches(in: path, options: [], range: pathRange).isNotEmpty
} else {
included = true
}
guard included else {
return false
}
guard let excludedRegex = config.excluded else {
return true
}
return excludedRegex.matches(in: path, options: [], range: pathRange).isEmpty
config.shouldValidate(filePath: path)
}
}

Expand Down
11 changes: 11 additions & 0 deletions Tests/SwiftLintFrameworkTests/CustomRulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ class CustomRulesTests: XCTestCase {
XCTAssertEqual(violations.count, 0)
}

func testCustomRulesExcludedArrayExcludesFile() {
var (regexConfig, customRules) = getCustomRules(["excluded": ["\\.pdf$", "\\.txt$"]])

var customRuleConfiguration = CustomRulesConfiguration()
customRuleConfiguration.customRuleConfigurations = [regexConfig]
customRules.configuration = customRuleConfiguration

let violations = customRules.validate(file: getTestTextFile())
XCTAssertEqual(violations.count, 0)
}
SimplyDanny marked this conversation as resolved.
Show resolved Hide resolved

func testCustomRulesCaptureGroup() {
let (_, customRules) = getCustomRules(["regex": #"\ba\s+(\w+)"#,
"capture_group": 1])
Expand Down
84 changes: 84 additions & 0 deletions Tests/SwiftLintFrameworkTests/RegexConfigurationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@testable import SwiftLintFramework
import XCTest

class RegexConfigurationTests: XCTestCase {
func testShouldValidateIsTrueByDefault() {
let config = RegexConfiguration(identifier: "example")
XCTAssertTrue(config.shouldValidate(filePath: "App/file.swift"))
}

func testShouldValidateWithSingleExluded() throws {
var config = RegexConfiguration(identifier: "example")
try config.apply(configuration: [
"regex": "try!",
"excluded": "Tests/.*\\.swift"
])

XCTAssertFalse(config.shouldValidate(filePath: "Tests/file.swift"))
XCTAssertTrue(config.shouldValidate(filePath: "App/file.swift"))
}

func testShouldValidateWithArrayExluded() throws {
var config = RegexConfiguration(identifier: "example")
try config.apply(configuration: [
"regex": "try!",
"excluded": [
"^Tests/.*\\.swift",
"^MyFramework/Tests/.*\\.swift"
]
])

XCTAssertFalse(config.shouldValidate(filePath: "Tests/file.swift"))
XCTAssertFalse(config.shouldValidate(filePath: "MyFramework/Tests/file.swift"))
XCTAssertTrue(config.shouldValidate(filePath: "App/file.swift"))
}

func testShouldValidateWithSingleIncluded() throws {
var config = RegexConfiguration(identifier: "example")
try config.apply(configuration: [
"regex": "try!",
"included": "App/.*\\.swift"
])

XCTAssertFalse(config.shouldValidate(filePath: "Tests/file.swift"))
XCTAssertFalse(config.shouldValidate(filePath: "MyFramework/Tests/file.swift"))
XCTAssertTrue(config.shouldValidate(filePath: "App/file.swift"))
}

func testShouldValidateWithArrayIncluded() throws {
var config = RegexConfiguration(identifier: "example")
try config.apply(configuration: [
"regex": "try!",
"included": [
"App/.*\\.swift",
"MyFramework/.*\\.swift"
]
])

XCTAssertFalse(config.shouldValidate(filePath: "Tests/file.swift"))
XCTAssertTrue(config.shouldValidate(filePath: "App/file.swift"))
XCTAssertTrue(config.shouldValidate(filePath: "MyFramework/file.swift"))
}

func testShouldValidateWithIncludedAndExcluded() throws {
var config = RegexConfiguration(identifier: "example")
try config.apply(configuration: [
"regex": "try!",
"included": [
"App/.*\\.swift",
"MyFramework/.*\\.swift"
],
"excluded": [
"Tests/.*\\.swift",
"App/Fixtures/.*\\.swift"
]
])

XCTAssertTrue(config.shouldValidate(filePath: "App/file.swift"))
XCTAssertTrue(config.shouldValidate(filePath: "MyFramework/file.swift"))

XCTAssertFalse(config.shouldValidate(filePath: "App/Fixtures/file.swift"))
XCTAssertFalse(config.shouldValidate(filePath: "Tests/file.swift"))
XCTAssertFalse(config.shouldValidate(filePath: "MyFramework/Tests/file.swift"))
}
}