Skip to content

Commit

Permalink
Add syntax to swiftlint_version to allow for specifying minimum and m…
Browse files Browse the repository at this point in the history
…aximum versions
  • Loading branch information
alex-taffe committed Aug 1, 2024
1 parent 75d3a87 commit 88111b9
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 9 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 20% faster due to the praisworthy performance
improvements done in the [SwiftSyntax](https://github.com/swiftlang/swift-syntax)
library.

Expand Down
105 changes: 97 additions & 8 deletions Source/SwiftLintCore/Models/Configuration.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// swiftlint:disable file_length
// Everything in this file makes sense to be in this file

import Foundation
import SourceKittenFramework

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 88111b9

Please sign in to comment.