Files
SwiftLint/Source/SwiftLintFramework/Models/Configuration.swift
T
Norio Nomura 40828dff03 Merge branch 'master' into swift3.0
* master: (41 commits)
  Fix formatting in CHANGELOG.md
  release 0.13.0
  Update CHANGELOG.md
  Fix check for trailing whitespace to return early
  Fix checks for some inline comments
  Replace check for comments to use SyntaxKind
  Add configuration for trailing_whitespace to ignore comments
  Unwanted space removed
  - Lint issues fixed
  Updated HTML Reporter
  PR feedback
  Add check on autocorrect for disabled range
  Use `utf8.count` instead of `utf16.count` to byte range
  Re-write `ExplicitInitRule` to `ASTRule`
  added ExplicitInitRule
  Updated CHANGELOG
  HTML Reporter added
  HTML Reporter added
  Adds information about SwiftLint plugin for AppCode into README.md
  added reasons why a new rule should be opt in
  ...

# Conflicts:
#	Source/SwiftLintFramework/Extensions/File+SwiftLint.swift
#	Source/SwiftLintFramework/Extensions/Structure+SwiftLint.swift
#	Source/SwiftLintFramework/Rules/ColonRule.swift
#	Source/SwiftLintFramework/Rules/CommaRule.swift
#	Source/SwiftLintFramework/Rules/LegacyCGGeometryFunctionsRule.swift
#	Source/SwiftLintFramework/Rules/LegacyConstantRule.swift
#	Source/SwiftLintFramework/Rules/LegacyConstructorRule.swift
#	Source/SwiftLintFramework/Rules/LegacyNSGeometryFunctionsRule.swift
#	Source/SwiftLintFramework/Rules/LineLengthRule.swift
#	Source/SwiftLintFramework/Rules/OperatorFunctionWhitespaceRule.swift
#	Source/SwiftLintFramework/Rules/ReturnArrowWhitespaceRule.swift
#	Source/SwiftLintFramework/Rules/RuleConfigurations/StatementPositionConfiguration.swift
#	Source/SwiftLintFramework/Rules/StatementPositionRule.swift
#	Source/SwiftLintFramework/Rules/TrailingWhitespaceRule.swift
#	Tests/SwiftLintFramework/RuleConfigurationTests.swift
2016-11-04 21:40:56 +09:00

254 lines
10 KiB
Swift

//
// Configuration.swift
// SwiftLint
//
// Created by JP Simard on 2015-08-23.
// Copyright (c) 2015 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
private let fileManager = FileManager.default
private enum ConfigurationKey: String {
case DisabledRules = "disabled_rules"
case EnabledRules = "enabled_rules" // deprecated in favor of OptInRules
case Excluded = "excluded"
case Included = "included"
case OptInRules = "opt_in_rules"
case Reporter = "reporter"
case UseNestedConfigs = "use_nested_configs" // deprecated
case WhitelistRules = "whitelist_rules"
case WarningThreshold = "warning_threshold"
}
public struct Configuration: Equatable {
public static let fileName = ".swiftlint.yml"
public let included: [String] // included
public let excluded: [String] // excluded
public let reporter: String // reporter (xcode, json, csv, checkstyle)
public var warningThreshold: Int? // warning threshold
public let rules: [Rule]
public var rootPath: String? // the root path to search for nested configurations
public var configurationPath: String? // if successfully loaded from a path
public init?(disabledRules: [String] = [],
optInRules: [String] = [],
whitelistRules: [String] = [],
included: [String] = [],
excluded: [String] = [],
warningThreshold: Int? = nil,
reporter: String = XcodeReporter.identifier,
configuredRules: [Rule] = masterRuleList.configuredRules(with: [:])) {
self.included = included
self.excluded = excluded
self.reporter = reporter
// Validate that all rule identifiers map to a defined rule
let validRuleIdentifiers = configuredRules.map {
type(of: $0).description.identifier
}
let validDisabledRules = disabledRules.filter({ validRuleIdentifiers.contains($0) })
let invalidRules = disabledRules.filter({ !validRuleIdentifiers.contains($0) })
if !invalidRules.isEmpty {
for invalidRule in invalidRules {
queuedPrintError(
"configuration error: '\(invalidRule)' is not a valid rule identifier"
)
}
let listOfValidRuleIdentifiers = validRuleIdentifiers.joined(separator: "\n")
queuedPrintError("Valid rule identifiers:\n\(listOfValidRuleIdentifiers)")
}
// Validate that rule identifiers aren't listed multiple times
if Set(validDisabledRules).count != validDisabledRules.count {
let duplicateRules = validDisabledRules.reduce([String: Int]()) { accu, element in
var accu = accu
accu[element] = (accu[element] ?? 0) + 1
return accu
}.filter { $0.1 > 1 }
queuedPrintError(duplicateRules.map { rule in
"configuration error: '\(rule.0)' is listed \(rule.1) times"
}.joined(separator: "\n"))
return nil
}
// set the config threshold to the threshold provided in the config file
self.warningThreshold = warningThreshold
// white_list rules take precendence over all else.
if !whitelistRules.isEmpty {
if !disabledRules.isEmpty || !optInRules.isEmpty {
queuedPrintError("'\(ConfigurationKey.DisabledRules.rawValue)' or " +
"'\(ConfigurationKey.OptInRules.rawValue)' cannot be used in combination " +
"with '\(ConfigurationKey.WhitelistRules.rawValue)'")
return nil
}
rules = configuredRules.filter { rule in
return whitelistRules.contains(type(of: rule).description.identifier)
}
} else {
rules = configuredRules.filter { rule in
let id = type(of: rule).description.identifier
if validDisabledRules.contains(id) { return false }
return optInRules.contains(id) || !(rule is OptInRule)
}
}
}
public init?(dict: [String: Any]) {
// Deprecation warning for "enabled_rules"
if dict[ConfigurationKey.EnabledRules.rawValue] != nil {
queuedPrintError("'\(ConfigurationKey.EnabledRules.rawValue)' has been renamed to " +
"'\(ConfigurationKey.OptInRules.rawValue)' and will be completely removed in a " +
"future release.")
}
// Deprecation warning for "use_nested_configs"
if dict[ConfigurationKey.UseNestedConfigs.rawValue] != nil {
queuedPrintError("Support for '\(ConfigurationKey.UseNestedConfigs.rawValue)' has " +
"been deprecated and its value is now ignored. Nested configuration files are " +
"now always considered.")
}
func defaultStringArray(_ object: Any?) -> [String] {
return [String].arrayOf(object) ?? []
}
// Use either new 'opt_in_rules' or deprecated 'enabled_rules' for now.
let optInRules = defaultStringArray(
dict[ConfigurationKey.OptInRules.rawValue] ??
dict[ConfigurationKey.EnabledRules.rawValue]
)
// Log an error when supplying invalid keys in the configuration dictionary
let validKeys = [
ConfigurationKey.DisabledRules,
.EnabledRules,
.Excluded,
.Included,
.OptInRules,
.Reporter,
.UseNestedConfigs,
.WarningThreshold,
.WhitelistRules
].map({ $0.rawValue }) + masterRuleList.list.keys
let invalidKeys = Set(dict.keys).subtracting(validKeys)
if !invalidKeys.isEmpty {
queuedPrintError("Configuration contains invalid keys:\n\(invalidKeys)")
}
self.init(
disabledRules: defaultStringArray(dict[ConfigurationKey.DisabledRules.rawValue]),
optInRules: optInRules,
whitelistRules: defaultStringArray(dict[ConfigurationKey.WhitelistRules.rawValue]),
included: defaultStringArray(dict[ConfigurationKey.Included.rawValue]),
excluded: defaultStringArray(dict[ConfigurationKey.Excluded.rawValue]),
warningThreshold: dict[ConfigurationKey.WarningThreshold.rawValue] as? Int,
reporter: dict[ConfigurationKey.Reporter.rawValue] as? String ??
XcodeReporter.identifier,
configuredRules: masterRuleList.configuredRules(with: dict)
)
}
public init(path: String = Configuration.fileName, rootPath: String? = nil,
optional: Bool = true, quiet: Bool = false) {
let fullPath = (path as NSString).absolutePathRepresentation()
let fail = { (msg: String) in
fatalError("Could not read configuration file at path '\(fullPath)': \(msg)")
}
if path.isEmpty || !FileManager.default.fileExists(atPath: fullPath) {
if !optional { fail("File not found.") }
self.init()!
self.rootPath = rootPath
return
}
do {
let yamlContents = try NSString(contentsOfFile: fullPath,
encoding: String.Encoding.utf8.rawValue) as String
let dict = try YamlParser.parse(yamlContents)
if !quiet {
queuedPrintError("Loading configuration from '\(path)'")
}
self.init(dict: dict)!
configurationPath = fullPath
self.rootPath = rootPath
return
} catch YamlParserError.yamlParsing(let message) {
fail("Error parsing YAML: \(message)")
} catch {
fail("\(error)")
}
self.init()!
}
public func lintablePathsForPath(_ path: String,
fileManager: FileManager = fileManager) -> [String] {
let pathsForPath = included.isEmpty ? fileManager.filesToLintAtPath(path) : []
let excludedPaths = excluded.flatMap {
fileManager.filesToLintAtPath($0, rootDirectory: self.rootPath)
}
let includedPaths = included.flatMap {
fileManager.filesToLintAtPath($0, rootDirectory: self.rootPath)
}
return (pathsForPath + includedPaths).filter({ !excludedPaths.contains($0) })
}
public func lintableFilesForPath(_ path: String) -> [File] {
return lintablePathsForPath(path).flatMap { File(path: $0) }
}
public func configurationForFile(_ file: File) -> Configuration {
if let containingDir = (file.path as NSString?)?.deletingLastPathComponent {
return configurationForPath(containingDir)
}
return self
}
}
// MARK: - Nested Configurations Extension
extension Configuration {
fileprivate func configurationForPath(_ path: String) -> Configuration {
let path = path as NSString
let configurationSearchPath = path.appendingPathComponent(Configuration.fileName)
// If a configuration exists and it isn't us, load and merge the gurations
if configurationSearchPath != configurationPath &&
FileManager.default.fileExists(atPath: configurationSearchPath) {
return merge(Configuration(path: configurationSearchPath, rootPath: rootPath,
optional: false, quiet: true))
}
// If we are not at the root path, continue down the tree
if path as String != rootPath && path != "/" {
return configurationForPath(path.deletingLastPathComponent)
}
// If nothing else, return self
return self
}
// Currently merge simply overrides the current configuration with the new configuration.
// This requires that all configuration files be fully specified. In the future this should be
// improved to do a more intelligent merge allowing for partial nested configurations.
internal func merge(_ configuration: Configuration) -> Configuration {
return configuration
}
}
// Mark - == Implementation
public func == (lhs: Configuration, rhs: Configuration) -> Bool {
return (lhs.excluded == rhs.excluded) &&
(lhs.included == rhs.included) &&
(lhs.reporter == rhs.reporter) &&
(lhs.configurationPath == rhs.configurationPath) &&
(lhs.rootPath == lhs.rootPath) &&
(lhs.rules == rhs.rules)
}