Compare commits

...

22 Commits

Author SHA1 Message Date
Marcelo Fabri 1e72a7bc97 Make tests thread safe 2019-01-04 01:54:55 -08:00
Marcelo Fabri cfc01e7875 Bump platform 2019-01-04 01:54:42 -08:00
Marcelo Fabri 00cf87af6b Add BlueSocket to podspec 2019-01-04 01:34:23 -08:00
Marcelo Fabri 0f816cee95 Use Swift 4.2.1 docker image on Circle CI 2019-01-04 01:33:16 -08:00
Marcelo Fabri 053dd7043b Add tests for RemoteRule 2019-01-04 00:39:08 -08:00
Marcelo Fabri b054c1f5df Remove unused property 2019-01-03 22:06:32 -08:00
Marcelo Fabri 5c46ba8b8e Add test for RemoteRuleResolver 2018-12-30 23:46:32 -08:00
Marcelo Fabri 988ebbb795 Introduce “<plugin> plugin_description” 2018-12-30 02:07:54 -08:00
Marcelo Fabri e470e7abff Add support to use commands (disable/enable) with remote rules 2018-12-30 01:40:07 -08:00
Marcelo Fabri 2cb160c538 Test on 4.2.1 on Linux
To avoid “Value of type 'JSONDecoder' has no member 'keyDecodingStrategy’”
2018-12-30 01:15:01 -08:00
Marcelo Fabri 155cd67997 Support disabling/whitelisting remote rules 2018-12-30 00:58:23 -08:00
Marcelo Fabri be75073b74 Add basic tests for plugin handling in configuration 2018-12-30 00:14:13 -08:00
Marcelo Fabri 3a3918e30c Add a test for RuleDescription’s Codable implementation 2018-12-29 23:50:28 -08:00
Marcelo Fabri 545580eb1f More permissive RuleDescription Codable 2018-12-28 00:53:30 -08:00
Marcelo Fabri fb4e93e36c Update BlueSocket to latest version 2018-12-28 00:31:28 -08:00
Marcelo Fabri 804e8d69a0 Lauch plugin process from SwiftLint when linting 2018-12-28 00:22:02 -08:00
Marcelo Fabri a42a8e940d Only run “plugin rule_description” once per run 2018-12-28 00:08:52 -08:00
Marcelo Fabri 0d1e1cb094 Lint remote rules first 2018-12-27 23:44:14 -08:00
Marcelo Fabri 01b70ee2c9 Use a socket 2018-12-26 23:43:58 -08:00
Marcelo Fabri 1b984b6cf9 Add BlueSocket 2018-12-26 23:42:49 -08:00
Marcelo Fabri 93d4696e4d WIP 2018-12-26 22:55:50 -08:00
Marcelo Fabri 7782098654 WIP 2018-12-26 00:53:01 -08:00
36 changed files with 932 additions and 103 deletions

3
.gitmodules vendored
View File

@ -19,3 +19,6 @@
[submodule "Carthage/Checkouts/Yams"]
path = Carthage/Checkouts/Yams
url = https://github.com/jpsim/Yams.git
[submodule "Carthage/Checkouts/BlueSocket"]
path = Carthage/Checkouts/BlueSocket
url = https://github.com/IBM-Swift/BlueSocket.git

View File

@ -1,3 +1,4 @@
github "Carthage/Commandant" ~> 0.15.0
github "IBM-Swift/BlueSocket" ~> 1.0.0
github "jpsim/Yams" ~> 1.0.1
github "jspahrsummers/xcconfigs" ~> 0.12.0

View File

@ -1,6 +1,7 @@
github "Carthage/Commandant" "0.15.0"
github "antitypical/Result" "4.0.0"
github "drmohundro/SWXMLHash" "4.7.6"
github "IBM-Swift/BlueSocket" "1.0.43"
github "jpsim/SourceKitten" "0.22.0"
github "jpsim/Yams" "1.0.1"
github "jspahrsummers/xcconfigs" "0.12"

1
Carthage/Checkouts/BlueSocket vendored Submodule

@ -0,0 +1 @@
Subproject commit 9c1b578c618f0bfac017aaaaead19de57bb37831

View File

@ -119,7 +119,7 @@ archive:
release: package archive portable_zip
docker_test:
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm norionomura/swift:42 swift test --parallel
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm norionomura/swift:421 swift test --parallel
docker_htop:
docker run -it --rm --pid=container:swiftlint terencewestphal/htop || reset

View File

@ -1,6 +1,15 @@
{
"object": {
"pins": [
{
"package": "Socket",
"repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git",
"state": {
"branch": null,
"revision": "9c1b578c618f0bfac017aaaaead19de57bb37831",
"version": "1.0.43"
}
},
{
"package": "Commandant",
"repositoryURL": "https://github.com/Carthage/Commandant.git",

View File

@ -15,6 +15,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/Carthage/Commandant.git", from: "0.15.0"),
.package(url: "https://github.com/IBM-Swift/BlueSocket.git", from: "1.0.0"),
.package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.22.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "1.0.1"),
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.8.2"),
@ -31,6 +32,7 @@ let package = Package(
.target(
name: "SwiftLintFramework",
dependencies: [
"Socket",
"SourceKittenFramework",
"Yams",
] + (addCryptoSwift ? ["CryptoSwift"] : [])

View File

@ -115,7 +115,9 @@ extension Configuration {
rules: mergingRules(with: configuration),
cachePath: cachePath, // Always use the parent cache path
rootPath: configuration.rootPath,
indentation: configuration.indentation
indentation: configuration.indentation,
plugins: plugins,
remoteRules: remoteRules
)
}
}

View File

@ -13,10 +13,11 @@ extension Configuration {
case whitelistRules = "whitelist_rules"
case indentation = "indentation"
case analyzerRules = "analyzer_rules"
case plugins = "plugins"
}
private static func validKeys(ruleList: RuleList) -> [String] {
return [
private static func validKeys(ruleList: RuleList, remoteRules: [RemoteRule]) -> [String] {
let globalKeys = [
Key.cachePath,
.disabledRules,
.enabledRules,
@ -29,8 +30,12 @@ extension Configuration {
.warningThreshold,
.whitelistRules,
.indentation,
.analyzerRules
].map({ $0.rawValue }) + ruleList.allValidIdentifiers()
.analyzerRules,
.plugins
].map { $0.rawValue }
return globalKeys + ruleList.allValidIdentifiers() +
remoteRules.flatMap { $0.ruleDescription.allIdentifiers }
}
private static func getIndentationLogIfInvalid(from dict: [String: Any]) -> IndentationStyle {
@ -46,23 +51,23 @@ extension Configuration {
return .default
}
public init?(dict: [String: Any], ruleList: RuleList = masterRuleList, enableAllRules: Bool = false,
cachePath: String? = nil) {
func defaultStringArray(_ object: Any?) -> [String] {
return [String].array(of: object) ?? []
}
public init?(dict: [String: Any],
ruleList: RuleList = masterRuleList,
enableAllRules: Bool = false,
cachePath: String? = nil,
remoteRulesResolver: RemoteRuleResolverProtocol = RemoteRuleResolver()) {
// Use either new 'opt_in_rules' or deprecated 'enabled_rules' for now.
let optInRules = defaultStringArray(
dict[Key.optInRules.rawValue] ?? dict[Key.enabledRules.rawValue]
)
// Log an error when supplying invalid keys in the configuration dictionary
let invalidKeys = Set(dict.keys).subtracting(Configuration.validKeys(ruleList: ruleList))
if !invalidKeys.isEmpty {
queuedPrintError("Configuration contains invalid keys:\n\(invalidKeys)")
let plugins = defaultStringArray(dict[Key.plugins.rawValue])
let remoteRules = plugins.compactMap {
try? remoteRulesResolver.remoteRule(forExecutable: $0, configuration: dict)
}
Configuration.validateConfigurationKeys(dict: dict, ruleList: ruleList, remoteRules: remoteRules)
let disabledRules = defaultStringArray(dict[Key.disabledRules.rawValue])
let whitelistRules = defaultStringArray(dict[Key.whitelistRules.rawValue])
let analyzerRules = defaultStringArray(dict[Key.analyzerRules.rawValue])
@ -77,9 +82,7 @@ extension Configuration {
do {
configuredRules = try ruleList.configuredRules(with: dict)
} catch RuleListError.duplicatedConfigurations(let ruleType) {
let aliases = ruleType.description.deprecatedAliases.map { "'\($0)'" }.joined(separator: ", ")
let identifier = ruleType.description.identifier
queuedPrintError("Multiple configurations found for '\(identifier)'. Check for any aliases: \(aliases).")
Configuration.warnAboutDuplicateConfigurations(for: ruleType)
return nil
} catch {
return nil
@ -99,7 +102,9 @@ extension Configuration {
configuredRules: configuredRules,
swiftlintVersion: swiftlintVersion,
cachePath: cachePath ?? dict[Key.cachePath.rawValue] as? String,
indentation: indentation)
indentation: indentation,
plugins: plugins,
remoteRules: remoteRules)
}
private init?(disabledRules: [String],
@ -115,7 +120,9 @@ extension Configuration {
configuredRules: [Rule]?,
swiftlintVersion: String?,
cachePath: String?,
indentation: IndentationStyle) {
indentation: IndentationStyle,
plugins: [String],
remoteRules: [RemoteRule]) {
let rulesMode: RulesMode
if enableAllRules {
rulesMode = .allEnabled
@ -140,7 +147,9 @@ extension Configuration {
configuredRules: configuredRules,
swiftlintVersion: swiftlintVersion,
cachePath: cachePath,
indentation: indentation)
indentation: indentation,
plugins: plugins,
remoteRules: remoteRules)
}
private static func warnAboutDeprecations(configurationDictionary dict: [String: Any],
@ -177,4 +186,23 @@ extension Configuration {
"completely removed in a future release.")
}
}
private static func warnAboutDuplicateConfigurations(for ruleType: Rule.Type) {
let aliases = ruleType.description.deprecatedAliases.map { "'\($0)'" }.joined(separator: ", ")
let identifier = ruleType.description.identifier
queuedPrintError("Multiple configurations found for '\(identifier)'. Check for any aliases: \(aliases).")
}
private static func validateConfigurationKeys(dict: [String: Any], ruleList: RuleList, remoteRules: [RemoteRule]) {
// Log an error when supplying invalid keys in the configuration dictionary
let invalidKeys = Set(dict.keys).subtracting(validKeys(ruleList: ruleList,
remoteRules: remoteRules))
if !invalidKeys.isEmpty {
queuedPrintError("Configuration contains invalid keys:\n\(invalidKeys)")
}
}
}
private func defaultStringArray(_ object: Any?) -> [String] {
return [String].array(of: object) ?? []
}

View File

@ -40,6 +40,15 @@ public func queuedPrintError(_ string: String) {
}
}
/**
A thread-safe, newline-terminated version of fputs(..., stderr).
- parameter error: Error to print.
*/
public func queuedPrintError(_ error: Error) {
queuedPrintError(error.localizedDescription)
}
/**
A thread-safe, newline-terminated version of fatalError that doesn't leak
the source path from the compiled binary.

View File

@ -18,6 +18,7 @@ public struct Configuration: Hashable {
public let excluded: [String] // excluded
public let reporter: String // reporter (xcode, json, csv, checkstyle)
public let warningThreshold: Int? // warning threshold
public let plugins: [String]
public private(set) var rootPath: String? // the root path to search for nested configurations
public private(set) var configurationPath: String? // if successfully loaded from a path
public let cachePath: String?
@ -33,6 +34,7 @@ public struct Configuration: Hashable {
hasher.combine(included)
hasher.combine(excluded)
hasher.combine(reporter)
hasher.combine(plugins)
}
}
@ -40,9 +42,11 @@ public struct Configuration: Hashable {
// MARK: Rules Properties
// All rules enabled in this configuration, derived from disabled, opt-in and whitelist rules
/// All rules enabled in this configuration, derived from disabled, opt-in and whitelist rules
public let rules: [Rule]
internal let remoteRules: [RemoteRule]
internal let rulesMode: RulesMode
// MARK: Initializers
@ -56,7 +60,10 @@ public struct Configuration: Hashable {
configuredRules: [Rule]? = nil,
swiftlintVersion: String? = nil,
cachePath: String? = nil,
indentation: IndentationStyle = .default) {
indentation: IndentationStyle = .default,
plugins: [String] = [],
remoteRulesResolver: RemoteRuleResolverProtocol = RemoteRuleResolver(),
remoteRules: [RemoteRule]? = nil) {
if let pinnedVersion = swiftlintVersion, pinnedVersion != Version.current.value {
queuedPrintError("Currently running SwiftLint \(Version.current.value) but " +
"configuration specified version \(pinnedVersion).")
@ -67,11 +74,16 @@ public struct Configuration: Hashable {
?? (try? ruleList.configuredRules(with: [:]))
?? []
let remoteRules = remoteRules ?? plugins.compactMap {
try? remoteRulesResolver.remoteRule(forExecutable: $0, configuration: nil)
}
let handleAliasWithRuleList: (String) -> String = { ruleList.identifier(for: $0) ?? $0 }
guard let rules = enabledRules(from: configuredRules,
with: rulesMode,
aliasResolver: handleAliasWithRuleList) else {
guard let enabledRules = enabledRules(from: configuredRules,
with: rulesMode,
remoteRules: remoteRules,
aliasResolver: handleAliasWithRuleList) else {
return nil
}
@ -80,9 +92,11 @@ public struct Configuration: Hashable {
excluded: excluded,
warningThreshold: warningThreshold,
reporter: reporter,
rules: rules,
rules: enabledRules.0,
cachePath: cachePath,
indentation: indentation)
indentation: indentation,
plugins: plugins,
remoteRules: enabledRules.1)
}
internal init(rulesMode: RulesMode,
@ -93,7 +107,9 @@ public struct Configuration: Hashable {
rules: [Rule],
cachePath: String?,
rootPath: String? = nil,
indentation: IndentationStyle) {
indentation: IndentationStyle,
plugins: [String],
remoteRules: [RemoteRule]) {
self.rulesMode = rulesMode
self.included = included
self.excluded = excluded
@ -102,6 +118,8 @@ public struct Configuration: Hashable {
self.rules = rules
self.rootPath = rootPath
self.indentation = indentation
self.plugins = plugins
self.remoteRules = remoteRules
// set the config threshold to the threshold provided in the config file
self.warningThreshold = warningThreshold
@ -117,6 +135,8 @@ public struct Configuration: Hashable {
cachePath = configuration.cachePath
rootPath = configuration.rootPath
indentation = configuration.indentation
plugins = configuration.plugins
remoteRules = configuration.remoteRules
}
public init(path: String = Configuration.fileName, rootPath: String? = nil,
@ -176,7 +196,8 @@ public struct Configuration: Hashable {
(lhs.included == rhs.included) &&
(lhs.excluded == rhs.excluded) &&
(lhs.rules == rhs.rules) &&
(lhs.indentation == rhs.indentation)
(lhs.indentation == rhs.indentation) &&
(lhs.plugins == rhs.plugins)
}
}
@ -213,12 +234,13 @@ private func containsDuplicateIdentifiers(_ identifiers: [String]) -> Bool {
private func enabledRules(from configuredRules: [Rule],
with mode: Configuration.RulesMode,
aliasResolver: (String) -> String) -> [Rule]? {
let validRuleIdentifiers = configuredRules.map { type(of: $0).description.identifier }
remoteRules: [RemoteRule],
aliasResolver: (String) -> String) -> ([Rule], [RemoteRule])? {
let validRuleIdentifiers = configuredRules.map { type(of: $0).description.identifier } + remoteRules.identifiers
switch mode {
case .allEnabled:
return configuredRules
return (configuredRules, remoteRules)
case .whitelisted(let whitelistedRuleIdentifiers):
let validWhitelistedRuleIdentifiers = validateRuleIdentifiers(
ruleIdentifiers: whitelistedRuleIdentifiers.map(aliasResolver),
@ -227,9 +249,15 @@ private func enabledRules(from configuredRules: [Rule],
if containsDuplicateIdentifiers(validWhitelistedRuleIdentifiers) {
return nil
}
return configuredRules.filter { rule in
let whitelistedRules = configuredRules.filter { rule in
return validWhitelistedRuleIdentifiers.contains(type(of: rule).description.identifier)
}
let whitelistedRemoteRules = remoteRules.filter { rule in
return validWhitelistedRuleIdentifiers.contains(rule.ruleDescription.identifier)
}
return (whitelistedRules, whitelistedRemoteRules)
case let .default(disabledRuleIdentifiers, optInRuleIdentifiers):
let validDisabledRuleIdentifiers = validateRuleIdentifiers(
ruleIdentifiers: disabledRuleIdentifiers.map(aliasResolver),
@ -242,11 +270,19 @@ private func enabledRules(from configuredRules: [Rule],
|| containsDuplicateIdentifiers(validOptInRuleIdentifiers) {
return nil
}
return configuredRules.filter { rule in
let enabledRules = configuredRules.filter { rule in
let id = type(of: rule).description.identifier
if validDisabledRuleIdentifiers.contains(id) { return false }
return validOptInRuleIdentifiers.contains(id) || !(rule is OptInRule)
}
let enabledRemoteRules = remoteRules.filter { rule in
let id = rule.ruleDescription.identifier
return !validDisabledRuleIdentifiers.contains(id)
}
return (enabledRules, enabledRemoteRules)
}
}

View File

@ -0,0 +1,9 @@
// extracted from https://forums.swift.org/t/pitch-declaring-local-variables-as-lazy/9287/3
internal class Lazy<Result> {
private var computation: () -> Result
private(set) lazy var value: Result = computation()
init(_ computation: @escaping @autoclosure () -> Result) {
self.computation = computation
}
}

View File

@ -7,16 +7,83 @@ private struct LintResult {
let deprecatedToValidIDPairs: [(String, String)]
}
private extension Rule {
static func superfluousDisableCommandViolations(regions: [Region],
superfluousDisableCommandRule: SuperfluousDisableCommandRule?,
allViolations: [StyleViolation]) -> [StyleViolation] {
private enum LintableBox {
case rule(Rule)
case remoteRule(RemoteRule)
private var ruleDescription: RuleDescription {
switch self {
case .rule(let rule):
return type(of: rule).description
case .remoteRule(let remoteRule):
return remoteRule.ruleDescription
}
}
func lint(file: File, regions: [Region], benchmark: Bool,
superfluousDisableCommandRule: SuperfluousDisableCommandRule?,
compilerArguments: [String]) -> LintResult? {
let lintResultBeforeDisableCommand: LintResult?
switch self {
case .rule(let rule):
lintResultBeforeDisableCommand = rule.lint(file: file, regions: regions, benchmark: benchmark,
compilerArguments: compilerArguments)
case .remoteRule(let remoteRule):
lintResultBeforeDisableCommand = remoteRule.lint(file: file, regions: regions, benchmark: benchmark,
compilerArguments: compilerArguments)
}
guard let lintResult = lintResultBeforeDisableCommand else {
return nil
}
let violations = lintResult.violations
let (disabledViolationsAndRegions, enabledViolationsAndRegions) = violations.map { violation in
return (violation, regions.first { $0.contains(violation.location) })
}.partitioned { _, region in
return region?.isRuleEnabled(ruleDescription) ?? true
}
let ruleIDs = ruleDescription.allIdentifiers +
(superfluousDisableCommandRule.map({ type(of: $0) })?.description.allIdentifiers ?? [])
let ruleIdentifiers = Set(ruleIDs.map { RuleIdentifier($0) })
let superfluousDisableCommandViolations = ruleDescription.superfluousDisableCommandViolations(
regions: regions.count > 1 ? file.regions(restrictingRuleIdentifiers: ruleIdentifiers) : regions,
superfluousDisableCommandRule: superfluousDisableCommandRule,
allViolations: violations
)
let enabledViolations: [StyleViolation]
if file.contents.hasPrefix("#!") { // if a violation happens on the same line as a shebang, ignore it
enabledViolations = enabledViolationsAndRegions.compactMap { violation, _ in
if violation.location.line == 1 { return nil }
return violation
}
} else {
enabledViolations = enabledViolationsAndRegions.map { $0.0 }
}
let deprecatedToValidIDPairs = disabledViolationsAndRegions.flatMap { _, region -> [(String, String)] in
let identifiers = region?.deprecatedAliasesDisabling(ruleDescription: ruleDescription) ?? []
return identifiers.map { ($0, ruleDescription.identifier) }
}
return LintResult(violations: enabledViolations + superfluousDisableCommandViolations,
ruleTime: lintResult.ruleTime,
deprecatedToValidIDPairs: deprecatedToValidIDPairs)
}
}
private extension RuleDescription {
func superfluousDisableCommandViolations(regions: [Region],
superfluousDisableCommandRule: SuperfluousDisableCommandRule?,
allViolations: [StyleViolation]) -> [StyleViolation] {
guard !regions.isEmpty, let superfluousDisableCommandRule = superfluousDisableCommandRule else {
return []
}
let regionsDisablingCurrentRule = regions.filter { region in
return region.isRuleDisabled(self.init())
return region.isRuleDisabled(self)
}
let regionsDisablingSuperflousDisableRule = regions.filter { region in
return region.isRuleDisabled(superfluousDisableCommandRule)
@ -43,9 +110,10 @@ private extension Rule {
)
}
}
}
private extension Rule {
func lint(file: File, regions: [Region], benchmark: Bool,
superfluousDisableCommandRule: SuperfluousDisableCommandRule?,
compilerArguments: [String]) -> LintResult? {
if !(self is SourceKitFreeRule) && file.sourcekitdFailed {
return nil
@ -64,38 +132,29 @@ private extension Rule {
ruleTime = nil
}
let (disabledViolationsAndRegions, enabledViolationsAndRegions) = violations.map { violation in
return (violation, regions.first { $0.contains(violation.location) })
}.partitioned { _, region in
return region?.isRuleEnabled(self) ?? true
}
return LintResult(violations: violations, ruleTime: ruleTime,
deprecatedToValidIDPairs: [])
}
}
let ruleIDs = Self.description.allIdentifiers +
(superfluousDisableCommandRule.map({ type(of: $0) })?.description.allIdentifiers ?? [])
let ruleIdentifiers = Set(ruleIDs.map { RuleIdentifier($0) })
private extension RemoteRule {
func lint(file: File, regions: [Region], benchmark: Bool,
compilerArguments: [String]) -> LintResult? {
let ruleID = ruleDescription.identifier
let superfluousDisableCommandViolations = Self.superfluousDisableCommandViolations(
regions: regions.count > 1 ? file.regions(restrictingRuleIdentifiers: ruleIdentifiers) : regions,
superfluousDisableCommandRule: superfluousDisableCommandRule,
allViolations: violations
)
let enabledViolations: [StyleViolation]
if file.contents.hasPrefix("#!") { // if a violation happens on the same line as a shebang, ignore it
enabledViolations = enabledViolationsAndRegions.compactMap { violation, _ in
if violation.location.line == 1 { return nil }
return violation
}
let violations: [StyleViolation]
let ruleTime: (String, Double)?
if benchmark {
let start = Date()
violations = validate(file: file)
ruleTime = (ruleID, -start.timeIntervalSinceNow)
} else {
enabledViolations = enabledViolationsAndRegions.map { $0.0 }
}
let deprecatedToValidIDPairs = disabledViolationsAndRegions.flatMap { _, region -> [(String, String)] in
let identifiers = region?.deprecatedAliasesDisabling(rule: self) ?? []
return identifiers.map { ($0, ruleID) }
violations = validate(file: file)
ruleTime = nil
}
return LintResult(violations: enabledViolations + superfluousDisableCommandViolations, ruleTime: ruleTime,
deprecatedToValidIDPairs: deprecatedToValidIDPairs)
return LintResult(violations: violations, ruleTime: ruleTime,
deprecatedToValidIDPairs: [])
}
}
@ -105,6 +164,7 @@ public struct Linter {
private let cache: LinterCache?
private let configuration: Configuration
private let compilerArguments: [String]
private let remoteRules: [RemoteRule]
public var styleViolations: [StyleViolation] {
return getStyleViolations().0
@ -126,7 +186,9 @@ public struct Linter {
let superfluousDisableCommandRule = rules.first(where: {
$0 is SuperfluousDisableCommandRule
}) as? SuperfluousDisableCommandRule
let validationResults = rules.parallelCompactMap {
let lintables = remoteRules.map(LintableBox.remoteRule) + rules.map(LintableBox.rule)
let validationResults = lintables.parallelCompactMap {
$0.lint(file: self.file, regions: regions, benchmark: benchmark,
superfluousDisableCommandRule: superfluousDisableCommandRule,
compilerArguments: self.compilerArguments)
@ -184,6 +246,7 @@ public struct Linter {
return rule is AnalyzerRule
}
}
self.remoteRules = configuration.remoteRules
}
public func correct() -> [Correction] {

View File

@ -0,0 +1,23 @@
public enum PluginRequiredInput: String, Codable {
case syntaxMap = "syntax_map"
case structure = "structure"
}
public struct PluginDescription: Equatable, Codable {
public let ruleDescription: RuleDescription
public let requiredInformation: Set<PluginRequiredInput>
public init(ruleDescription: RuleDescription,
requiredInformation: Set<PluginRequiredInput> = []) {
self.ruleDescription = ruleDescription
self.requiredInformation = requiredInformation
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.ruleDescription = try container.decode(RuleDescription.self, forKey: .ruleDescription)
self.requiredInformation = try container.decodeIfPresent(Set<PluginRequiredInput>.self,
forKey: .requiredInformation) ?? []
}
}

View File

@ -13,22 +13,34 @@ public struct Region: Equatable {
return start <= location && end >= location
}
public func isRuleEnabled(_ ruleDescription: RuleDescription) -> Bool {
return !isRuleDisabled(ruleDescription)
}
public func isRuleDisabled(_ ruleDescription: RuleDescription) -> Bool {
guard !disabledRuleIdentifiers.contains(.all) else {
return true
}
let identifiersToCheck = ruleDescription.allIdentifiers
let regionIdentifiers = Set(disabledRuleIdentifiers.map { $0.stringRepresentation })
return !regionIdentifiers.isDisjoint(with: identifiersToCheck)
}
public func isRuleEnabled(_ rule: Rule) -> Bool {
return !isRuleDisabled(rule)
}
public func isRuleDisabled(_ rule: Rule) -> Bool {
guard !disabledRuleIdentifiers.contains(.all) else {
return true
}
return isRuleDisabled(type(of: rule).description)
}
let identifiersToCheck = type(of: rule).description.allIdentifiers
let regionIdentifiers = Set(disabledRuleIdentifiers.map { $0.stringRepresentation })
return !regionIdentifiers.isDisjoint(with: identifiersToCheck)
public func deprecatedAliasesDisabling(ruleDescription: RuleDescription) -> Set<String> {
let identifiers = ruleDescription.deprecatedAliases
return Set(disabledRuleIdentifiers.map { $0.stringRepresentation }).intersection(identifiers)
}
public func deprecatedAliasesDisabling(rule: Rule) -> Set<String> {
let identifiers = type(of: rule).description.deprecatedAliases
return Set(disabledRuleIdentifiers.map { $0.stringRepresentation }).intersection(identifiers)
return deprecatedAliasesDisabling(ruleDescription: type(of: rule).description)
}
}

View File

@ -0,0 +1,119 @@
import Dispatch
import Foundation
import Socket
protocol RemoteLintServerDelegate: AnyObject {
func server(_ server: RemoteLintServer, didReceivePayload payload: RemoteRulePayload) -> [StyleViolation]
func serverStartedListening(_ server: RemoteLintServer)
}
final class RemoteLintServer {
private let socketPath: String
private var listenSocket: Socket?
private var continueRunning = true
private var connectedSockets = [Int32: Socket]()
private let socketLockQueue = DispatchQueue(label: "io.realm.swiftlint.remoteLintServer")
weak var delegate: RemoteLintServerDelegate?
init(socketPath: String) {
self.socketPath = socketPath
}
deinit {
for socket in connectedSockets.values {
socket.close()
}
listenSocket?.close()
}
func run() {
let queue = DispatchQueue.global(qos: .userInteractive)
queue.async { [unowned self] in
do {
let socket = try Socket.create(family: .unix, type: .stream, proto: .unix)
socket.readBufferSize = 65_507
self.listenSocket = socket
try socket.listen(on: self.socketPath)
self.delegate?.serverStartedListening(self)
repeat {
let newSocket = try socket.acceptClientConnection()
self.addNewConnection(socket: newSocket)
} while self.continueRunning
} catch {
queuedPrintError(error)
}
}
}
private func addNewConnection(socket: Socket) {
socketLockQueue.sync { [unowned self, socket] in
self.connectedSockets[socket.socketfd] = socket
}
let queue = DispatchQueue.global(qos: .default)
queue.async { [unowned self, socket] in
var shouldKeepRunning = true
var readData = Data()
do {
repeat {
let bytesRead = try socket.read(into: &readData)
if bytesRead > 0 {
guard let json = (try? JSONSerialization.jsonObject(with: readData)) as? [String: Any],
let payload = RemoteRulePayload(json: json) else {
readData.count = 0
break
}
let violations = self.delegate?.server(self, didReceivePayload: payload) ?? []
let data = try JSONSerialization.data(withJSONObject: violations.map { $0.toPluginJSON() })
try socket.write(from: data)
}
if bytesRead == 0 {
shouldKeepRunning = false
break
}
readData.count = 0
} while shouldKeepRunning
socket.close()
self.socketLockQueue.sync { [unowned self, socket] in
self.connectedSockets[socket.socketfd] = nil
}
} catch {
queuedPrintError(error)
}
}
}
func shutdown() {
continueRunning = false
for socket in connectedSockets.values {
socket.close()
}
listenSocket?.close()
}
}
private extension StyleViolation {
func toPluginJSON() -> [String: Any] {
return [
"severity": severity.rawValue,
"location": [
"line": location.line ?? 1,
"character": location.character ?? 1
],
"reason": reason
]
}
}

View File

@ -0,0 +1,79 @@
import Foundation
import Socket
import SourceKittenFramework
public final class RemoteRule {
public let description: PluginDescription
public let configuration: Any?
public var ruleDescription: RuleDescription {
return description.ruleDescription
}
public init(description: PluginDescription, configuration: Any?) {
self.description = description
self.configuration = configuration
}
public func validate(file: File) -> [StyleViolation] {
let payload = RemoteRulePayload(structure: Lazy(file.structure.dictionary),
syntaxMap: Lazy(file.syntaxMap.tokens),
path: file.path,
contents: Lazy(file.contents),
configuration: configuration)
return validate(payload: payload, file: file)
}
private func validate(payload: RemoteRulePayload, file: File) -> [StyleViolation] {
do {
let socket = try Socket.create(family: .unix, type: .stream, proto: .unix)
try socket.connect(to: "/tmp/\(ruleDescription.identifier).socket")
let data = try payload.asJSONData(input: description.requiredInformation)
try socket.write(from: data)
var readData = Data()
_ = try socket.read(into: &readData)
guard let json = try JSONSerialization.jsonObject(with: readData) as? [[String: Any]] else {
return []
}
return json.compactMap { dictionary -> StyleViolation? in
let severity = (dictionary["severity"] as? String).flatMap(ViolationSeverity.init) ?? .warning
guard let location = Location(file: file, json: dictionary) else {
return nil
}
return StyleViolation(ruleDescription: ruleDescription,
severity: severity,
location: location,
reason: dictionary["reason"] as? String)
}
} catch {
queuedPrintError(error)
return []
}
}
}
internal extension Array where Element == RemoteRule {
var identifiers: [String] {
return map { $0.ruleDescription.identifier }
}
}
private extension Location {
init?(file: File, json: [String: Any]) {
if let byteOffset = json["byte_offset"] as? Int {
self = Location(file: file, byteOffset: byteOffset)
} else if let characterOffset = json["character_offset"] as? Int {
self = Location(file: file, characterOffset: characterOffset)
} else if let location = json["location"] as? [String: Int],
let line = location["line"] {
self = Location(file: file.path, line: line, character: location["character"] ?? 1)
} else {
return nil
}
}
}

View File

@ -0,0 +1,81 @@
import Foundation
import SourceKittenFramework
internal struct RemoteRulePayload {
let structure: Lazy<[String: SourceKitRepresentable]>
let syntaxMap: Lazy<[SyntaxToken]>
let path: String?
let contents: Lazy<String?>
let configuration: Any?
func asJSONData(input: Set<PluginRequiredInput>) throws -> Data {
return try JSONSerialization.data(withJSONObject: asJSONDictionary(input: input))
}
func asJSONDictionary(input: Set<PluginRequiredInput>) -> [String: Any] {
var json = [String: Any]()
if input.contains(.structure) {
json["structure"] = structure.value
}
if input.contains(.syntaxMap) {
json["syntax_map"] = syntaxMap.value.map { $0.dictionaryValue }
}
json["path"] = path
json["configuration"] = configuration
if path == nil {
json["contents"] = contents.value
}
return json
}
}
extension RemoteRulePayload {
init?(json: [String: Any]) {
let rawStructure = json["structure"]
let structure = rawStructure.map(convertingIntsToInt64) as? [String: SourceKitRepresentable] ?? [:]
let syntaxMap = (json["syntax_map"] as? [[String: Any]])?.compactMap(SyntaxToken.init(json:)) ?? []
let path = json["path"] as? String
let contents = json["contents"] as? String
let configuration = json["configuration"]
if path == nil && contents == nil {
return nil
}
self.init(structure: Lazy(structure), syntaxMap: Lazy(syntaxMap),
path: path, contents: Lazy(contents), configuration: configuration)
}
}
private func convertingIntsToInt64(value: Any) -> Any {
switch value {
case let value as Int:
return Int64(value)
case let values as [Any]:
return values.map(convertingIntsToInt64)
case let values as [String: Any]:
return values.mapValues(convertingIntsToInt64)
case let value as String:
return value
case let value as Int64:
return value
case let value as Bool:
return value
default:
return value
}
}
private extension SyntaxToken {
init?(json: [String: Any]) {
guard let type = json["type"] as? String,
let offset = json["offset"] as? Int,
let length = json["length"] as? Int else {
return nil
}
self.init(type: type, offset: offset, length: length)
}
}

View File

@ -0,0 +1,31 @@
import Foundation
public protocol RemoteRuleResolverProtocol {
func remoteRule(forExecutable executable: String,
configuration: [String: Any]?) throws -> RemoteRule
}
public final class RemoteRuleResolver: RemoteRuleResolverProtocol {
public init() {}
public func remoteRule(forExecutable executable: String,
configuration: [String: Any]?) throws -> RemoteRule {
let task = Process()
task.launchPath = executable
task.arguments = ["plugin_description"]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let file = pipe.fileHandleForReading
defer { file.closeFile() }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let description = try decoder.decode(PluginDescription.self, from: file.readDataToEndOfFile())
return RemoteRule(description: description,
configuration: configuration?[description.ruleDescription.identifier])
}
}

View File

@ -1,4 +1,4 @@
public struct RuleDescription: Equatable {
public struct RuleDescription: Equatable, Codable {
public let identifier: String
public let name: String
public let description: String
@ -39,4 +39,31 @@ public struct RuleDescription: Equatable {
public static func == (lhs: RuleDescription, rhs: RuleDescription) -> Bool {
return lhs.identifier == rhs.identifier
}
// MARK: Codable
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.identifier = try container.decode(String.self, forKey: .identifier)
self.name = try container.decode(String.self, forKey: .name)
self.description = try container.decode(String.self, forKey: .description)
self.kind = try container.decode(RuleKind.self, forKey: .kind)
self.nonTriggeringExamples = container.optionalDecode([String].self, forKey: .nonTriggeringExamples) ?? []
self.triggeringExamples = container.optionalDecode([String].self, forKey: .triggeringExamples) ?? []
self.corrections = container.optionalDecode([String: String].self, forKey: .corrections) ?? [:]
self.deprecatedAliases = container.optionalDecode(Set<String>.self, forKey: .deprecatedAliases) ?? []
self.minSwiftVersion = container.optionalDecode(SwiftVersion.self, forKey: .minSwiftVersion) ?? .three
self.requiresFileOnDisk = container.optionalDecode(Bool.self, forKey: .requiresFileOnDisk) ?? false
}
}
private extension KeyedDecodingContainerProtocol {
func optionalDecode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<Key>.Key) -> T? where T: Decodable {
do {
return try decodeIfPresent(type, forKey: key)
} catch {
return nil
}
}
}

View File

@ -1,4 +1,4 @@
public enum RuleKind: String {
public enum RuleKind: String, Codable {
case lint
case idiomatic
case style

View File

@ -1,7 +1,7 @@
import Foundation
import SourceKittenFramework
public struct SwiftVersion: RawRepresentable {
public struct SwiftVersion: RawRepresentable, Codable {
public typealias RawValue = String
public let rawValue: String

View File

@ -18,8 +18,8 @@ public struct SuperfluousDisableCommandRule: ConfigurationProviderRule {
return []
}
public func reason(for rule: Rule.Type) -> String {
return "SwiftLint rule '\(rule.description.identifier)' did not trigger a violation " +
public func reason(for ruleDescription: RuleDescription) -> String {
return "SwiftLint rule '\(ruleDescription.identifier)' did not trigger a violation " +
"in the disabled region. Please remove the disable command."
}
}

View File

@ -124,16 +124,6 @@ public struct LineLengthRule: ConfigurationProviderRule {
}
}
// extracted from https://forums.swift.org/t/pitch-declaring-local-variables-as-lazy/9287/3
private class Lazy<Result> {
private var computation: () -> Result
fileprivate private(set) lazy var value: Result = computation()
init(_ computation: @escaping @autoclosure () -> Result) {
self.computation = computation
}
}
private extension String {
var strippingURLs: String {
let range = NSRange(location: 0, length: bridge().length)

View File

@ -163,6 +163,16 @@ extension Configuration {
return LintableFilesVisitor.create(options, cache: cache, block: visitorBlock).flatMap(visitLintableFiles)
}
func startPlugins() -> [Process] {
return plugins.map { executable in
let task = Process()
task.launchPath = executable
task.arguments = ["lint"]
task.launch()
return task
}
}
// MARK: AutoCorrect Command
init(options: AutoCorrectOptions) {

View File

@ -27,6 +27,8 @@ struct LintOrAnalyzeCommand {
let reporter = reporterFrom(optionsReporter: options.reporter, configuration: configuration)
let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration)
let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation")
let pluginsProcesses = configuration.startPlugins()
return configuration.visitLintableFiles(options: options, cache: cache) { linter in
let currentViolations: [StyleViolation]
if options.benchmark {
@ -47,8 +49,7 @@ struct LintOrAnalyzeCommand {
linter.file.invalidateCache()
reporter.report(violations: currentViolations, realtimeCondition: true)
}.flatMap { files in
if isWarningThresholdBroken(configuration: configuration, violations: violations)
&& !options.lenient {
if isWarningThresholdBroken(configuration: configuration, violations: violations) && !options.lenient {
violations.append(createThresholdViolation(threshold: configuration.warningThreshold!))
reporter.report(violations: [violations.last!], realtimeCondition: true)
}
@ -63,6 +64,7 @@ struct LintOrAnalyzeCommand {
ruleBenchmark.save()
}
try? cache?.save()
pluginsProcesses.forEach { $0.terminate() }
return successOrExit(numberOfSeriousViolations: numberOfSeriousViolations,
strictWithViolations: options.strict && !violations.isEmpty)
}

View File

@ -224,6 +224,8 @@
D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */; };
D414D6AC21D0B77F00960935 /* DiscouragedObjectLiteralRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D414D6AB21D0B77F00960935 /* DiscouragedObjectLiteralRuleTests.swift */; };
D414D6AE21D22FF500960935 /* LastWhereRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D414D6AD21D22FF500960935 /* LastWhereRule.swift */; };
D414D6B021D34FA500960935 /* RemoteRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D414D6AF21D34FA500960935 /* RemoteRule.swift */; };
D414D6B221D3509B00960935 /* RemoteRuleResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D414D6B121D3509B00960935 /* RemoteRuleResolver.swift */; };
D41B57781ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */; };
D41E7E0B1DF9DABB0065259A /* RedundantStringEnumValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */; };
D4246D6D1F30D8620097E658 /* PrivateOverFilePrivateRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4246D6C1F30D8620097E658 /* PrivateOverFilePrivateRuleConfiguration.swift */; };
@ -236,6 +238,7 @@
D43B04661E071ED3004016AF /* ColonRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04651E071ED3004016AF /* ColonRuleTests.swift */; };
D43B04691E072291004016AF /* ColonConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04671E07228D004016AF /* ColonConfiguration.swift */; };
D43B046B1E075905004016AF /* ClosureEndIndentationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */; };
D43CD01721D4AFD200944FAC /* Socket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D43CD01621D4AFD200944FAC /* Socket.framework */; };
D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */; };
D44037972132730000FDA77B /* ProhibitedInterfaceBuilderRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44037962132730000FDA77B /* ProhibitedInterfaceBuilderRule.swift */; };
D44254201DB87CA200492EA4 /* ValidIBInspectableRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */; };
@ -256,6 +259,10 @@
D47079AB1DFDCF7A00027086 /* SwiftExpressionKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47079AA1DFDCF7A00027086 /* SwiftExpressionKind.swift */; };
D47079AD1DFE2FA700027086 /* EmptyParametersRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47079AC1DFE2FA700027086 /* EmptyParametersRule.swift */; };
D47079AF1DFE520000027086 /* VoidReturnRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47079AE1DFE520000027086 /* VoidReturnRule.swift */; };
D47820C621D8B3ED00F24836 /* ConfigurationTests+Plugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47820C521D8B3ED00F24836 /* ConfigurationTests+Plugins.swift */; };
D47820C821D8CBC000F24836 /* PluginDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47820C721D8CBC000F24836 /* PluginDescription.swift */; };
D47820CA21D8CD2C00F24836 /* Lazy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47820C921D8CD2C00F24836 /* Lazy.swift */; };
D47820CD21D9FA7100F24836 /* RemoteRuleResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47820CB21D9FA5B00F24836 /* RemoteRuleResolverTests.swift */; };
D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47A510D1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift */; };
D47A51101DB2DD4800A4CC21 /* AttributesRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47A510F1DB2DD4800A4CC21 /* AttributesRule.swift */; };
D47EF4801F69E3100012C4CA /* ColonRule+FunctionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47EF47F1F69E3100012C4CA /* ColonRule+FunctionCall.swift */; };
@ -267,6 +274,9 @@
D48B51231F4F5E4B0068AB98 /* DocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48B51221F4F5E4B0068AB98 /* DocumentationTests.swift */; };
D495B1A221165DAA00E2CD7B /* FileNameRuleFixtures in Resources */ = {isa = PBXBuildFile; fileRef = D495B1A021165DAA00E2CD7B /* FileNameRuleFixtures */; };
D495B1A321165DAA00E2CD7B /* FileHeaderRuleFixtures in Resources */ = {isa = PBXBuildFile; fileRef = D495B1A121165DAA00E2CD7B /* FileHeaderRuleFixtures */; };
D496009221DF32D700520803 /* RemoteLintServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D496009121DF32D700520803 /* RemoteLintServer.swift */; };
D496009421DF376B00520803 /* RemoteRulePayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D496009321DF376B00520803 /* RemoteRulePayload.swift */; };
D496009621DF39DF00520803 /* RemoteRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D496009521DF39DF00520803 /* RemoteRuleTests.swift */; };
D49896F12026B36C00814A83 /* RedundantSetAccessControlRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49896F02026B36C00814A83 /* RedundantSetAccessControlRule.swift */; };
D4998DE71DF191380006E05D /* AttributesRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4998DE61DF191380006E05D /* AttributesRuleTests.swift */; };
D4998DE91DF194F20006E05D /* FileHeaderRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */; };
@ -288,6 +298,7 @@
D4C889711E385B7B00BAE88D /* RedundantDiscardableLetRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */; };
D4CA758F1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CA758E1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift */; };
D4CFC5D2209EC95A00668488 /* FunctionDefaultParameterAtEndRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CFC5D1209EC95A00668488 /* FunctionDefaultParameterAtEndRule.swift */; };
D4D11B1D21D8AE2300FB9D93 /* RuleDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D11B1C21D8AE2300FB9D93 /* RuleDescriptionTests.swift */; };
D4D1B9BB1EAC2C910028BE6A /* AccessControlLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D1B9B91EAC2C870028BE6A /* AccessControlLevel.swift */; };
D4D383852145F550000235BD /* StaticOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D383842145F550000235BD /* StaticOperatorRule.swift */; };
D4D5A5FF1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */; };
@ -678,6 +689,8 @@
D4130D981E16CC1300242361 /* TypeNameRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeNameRuleExamples.swift; sourceTree = "<group>"; };
D414D6AB21D0B77F00960935 /* DiscouragedObjectLiteralRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedObjectLiteralRuleTests.swift; sourceTree = "<group>"; };
D414D6AD21D22FF500960935 /* LastWhereRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastWhereRule.swift; sourceTree = "<group>"; };
D414D6AF21D34FA500960935 /* RemoteRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRule.swift; sourceTree = "<group>"; };
D414D6B121D3509B00960935 /* RemoteRuleResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRuleResolver.swift; sourceTree = "<group>"; };
D41B57771ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessModifierRule.swift; sourceTree = "<group>"; };
D41E7E0A1DF9DABB0065259A /* RedundantStringEnumValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStringEnumValueRule.swift; sourceTree = "<group>"; };
D4246D6C1F30D8620097E658 /* PrivateOverFilePrivateRuleConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOverFilePrivateRuleConfiguration.swift; sourceTree = "<group>"; };
@ -690,6 +703,7 @@
D43B04651E071ED3004016AF /* ColonRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColonRuleTests.swift; sourceTree = "<group>"; };
D43B04671E07228D004016AF /* ColonConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColonConfiguration.swift; sourceTree = "<group>"; };
D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureEndIndentationRule.swift; sourceTree = "<group>"; };
D43CD01621D4AFD200944FAC /* Socket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Socket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitGetterRule.swift; sourceTree = "<group>"; };
D44037962132730000FDA77B /* ProhibitedInterfaceBuilderRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProhibitedInterfaceBuilderRule.swift; sourceTree = "<group>"; };
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidIBInspectableRule.swift; sourceTree = "<group>"; };
@ -710,6 +724,10 @@
D47079AA1DFDCF7A00027086 /* SwiftExpressionKind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftExpressionKind.swift; sourceTree = "<group>"; };
D47079AC1DFE2FA700027086 /* EmptyParametersRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyParametersRule.swift; sourceTree = "<group>"; };
D47079AE1DFE520000027086 /* VoidReturnRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoidReturnRule.swift; sourceTree = "<group>"; };
D47820C521D8B3ED00F24836 /* ConfigurationTests+Plugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationTests+Plugins.swift"; sourceTree = "<group>"; };
D47820C721D8CBC000F24836 /* PluginDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDescription.swift; sourceTree = "<group>"; };
D47820C921D8CD2C00F24836 /* Lazy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lazy.swift; sourceTree = "<group>"; };
D47820CB21D9FA5B00F24836 /* RemoteRuleResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRuleResolverTests.swift; sourceTree = "<group>"; };
D47A510D1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchCaseOnNewlineRule.swift; sourceTree = "<group>"; };
D47A510F1DB2DD4800A4CC21 /* AttributesRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesRule.swift; sourceTree = "<group>"; };
D47EF47F1F69E3100012C4CA /* ColonRule+FunctionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ColonRule+FunctionCall.swift"; sourceTree = "<group>"; };
@ -721,6 +739,9 @@
D48B51221F4F5E4B0068AB98 /* DocumentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentationTests.swift; sourceTree = "<group>"; };
D495B1A021165DAA00E2CD7B /* FileNameRuleFixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = FileNameRuleFixtures; sourceTree = "<group>"; };
D495B1A121165DAA00E2CD7B /* FileHeaderRuleFixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; path = FileHeaderRuleFixtures; sourceTree = "<group>"; };
D496009121DF32D700520803 /* RemoteLintServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteLintServer.swift; sourceTree = "<group>"; };
D496009321DF376B00520803 /* RemoteRulePayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRulePayload.swift; sourceTree = "<group>"; };
D496009521DF39DF00520803 /* RemoteRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRuleTests.swift; sourceTree = "<group>"; };
D49896F02026B36C00814A83 /* RedundantSetAccessControlRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedundantSetAccessControlRule.swift; sourceTree = "<group>"; };
D4998DE61DF191380006E05D /* AttributesRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesRuleTests.swift; sourceTree = "<group>"; };
D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderRuleTests.swift; sourceTree = "<group>"; };
@ -742,6 +763,7 @@
D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantDiscardableLetRule.swift; sourceTree = "<group>"; };
D4CA758E1E2DEEA500A40E8A /* NumberSeparatorRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorRuleTests.swift; sourceTree = "<group>"; };
D4CFC5D1209EC95A00668488 /* FunctionDefaultParameterAtEndRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionDefaultParameterAtEndRule.swift; sourceTree = "<group>"; };
D4D11B1C21D8AE2300FB9D93 /* RuleDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleDescriptionTests.swift; sourceTree = "<group>"; };
D4D1B9B91EAC2C870028BE6A /* AccessControlLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessControlLevel.swift; sourceTree = "<group>"; };
D4D383842145F550000235BD /* StaticOperatorRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticOperatorRule.swift; sourceTree = "<group>"; };
D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShorthandOperatorRule.swift; sourceTree = "<group>"; };
@ -855,6 +877,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D43CD01721D4AFD200944FAC /* Socket.framework in Frameworks */,
E876BFBE1B07828500114ED5 /* SourceKittenFramework.framework in Frameworks */,
E8C0DFCD1AD349DB007EE3D4 /* SWXMLHash.framework in Frameworks */,
3BBF2F9D1C640A0F006CD775 /* SwiftyTextTable.framework in Frameworks */,
@ -1190,6 +1213,7 @@
D0D1211A19E87861005E4BAA /* swiftlint */,
D0D1216E19E87B05005E4BAA /* SwiftLintFramework */,
D0D1217B19E87B05005E4BAA /* SwiftLintFrameworkTests */,
D43CD01521D4AFD200944FAC /* Frameworks */,
);
indentWidth = 4;
sourceTree = "<group>";
@ -1329,6 +1353,7 @@
C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */,
E809EDA21B8A73FB00399043 /* ConfigurationTests.swift */,
F480DC7E1F26090000099465 /* ConfigurationTests+Nested.swift */,
D47820C521D8B3ED00F24836 /* ConfigurationTests+Plugins.swift */,
F480DC821F2609D700099465 /* ConfigurationTests+ProjectMock.swift */,
3BB47D861C51DE6E00AE6A10 /* CustomRulesTests.swift */,
67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */,
@ -1363,11 +1388,14 @@
D4F5851820E99B5A0085C6D8 /* PrivateOutletRuleTests.swift */,
D4246D6E1F30DB260097E658 /* PrivateOverFilePrivateRuleTests.swift */,
E81ADD711ED5ED9D000CD451 /* RegionTests.swift */,
D47820CB21D9FA5B00F24836 /* RemoteRuleResolverTests.swift */,
D496009521DF39DF00520803 /* RemoteRuleTests.swift */,
E86396C61BADAFE6002C9E88 /* ReporterTests.swift */,
B89F3BCB1FD5EDA900931E59 /* RequiredEnumCaseRuleTestCase.swift */,
3B12C9BE1C3209AC000B423F /* Resources */,
3BCC04D31C502BAB006073C3 /* RuleConfigurationTests.swift */,
D45255C71F0932F8003C9B56 /* RuleDescription+Examples.swift */,
D4D11B1C21D8AE2300FB9D93 /* RuleDescriptionTests.swift */,
E8BB8F9B1B17DE3B00199606 /* RulesTests.swift */,
3B12C9C61C3361CB000B423F /* RuleTests.swift */,
6C7045431C6ADA450003F15A /* SourceKitCrashTests.swift */,
@ -1399,6 +1427,14 @@
path = "Supporting Files";
sourceTree = "<group>";
};
D43CD01521D4AFD200944FAC /* Frameworks */ = {
isa = PBXGroup;
children = (
D43CD01621D4AFD200944FAC /* Socket.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
E802ECFE1C56A54600A35AE1 /* Helpers */ = {
isa = PBXGroup;
children = (
@ -1475,10 +1511,12 @@
3BCC04CC1C4F5694006073C3 /* ConfigurationError.swift */,
E8B67C3D1C095E6300FDED8E /* Correction.swift */,
E812249B1B04FADC001783D2 /* Linter.swift */,
D47820C921D8CD2C00F24836 /* Lazy.swift */,
D4FD58B11E12A0200019503C /* LinterCache.swift */,
E88DEA6E1B09843F00A66CB0 /* Location.swift */,
3B12C9C41C322032000B423F /* MasterRuleList.swift */,
E80E018E1B92C1350078EB70 /* Region.swift */,
D47820C721D8CBC000F24836 /* PluginDescription.swift */,
83D71E261B131EB5000395DE /* RuleDescription.swift */,
CC26ED05204DE86E0013BBBC /* RuleIdentifier.swift */,
E8BDE3FE1EDF91B6002EC12F /* RuleList.swift */,
@ -1490,6 +1528,10 @@
D4C27BFD1E12D53F00DF713E /* Version.swift */,
E88DEA701B09847500A66CB0 /* ViolationSeverity.swift */,
3BD9CD3C1C37175B009A5D25 /* YamlParser.swift */,
D414D6AF21D34FA500960935 /* RemoteRule.swift */,
D414D6B121D3509B00960935 /* RemoteRuleResolver.swift */,
D496009121DF32D700520803 /* RemoteLintServer.swift */,
D496009321DF376B00520803 /* RemoteRulePayload.swift */,
);
path = Models;
sourceTree = "<group>";
@ -1831,6 +1873,7 @@
BFF028AE1CBCF8A500B38A9D /* TrailingWhitespaceConfiguration.swift in Sources */,
3B034B6E1E0BE549005D49A9 /* LineLengthConfiguration.swift in Sources */,
B25DCD0C1F7E9FA20028A199 /* MultilineArgumentsRule.swift in Sources */,
D414D6B021D34FA500960935 /* RemoteRule.swift in Sources */,
6258783B1FFC458100AC34F2 /* DiscouragedObjectLiteralRule.swift in Sources */,
62A3E95D209E084000547A86 /* EmptyXCTestMethodRule.swift in Sources */,
D4C4A34C1DEA4FF000E0E04C /* AttributesConfiguration.swift in Sources */,
@ -1862,12 +1905,14 @@
D4DA1DFE1E1A10DB0037413D /* NumberSeparatorConfiguration.swift in Sources */,
E88198601BEA98F000333A11 /* IdentifierNameRule.swift in Sources */,
E88DEA791B098D4400A66CB0 /* RuleParameter.swift in Sources */,
D496009221DF32D700520803 /* RemoteLintServer.swift in Sources */,
8F715B83213B528B00427BD9 /* UnusedImportRule.swift in Sources */,
626D02971F31CBCC0054788D /* XCTFailMessageRule.swift in Sources */,
D4DA1DFA1E18D6200037413D /* LargeTupleRule.swift in Sources */,
D4B022A41E105636007E5297 /* GenericTypeNameRule.swift in Sources */,
E86396CB1BADB519002C9E88 /* CSVReporter.swift in Sources */,
37B3FA8B1DFD45A700AD30D2 /* Dictionary+SwiftLint.swift in Sources */,
D47820CA21D8CD2C00F24836 /* Lazy.swift in Sources */,
823EDC6221020D850070B7CD /* MultilineLiteralBracketsRule.swift in Sources */,
D47EF4801F69E3100012C4CA /* ColonRule+FunctionCall.swift in Sources */,
E88198561BEA94D800333A11 /* FileLengthRule.swift in Sources */,
@ -1907,6 +1952,7 @@
C3EF547821B5A4000009262F /* LegacyHashingRule.swift in Sources */,
31F1B6CC1F60BF4500A57456 /* SwitchCaseAlignmentRule.swift in Sources */,
E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */,
D496009421DF376B00520803 /* RemoteRulePayload.swift in Sources */,
E88198591BEA95F100333A11 /* LeadingWhitespaceRule.swift in Sources */,
D42B45D91F0AF5E30086B683 /* StrictFilePrivateRule.swift in Sources */,
1EC163521D5992D900DD2928 /* VerticalWhitespaceRule.swift in Sources */,
@ -2008,10 +2054,12 @@
CE8178ED1EAC039D0063186E /* UnusedOptionalBindingConfiguration.swift in Sources */,
62DEA1661FB21A9E00BCCCC6 /* PrivateActionRule.swift in Sources */,
D4FD58B21E12A0200019503C /* LinterCache.swift in Sources */,
D47820C821D8CBC000F24836 /* PluginDescription.swift in Sources */,
3BD9CD3D1C37175B009A5D25 /* YamlParser.swift in Sources */,
F22314B01D4FA4D7009AD165 /* LegacyNSGeometryFunctionsRule.swift in Sources */,
E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */,
62426A032118BA6E007E6340 /* ClosureBodyLengthRule.swift in Sources */,
D414D6B221D3509B00960935 /* RemoteRuleResolver.swift in Sources */,
1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */,
E80746F61ECB722F00548D31 /* CacheDescriptionProvider.swift in Sources */,
094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */,
@ -2085,6 +2133,7 @@
D4C27C001E12DFF500DF713E /* LinterCacheTests.swift in Sources */,
D45255C81F0932F8003C9B56 /* RuleDescription+Examples.swift in Sources */,
E81ADD721ED5ED9D000CD451 /* RegionTests.swift in Sources */,
D4D11B1D21D8AE2300FB9D93 /* RuleDescriptionTests.swift in Sources */,
D4998DE91DF194F20006E05D /* FileHeaderRuleTests.swift in Sources */,
750BBD0B214180AF007EC437 /* CollectionAlignmentRuleTests.swift in Sources */,
8B01E50220A4349100C9233E /* FunctionParameterCountRuleTests.swift in Sources */,
@ -2092,6 +2141,7 @@
E81ADD741ED6052F000CD451 /* CommandTests.swift in Sources */,
29FFC37D1F157BDE007E4825 /* FileLengthRuleTests.swift in Sources */,
006204DE1E1E4E0A00FFFBE1 /* VerticalWhitespaceRuleTests.swift in Sources */,
D47820CD21D9FA7100F24836 /* RemoteRuleResolverTests.swift in Sources */,
02FD8AEF1BFC18D60014BFFB /* ExtendedNSStringTests.swift in Sources */,
12E3D4DC2042729300B3E30E /* ExplicitTypeInterfaceRuleTests.swift in Sources */,
D48B51231F4F5E4B0068AB98 /* DocumentationTests.swift in Sources */,
@ -2103,6 +2153,7 @@
6C7045441C6ADA450003F15A /* SourceKitCrashTests.swift in Sources */,
820F451E21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift in Sources */,
D4246D6F1F30DB260097E658 /* PrivateOverFilePrivateRuleTests.swift in Sources */,
D496009621DF39DF00520803 /* RemoteRuleTests.swift in Sources */,
B25DCD101F7EF6DC0028A199 /* MultilineArgumentsRuleTests.swift in Sources */,
3BB47D871C51DE6E00AE6A10 /* CustomRulesTests.swift in Sources */,
E812249A1B04F85B001783D2 /* TestHelpers.swift in Sources */,
@ -2135,6 +2186,7 @@
D4470D5B1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift in Sources */,
787CDE3B208F9C34005F3D2F /* SwitchCaseAlignmentRuleTests.swift in Sources */,
F480DC811F2609AB00099465 /* XCTestCase+BundlePath.swift in Sources */,
D47820C621D8B3ED00F24836 /* ConfigurationTests+Plugins.swift in Sources */,
B89F3BCE1FD5EE0200931E59 /* RequiredEnumCaseRuleTestCase.swift in Sources */,
C9802F2F1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift in Sources */,
3B63D46F1E1F09DF0057BE35 /* LineLengthRuleTests.swift in Sources */,
@ -2186,7 +2238,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0D1212619E878CC005E4BAA /* Debug.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.11;
SWIFT_VERSION = 4.0;
};
name = Debug;
@ -2195,7 +2247,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0D1212819E878CC005E4BAA /* Release.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.11;
SWIFT_VERSION = 4.0;
};
name = Release;
@ -2272,7 +2324,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0D1212719E878CC005E4BAA /* Profile.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.11;
SWIFT_VERSION = 4.0;
};
name = Profile;
@ -2315,7 +2367,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = D0D1212919E878CC005E4BAA /* Test.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.11;
SWIFT_VERSION = 4.0;
};
name = Test;

View File

@ -22,4 +22,7 @@
<FileRef
location = "group:Carthage/Checkouts/Yams/Yams.xcodeproj">
</FileRef>
<FileRef
location = "group:Carthage/Checkouts/BlueSocket/BlueSocket.xcodeproj">
</FileRef>
</Workspace>

View File

@ -6,9 +6,10 @@ Pod::Spec.new do |s|
s.source = { :git => s.homepage + '.git', :tag => s.version }
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'JP Simard' => 'jp@jpsim.com' }
s.platform = :osx, '10.10'
s.platform = :osx, '10.11'
s.source_files = 'Source/SwiftLintFramework/**/*.swift'
s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
s.dependency 'SourceKittenFramework', '~> 0.22'
s.dependency 'Yams', '~> 1.0'
s.dependency 'BlueSocket', '~> 1.0.43'
end

View File

@ -168,6 +168,10 @@ extension ConfigurationTests {
("testIndentationFallback", testIndentationFallback),
("testConfiguresCorrectlyFromDict", testConfiguresCorrectlyFromDict),
("testConfigureFallsBackCorrectly", testConfigureFallsBackCorrectly),
("testLoadsPlugins", testLoadsPlugins),
("testEnableAllRulesConfigurationWithPlugins", testEnableAllRulesConfigurationWithPlugins),
("testWhitelistRulesWithPlugins", testWhitelistRulesWithPlugins),
("testDisabledRulesWithPlugins", testDisabledRulesWithPlugins),
("testMerge", testMerge),
("testLevel0", testLevel0),
("testLevel1", testLevel1),
@ -1002,6 +1006,18 @@ extension RegionTests {
]
}
extension RemoteRuleResolverTests {
static var allTests: [(String, (RemoteRuleResolverTests) -> () throws -> Void)] = [
("testCreatesRemoteRule", testCreatesRemoteRule)
]
}
extension RemoteRuleTests {
static var allTests: [(String, (RemoteRuleTests) -> () throws -> Void)] = [
("testValidate", testValidate)
]
}
extension ReporterTests {
static var allTests: [(String, (ReporterTests) -> () throws -> Void)] = [
("testReporterFromString", testReporterFromString),
@ -1071,6 +1087,12 @@ extension RuleConfigurationTests {
]
}
extension RuleDescriptionTests {
static var allTests: [(String, (RuleDescriptionTests) -> () throws -> Void)] = [
("testCodableWithMissingValues", testCodableWithMissingValues)
]
}
extension RuleTests {
static var allTests: [(String, (RuleTests) -> () throws -> Void)] = [
("testRuleIsEqualTo", testRuleIsEqualTo),
@ -1518,10 +1540,13 @@ XCTMain([
testCase(RedundantTypeAnnotationRuleTests.allTests),
testCase(RedundantVoidReturnRuleTests.allTests),
testCase(RegionTests.allTests),
testCase(RemoteRuleResolverTests.allTests),
testCase(RemoteRuleTests.allTests),
testCase(ReporterTests.allTests),
testCase(RequiredEnumCaseRuleTestCase.allTests),
testCase(ReturnArrowWhitespaceRuleTests.allTests),
testCase(RuleConfigurationTests.allTests),
testCase(RuleDescriptionTests.allTests),
testCase(RuleTests.allTests),
testCase(RulesTests.allTests),
testCase(ShorthandOperatorRuleTests.allTests),

View File

@ -0,0 +1,73 @@
@testable import SwiftLintFramework
import XCTest
extension ConfigurationTests {
func testLoadsPlugins() {
let configurationJSON = ["plugins": ["path/to/plugin"], "test": 10] as [String: Any]
let resolver = ResolverMock()
let configuration = Configuration(dict: configurationJSON, remoteRulesResolver: resolver)!
XCTAssertEqual(configuration.plugins, ["path/to/plugin"])
XCTAssertEqual(configuration.remoteRules.map { $0.ruleDescription.identifier }, ["test"])
XCTAssertEqual(resolver.executable, "path/to/plugin")
XCTAssertEqual(resolver.configuration?.bridge(), configurationJSON.bridge())
}
func testEnableAllRulesConfigurationWithPlugins() {
let configuration = Configuration(dict: ["plugins": ["path/to/plugin", "path/to/mock"]],
ruleList: masterRuleList,
enableAllRules: true, cachePath: nil,
remoteRulesResolver: ResolverMock())!
XCTAssertEqual(configuration.rules.count, masterRuleList.list.count)
XCTAssertEqual(configuration.remoteRules.count, 2)
}
func testWhitelistRulesWithPlugins() {
let config = Configuration(dict: ["whitelist_rules": ["nesting", "test"],
"plugins": ["path/to/plugin",
"path/to/mock"]],
remoteRulesResolver: ResolverMock())!
let configuredIdentifiers = config.rules.map {
type(of: $0).description.identifier
}.sorted()
XCTAssertEqual(["nesting"], configuredIdentifiers)
XCTAssertEqual(["test"], config.remoteRules.map { $0.ruleDescription.identifier })
}
func testDisabledRulesWithPlugins() {
let resolver = ResolverMock()
let disabledConfig = Configuration(dict: ["disabled_rules": ["nesting", "test"],
"plugins": ["path/to/plugin",
"path/to/mock"]],
remoteRulesResolver: resolver)!
XCTAssertEqual(disabledConfig.disabledRules, ["nesting"])
let expectedIdentifiers = Set(masterRuleList.list.keys
.filter({ !(["nesting" ] + optInRules).contains($0) }))
let configuredIdentifiers = Set(disabledConfig.rules.map {
type(of: $0).description.identifier
})
XCTAssertEqual(expectedIdentifiers, configuredIdentifiers)
XCTAssertEqual(disabledConfig.remoteRules.identifiers, ["mock"])
}
}
private class ResolverMock: RemoteRuleResolverProtocol {
private(set) var executable: String?
private(set) var configuration: [String: Any]?
private let identifiers = ["path/to/plugin": "test",
"path/to/mock": "mock"]
func remoteRule(forExecutable executable: String, configuration: [String: Any]?) throws -> RemoteRule {
self.executable = executable
self.configuration = configuration
let identifier = identifiers[executable] ?? ""
let ruleDescription = RuleDescription(identifier: identifier, name: "Test",
description: "", kind: .idiomatic)
let pluginDescription = PluginDescription(ruleDescription: ruleDescription)
return RemoteRule(description: pluginDescription, configuration: nil)
}
}

View File

@ -3,9 +3,9 @@ import SourceKittenFramework
@testable import SwiftLintFramework
import XCTest
private let optInRules = masterRuleList.list.filter({ $0.1.init() is OptInRule }).map({ $0.0 })
let optInRules = masterRuleList.list.filter({ $0.1.init() is OptInRule }).map({ $0.0 })
private extension Configuration {
extension Configuration {
var disabledRules: [String] {
let configuredRuleIDs = rules.map({ type(of: $0).description.identifier })
let defaultRuleIDs = Set(masterRuleList.list.values.filter({
@ -34,6 +34,8 @@ class ConfigurationTests: XCTestCase {
XCTAssertEqual(config.indentation, .spaces(count: 4))
XCTAssertEqual(config.reporter, "xcode")
XCTAssertEqual(reporterFrom(identifier: config.reporter).identifier, "xcode")
XCTAssertEqual(config.plugins, [])
XCTAssertTrue(config.remoteRules.isEmpty)
}
func testInitWithRelativePathAndRootPath() {

View File

@ -0,0 +1,42 @@
import Foundation
import SourceKittenFramework
@testable import SwiftLintFramework
import XCTest
class RemoteRuleResolverTests: XCTestCase {
func testCreatesRemoteRule() throws {
let ruleDescription = RuleDescription(identifier: "test", name: "Test", description: "Test", kind: .lint)
let pluginDescription = PluginDescription(ruleDescription: ruleDescription, requiredInformation: [.structure])
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let jsonString = String(decoding: try encoder.encode(pluginDescription), as: UTF8.self)
let scriptContent = """
#!/bin/bash
echo '\(jsonString)'
"""
let url = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
try scriptContent.data(using: .utf8)!.write(to: url)
try FileManager.default.setAttributes([.posixPermissions: 0o777], ofItemAtPath: url.path)
let ruleConfiguration = [
"k1": "v1",
"k2": 2
] as [String: Any]
let configuration = [
"foo": 20,
"test": ruleConfiguration
] as [String: Any]
let resolver = RemoteRuleResolver()
let remoteRule = try resolver.remoteRule(forExecutable: url.path,
configuration: configuration)
XCTAssertEqual(remoteRule.ruleDescription.identifier, "test")
XCTAssertEqual(remoteRule.description.requiredInformation, [.structure])
XCTAssertEqual(remoteRule.configuration as? NSDictionary, ruleConfiguration.bridge())
}
}

View File

@ -0,0 +1,59 @@
import Foundation
import SourceKittenFramework
@testable import SwiftLintFramework
import XCTest
class RemoteRuleTests: XCTestCase {
func testValidate() {
let ruleDescription = RuleDescription(identifier: "test", name: "Test", description: "Test", kind: .lint)
let pluginDescription = PluginDescription(ruleDescription: ruleDescription, requiredInformation: [.structure])
let configuration = ["key": "value"]
let fileContents = "let x = 10"
let server = RemoteLintServer(socketPath: "/tmp/test.socket")
let dispatchGroup = DispatchGroup()
let delegate = MockRemoteLintServerDelegate()
delegate.dispatchGroup = dispatchGroup
delegate.violationsToReturn = [
StyleViolation(ruleDescription: ruleDescription, severity: .error,
location: Location(file: nil, line: 1, character: 4),
reason: "Test violation")
]
server.delegate = delegate
dispatchGroup.enter()
server.run()
// wait until the server has started
dispatchGroup.wait()
let remoteRule = RemoteRule(description: pluginDescription, configuration: configuration)
let violations = remoteRule.validate(file: File(contents: fileContents))
XCTAssertEqual(violations, delegate.violationsToReturn)
XCTAssertEqual(delegate.payload?.contents.value, fileContents)
XCTAssertNil(delegate.payload!.path)
XCTAssertTrue(delegate.payload!.syntaxMap.value.isEmpty)
XCTAssertFalse(delegate.payload!.structure.value.isEmpty)
XCTAssertEqual(delegate.payload!.configuration as? [String: String], configuration)
server.shutdown()
}
}
private class MockRemoteLintServerDelegate: RemoteLintServerDelegate {
var payload: RemoteRulePayload?
var dispatchGroup: DispatchGroup?
var violationsToReturn: [StyleViolation] = []
func server(_ server: RemoteLintServer, didReceivePayload payload: RemoteRulePayload) -> [StyleViolation] {
self.payload = payload
return violationsToReturn
}
func serverStartedListening(_ server: RemoteLintServer) {
dispatchGroup?.leave()
}
}

View File

@ -0,0 +1,34 @@
import Foundation
import SwiftLintFramework
import XCTest
class RuleDescriptionTests: XCTestCase {
func testCodableWithMissingValues() throws {
let json = [
"identifier": "my_cool_rule",
"name": "Cool Rule",
"description": "Validates stuff",
"kind": "style"
]
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let data = try JSONSerialization.data(withJSONObject: json)
let ruleDescription = try decoder.decode(RuleDescription.self, from: data)
let expectedDescription = RuleDescription(identifier: "my_cool_rule", name: "Cool Rule",
description: "Validates stuff", kind: .style)
// Comparing field by field because RuleDescription's == only checks the identifier
XCTAssertEqual(ruleDescription.identifier, expectedDescription.identifier)
XCTAssertEqual(ruleDescription.name, expectedDescription.name)
XCTAssertEqual(ruleDescription.description, expectedDescription.description)
XCTAssertEqual(ruleDescription.kind, expectedDescription.kind)
XCTAssertEqual(ruleDescription.triggeringExamples, expectedDescription.triggeringExamples)
XCTAssertEqual(ruleDescription.nonTriggeringExamples, expectedDescription.nonTriggeringExamples)
XCTAssertEqual(ruleDescription.corrections, expectedDescription.corrections)
XCTAssertEqual(ruleDescription.deprecatedAliases, expectedDescription.deprecatedAliases)
XCTAssertEqual(ruleDescription.minSwiftVersion, expectedDescription.minSwiftVersion)
XCTAssertEqual(ruleDescription.requiresFileOnDisk, expectedDescription.requiresFileOnDisk)
}
}

View File

@ -59,7 +59,7 @@ jobs:
linux_swift_4.2:
docker:
- image: norionomura/swift:42
- image: norionomura/swift:421
steps:
- checkout
- run: swift test --parallel