From 28e4bd4305ed593c7686350d1e710c692f5b6d03 Mon Sep 17 00:00:00 2001 From: Alex Taffe Date: Wed, 24 Jul 2024 11:26:48 -0400 Subject: [PATCH] Add syntax to swiftlint_version to allow for specifying minimum and maximum versions --- CHANGELOG.md | 7 +- .../SwiftLintCore/Models/Configuration.swift | 105 ++++++++++++++++-- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9ad638b295..f3e29ca0c14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,12 @@ #### Enhancements -* Linting got up to 30% faster due to the praisworthy performance +* Add new syntax to swiftlint_version configuration option to allow for minimum + and maximum versions + [alex-taffe](https://github.com/alex-taffe) + [#5694](https://github.com/realm/SwiftLint/issues/5694) + +* Linting got around 30% faster due to the praisworthy performance improvements done in the [SwiftSyntax](https://github.com/swiftlang/swift-syntax) library. diff --git a/Source/SwiftLintCore/Models/Configuration.swift b/Source/SwiftLintCore/Models/Configuration.swift index 35a86abfe5e..11760ea928a 100644 --- a/Source/SwiftLintCore/Models/Configuration.swift +++ b/Source/SwiftLintCore/Models/Configuration.swift @@ -1,3 +1,6 @@ +// swiftlint:disable file_length +// Everything in this file makes sense to be in this file + import Foundation import SourceKittenFramework @@ -124,7 +127,7 @@ public struct Configuration { /// Creates a `Configuration` by specifying its properties directly, /// except that rules are still to be synthesized from rulesMode, ruleList & allRulesWrapped - /// and a check against the pinnedVersion is performed if given. + /// and a check against the pinnedVersion/minimumVersion is performed if given. /// /// - parameter rulesMode: The `RulesMode` for this configuration. /// - parameter allRulesWrapped: The rules with their own configurations already applied. @@ -138,7 +141,7 @@ public struct Configuration { /// - parameter warningThreshold: The threshold for the number of warnings to tolerate before treating the /// lint as having failed. /// - parameter reporter: The identifier for the `Reporter` to use to report style violations. - /// - parameter cachePath: The location of the persisted cache to use whith this configuration. + /// - parameter cachePath: The location of the persisted cache to use with this configuration. /// - parameter pinnedVersion: The SwiftLint version defined in this configuration. /// - parameter allowZeroLintableFiles: Allow SwiftLint to exit successfully when passed ignored or unlintable /// files. @@ -164,12 +167,8 @@ public struct Configuration { writeBaseline: String? = nil, checkForUpdates: Bool = false ) { - if let pinnedVersion, pinnedVersion != Version.current.value { - queuedPrintError( - "warning: Currently running SwiftLint \(Version.current.value) but " + - "configuration specified version \(pinnedVersion)." - ) - exit(2) + if let pinnedVersion { + Self.compareVersion(to: pinnedVersion) } self.init( @@ -286,6 +285,96 @@ public struct Configuration { $0.bridge().absolutePathRepresentation(rootDirectory: previousBasePath).path(relativeTo: newBasePath) } } + + /// Compares a supplied version to the version SwiftLint is currently running. If the version does not match, + /// or the version syntax is invalid, the method will abort. + /// The syntax for valid version strings is as follows: + /// 0.54.0 + /// >0.54.0 + /// >=0.54.0 + /// <0.54.0 + /// <=0.54.0 + /// >0.54.0 <=0.60.0 + /// + /// - Parameter configurationVersion: The configuration version to compare against + static func compareVersion(to configurationVersion: String) { // swiftlint:disable:this function_body_length + let versions = configurationVersion + .trimmingCharacters(in: .whitespacesAndNewlines) + .components(separatedBy: .whitespaces) + + let invalidVersionString = "error: swiftlint_version syntax invalid. " + + "Please specify a version or version range.\n" + + "0.54.0\n" + + ">0.54.0\n" + + ">=0.54.0\n" + + "<0.54.0\n" + + "<=0.54.0\n" + + ">0.54.0 <=0.60.0\n" + + func showInvalidVersionStringError() -> Never { + queuedFatalError( + invalidVersionString + ) + } + + func compareVersionString(_ versionString: String) { + var versionString = versionString + let comparator: (Version, Version) -> Bool + let errorDifferentiatorString: String + + if versionString.starts(with: ">=") { + comparator = (>=) + versionString = String(versionString.dropFirst(2)) + errorDifferentiatorString = "at least" + } else if versionString.starts(with: ">") { + comparator = (>) + versionString = String(versionString.dropFirst(1)) + errorDifferentiatorString = "greater than" + } else if versionString.starts(with: "<=") { + comparator = (<=) + versionString = String(versionString.dropFirst(2)) + errorDifferentiatorString = "at most" + } else if versionString.starts(with: "<") { + comparator = (<) + versionString = String(versionString.dropFirst(1)) + errorDifferentiatorString = "less than" + } else { + comparator = (==) + errorDifferentiatorString = "exactly" + } + + // make sure the remaining string is just a version string of numeral.numeral.numeral + versionString = versionString.trimmingCharacters(in: .whitespacesAndNewlines) + guard versionString.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil else { + showInvalidVersionStringError() + } + + let configVersion = Version(value: versionString) + + if !comparator(Version.current, configVersion) { + queuedPrintError( + "warning: Currently running SwiftLint \(Version.current.value) but " + + "configuration specified \(errorDifferentiatorString) \(versionString)." + ) + exit(2) + } + } + + if versions.count == 2 { + // Assume we're going for a range + let firstVersion = versions[0] + let secondVersion = versions[1] + compareVersionString(firstVersion) + compareVersionString(secondVersion) + } else if versions.count == 1 { + // Assume we're going for a discrete version or gt/gte/lt/lte scenario + let versionString = versions[0] + compareVersionString(versionString) + } else { + // The user typed in too much + showInvalidVersionStringError() + } + } } // MARK: - Hashable