-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Configuration+Merging.swift
140 lines (121 loc) · 6.08 KB
/
Configuration+Merging.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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: - Subtypes
// MARK: - Methods: Merging
internal 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
)
}
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)
} else {
return parentWarningTreshold
}
} else {
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 {
return (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(Configuration.defaultFileName)
let cacheIdentifier = "nestedPath" + rootDirectory + configurationSearchPath
if Configuration.getIsNestedConfigurationSelf(forIdentifier: cacheIdentifier) == true {
return self
} else if let cached = Configuration.getCached(forIdentifier: cacheIdentifier) {
return cached
} else {
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) != true
{
// 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
Configuration.setIsNestedConfigurationSelf(forIdentifier: cacheIdentifier, value: true)
}
return config
}
}
}