mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
a6c4fd98bc
Ideally, SwiftLintCore would some day only contain components that are needed to define rules. Consequently, it would be the only bundle required to import for (external) rule development.
142 lines
6.1 KiB
Swift
142 lines
6.1 KiB
Swift
import Foundation
|
|
import SourceKittenFramework
|
|
|
|
/// GENERAL NOTE ON MERGING: The child configuration is added on top of the parent configuration
|
|
/// and is preferred in case of conflicts!
|
|
|
|
extension Configuration {
|
|
// MARK: - Methods: Merging
|
|
package func merged(
|
|
withChild childConfiguration: Configuration,
|
|
rootDirectory: String = ""
|
|
) -> Configuration {
|
|
let mergedIncludedAndExcluded = self.mergedIncludedAndExcluded(
|
|
with: childConfiguration,
|
|
rootDirectory: rootDirectory
|
|
)
|
|
|
|
return Configuration(
|
|
rulesWrapper: rulesWrapper.merged(with: childConfiguration.rulesWrapper),
|
|
fileGraph: FileGraph(rootDirectory: rootDirectory),
|
|
includedPaths: mergedIncludedAndExcluded.includedPaths,
|
|
excludedPaths: mergedIncludedAndExcluded.excludedPaths,
|
|
indentation: childConfiguration.indentation,
|
|
warningThreshold: mergedWarningTreshold(with: childConfiguration),
|
|
reporter: reporter,
|
|
cachePath: cachePath,
|
|
allowZeroLintableFiles: childConfiguration.allowZeroLintableFiles,
|
|
strict: childConfiguration.strict,
|
|
lenient: childConfiguration.lenient,
|
|
baseline: childConfiguration.baseline,
|
|
writeBaseline: childConfiguration.writeBaseline,
|
|
checkForUpdates: childConfiguration.checkForUpdates
|
|
)
|
|
}
|
|
|
|
private func mergedIncludedAndExcluded(
|
|
with childConfiguration: Configuration,
|
|
rootDirectory: String
|
|
) -> (includedPaths: [String], excludedPaths: [String]) {
|
|
// Render paths relative to their respective root paths → makes them comparable
|
|
let childConfigIncluded = childConfiguration.includedPaths.map {
|
|
$0.bridge().absolutePathRepresentation(rootDirectory: childConfiguration.rootDirectory)
|
|
}
|
|
|
|
let childConfigExcluded = childConfiguration.excludedPaths.map {
|
|
$0.bridge().absolutePathRepresentation(rootDirectory: childConfiguration.rootDirectory)
|
|
}
|
|
|
|
let parentConfigIncluded = includedPaths.map {
|
|
$0.bridge().absolutePathRepresentation(rootDirectory: self.rootDirectory)
|
|
}
|
|
|
|
let parentConfigExcluded = excludedPaths.map {
|
|
$0.bridge().absolutePathRepresentation(rootDirectory: self.rootDirectory)
|
|
}
|
|
|
|
// Prefer child configuration over parent configuration
|
|
let includedPaths = parentConfigIncluded.filter { !childConfigExcluded.contains($0) } + childConfigIncluded
|
|
let excludedPaths = parentConfigExcluded.filter { !childConfigIncluded.contains($0) } + childConfigExcluded
|
|
|
|
// Return paths relative to the provided root directory
|
|
return (
|
|
includedPaths: includedPaths.map { $0.path(relativeTo: rootDirectory) },
|
|
excludedPaths: excludedPaths.map { $0.path(relativeTo: rootDirectory) }
|
|
)
|
|
}
|
|
|
|
private func mergedWarningTreshold(
|
|
with childConfiguration: Configuration
|
|
) -> Int? {
|
|
if let parentWarningTreshold = warningThreshold {
|
|
if let childWarningTreshold = childConfiguration.warningThreshold {
|
|
return min(childWarningTreshold, parentWarningTreshold)
|
|
}
|
|
return parentWarningTreshold
|
|
}
|
|
return childConfiguration.warningThreshold
|
|
}
|
|
|
|
// MARK: Accessing File Configurations
|
|
/// Returns a new configuration that applies to the specified file by merging the current configuration with any
|
|
/// nested configurations in the directory inheritance graph present until the level of the specified file.
|
|
///
|
|
/// - parameter file: The file for which to obtain a configuration value.
|
|
///
|
|
/// - returns: A new configuration.
|
|
public func configuration(for file: SwiftLintFile) -> Configuration {
|
|
(file.path?.bridge().deletingLastPathComponent).map(configuration(forDirectory:)) ?? self
|
|
}
|
|
|
|
private func configuration(forDirectory directory: String) -> Configuration {
|
|
// If the configuration was explicitly specified via the `--config` param, don't use nested configs
|
|
guard !basedOnCustomConfigurationFiles else { return self }
|
|
|
|
let directoryNSString = directory.bridge()
|
|
let configurationSearchPath = directoryNSString.appendingPathComponent(Self.defaultFileName)
|
|
let cacheIdentifier = "nestedPath" + rootDirectory + configurationSearchPath
|
|
|
|
if Self.getIsNestedConfigurationSelf(forIdentifier: cacheIdentifier) == true {
|
|
return self
|
|
}
|
|
if let cached = Self.getCached(forIdentifier: cacheIdentifier) {
|
|
return cached
|
|
}
|
|
var config: Configuration
|
|
|
|
if directory == rootDirectory {
|
|
// Use self if at level self
|
|
config = self
|
|
} else if
|
|
FileManager.default.fileExists(atPath: configurationSearchPath),
|
|
!fileGraph.includesFile(atPath: configurationSearchPath) {
|
|
// Use self merged with the nested config that was found
|
|
// iff that nested config has not already been used to build the main config
|
|
|
|
// Ignore parent_config / child_config specifications of nested configs
|
|
var childConfiguration = Configuration(
|
|
configurationFiles: [configurationSearchPath],
|
|
ignoreParentAndChildConfigs: true
|
|
)
|
|
childConfiguration.fileGraph = FileGraph(rootDirectory: directory)
|
|
config = merged(withChild: childConfiguration, rootDirectory: rootDirectory)
|
|
|
|
// Cache merged result to circumvent heavy merge recomputations
|
|
config.setCached(forIdentifier: cacheIdentifier)
|
|
} else if directory != "/" {
|
|
// If we are not at the root path, continue down the tree
|
|
config = configuration(forDirectory: directoryNSString.deletingLastPathComponent)
|
|
} else {
|
|
// Fallback to self
|
|
config = self
|
|
}
|
|
|
|
if config == self {
|
|
// Cache that for this path, the config equals self
|
|
Self.setIsNestedConfigurationSelf(forIdentifier: cacheIdentifier, value: true)
|
|
}
|
|
|
|
return config
|
|
}
|
|
}
|