import Foundation import SourceKittenFramework /// A value describing the version of the Swift compiler. public struct SwiftVersion: RawRepresentable, Codable, Comparable { public typealias RawValue = String public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue } public static func < (lhs: SwiftVersion, rhs: SwiftVersion) -> Bool { return lhs.rawValue < rhs.rawValue } } public extension SwiftVersion { /// Swift 3.x - https://swift.org/download/#swift-30 static let three = SwiftVersion(rawValue: "3.0.0") /// Swift 4.0.x - https://swift.org/download/#swift-40 static let four = SwiftVersion(rawValue: "4.0.0") /// Swift 4.1.x - https://swift.org/download/#swift-41 static let fourDotOne = SwiftVersion(rawValue: "4.1.0") /// Swift 4.2.x - https://swift.org/download/#swift-42 static let fourDotTwo = SwiftVersion(rawValue: "4.2.0") /// Swift 5.0.x - https://swift.org/download/#swift-50 static let five = SwiftVersion(rawValue: "5.0.0") /// Swift 5.1.x - https://swift.org/download/#swift-51 static let fiveDotOne = SwiftVersion(rawValue: "5.1.0") /// Swift 5.2.x - https://swift.org/download/#swift-52 static let fiveDotTwo = SwiftVersion(rawValue: "5.2.0") /// Swift 5.3.x - https://swift.org/download/#swift-53 static let fiveDotThree = SwiftVersion(rawValue: "5.3.0") /// The current detected Swift compiler version, based on the currently accessible SourceKit version. /// /// - note: Override by setting the `SWIFTLINT_SWIFT_VERSION` environment variable. static let current: SwiftVersion = { // Allow forcing the Swift version, useful in cases where SourceKit isn't available if let envVersion = ProcessInfo.processInfo.environment["SWIFTLINT_SWIFT_VERSION"] { switch envVersion { case "5": return .five case "4": return .four default: return .three } } if !Request.disableSourceKit { let params: SourceKitObject = ["key.request": UID("source.request.compiler_version")] if let result = try? Request.customRequest(request: params).send(), let major = result.versionMajor, let minor = result.versionMinor, let patch = result.versionPatch { return SwiftVersion(rawValue: "\(major).\(minor).\(patch)") } } if !Request.disableSourceKit, case let dynamicCallableFile = SwiftLintFile(contents: "@dynamicCallable"), dynamicCallableFile.syntaxMap.tokens.compactMap({ $0.kind }) == [.attributeID] { return .five } let file = SwiftLintFile(contents: """ #if swift(>=4.2.0) let version = "4.2.0" #elseif swift(>=4.1.50) let version = "4.1.50" #elseif swift(>=4.1.2) let version = "4.1.2" #elseif swift(>=4.1.1) let version = "4.1.1" #elseif swift(>=4.1.0) let version = "4.1.0" #elseif swift(>=4.0.3) let version = "4.0.3" #elseif swift(>=4.0.2) let version = "4.0.2" #elseif swift(>=4.0.1) let version = "4.0.1" #elseif swift(>=4.0.0) let version = "4.0.0" #elseif swift(>=3.4.0) let version = "3.4.0" #elseif swift(>=3.3.2) let version = "3.3.2" #elseif swift(>=3.3.1) let version = "3.3.1" #elseif swift(>=3.3.0) let version = "3.3.0" #elseif swift(>=3.2.3) let version = "3.2.3" #elseif swift(>=3.2.2) let version = "3.2.2" #elseif swift(>=3.2.1) let version = "3.2.1" #elseif swift(>=3.2.0) let version = "3.2.0" #elseif swift(>=3.1.1) let version = "3.1.1" #elseif swift(>=3.1.0) let version = "3.1.0" #elseif swift(>=3.0.2) let version = "3.0.2" #elseif swift(>=3.0.1) let version = "3.0.1" #elseif swift(>=3.0.0) let version = "3.0.0" #endif """) if !Request.disableSourceKit, let decl = file.structureDictionary.kinds() .first(where: { $0.kind == SwiftDeclarationKind.varGlobal.rawValue }), let token = file.syntaxMap.tokens(inByteRange: decl.byteRange).first(where: { $0.kind == .string }) { let offsetRange = ByteRange(location: token.offset + 1, length: token.length - 2) return .init(rawValue: file.stringView.substringWithByteRange(offsetRange)!) } return .three }() } private extension Dictionary where Key == String { var versionMajor: Int? { return (self["key.version_major"] as? Int64).flatMap({ Int($0) }) } var versionMinor: Int? { return (self["key.version_minor"] as? Int64).flatMap({ Int($0) }) } var versionPatch: Int? { return (self["key.version_patch"] as? Int64).flatMap({ Int($0) }) } }