@@ -21,12 +21,15 @@ extension Yaml {
2121 }
2222}
2323
24- public struct Configuration {
24+ public struct Configuration : Equatable {
2525 public let disabledRules : [ String ] // disabled_rules
2626 public let included : [ String ] // included
2727 public let excluded : [ String ] // excluded
2828 public let reporter : String // reporter (xcode, json, csv, checkstyle)
2929 public let rules : [ Rule ]
30+ public let useNestedConfigs : Bool // process nested configs, will default to false
31+ public var rootPath : String ? // the root path of the lint to search for nested configs
32+ private var configPath : String ? // if successfully load from a path
3033
3134 public var reporterFromString : Reporter . Type {
3235 switch reporter {
@@ -47,10 +50,12 @@ public struct Configuration {
4750 included: [ String ] = [ ] ,
4851 excluded: [ String ] = [ ] ,
4952 reporter: String = " xcode " ,
50- rules: [ Rule ] = Configuration . rulesFromYAML ( ) ) {
53+ rules: [ Rule ] = Configuration . rulesFromYAML ( ) ,
54+ useNestedConfigs: Bool = false ) {
5155 self . included = included
5256 self . excluded = excluded
5357 self . reporter = reporter
58+ self . useNestedConfigs = useNestedConfigs
5459
5560 // Validate that all rule identifiers map to a defined rule
5661
@@ -89,23 +94,35 @@ public struct Configuration {
8994 }
9095
9196 public init ? ( yaml: String ) {
92- let yamlResult = Yaml . load ( yaml)
93- guard let yamlConfig = yamlResult. value else {
94- if let error = yamlResult. error {
95- queuedPrint ( error)
96- }
97+ guard let yamlConfig = Configuration . loadYaml ( yaml) else {
9798 return nil
9899 }
100+ self . init ( yamlConfig: yamlConfig)
101+ }
102+
103+ private init ? ( yamlConfig: Yaml ) {
99104 self . init (
100105 disabledRules: yamlConfig [ " disabled_rules " ] . arrayOfStrings ?? [ ] ,
101106 included: yamlConfig [ " included " ] . arrayOfStrings ?? [ ] ,
102107 excluded: yamlConfig [ " excluded " ] . arrayOfStrings ?? [ ] ,
103108 reporter: yamlConfig [ " reporter " ] . string ?? XcodeReporter . identifier,
104- rules: Configuration . rulesFromYAML ( yamlConfig)
109+ rules: Configuration . rulesFromYAML ( yamlConfig) ,
110+ useNestedConfigs: yamlConfig [ " use_nested_configs " ] . bool ?? false
105111 )
106112 }
107113
108- public init ( path: String = " .swiftlint.yml " , optional: Bool = true ) {
114+ private static func loadYaml( yaml: String ) -> Yaml ? {
115+ let yamlResult = Yaml . load ( yaml)
116+ if let yamlConfig = yamlResult. value {
117+ return yamlConfig
118+ }
119+ if let error = yamlResult. error {
120+ queuedPrint ( error)
121+ }
122+ return nil
123+ }
124+
125+ public init ( path: String = " .swiftlint.yml " , optional: Bool = true , silent: Bool = false ) {
109126 let fullPath = ( path as NSString ) . absolutePathRepresentation ( )
110127 let failIfRequired = {
111128 if !optional { fatalError ( " Could not read configuration file at path ' \( fullPath) ' " ) }
@@ -118,9 +135,12 @@ public struct Configuration {
118135 do {
119136 let yamlContents = try NSString ( contentsOfFile: fullPath,
120137 encoding: NSUTF8StringEncoding) as String
121- if let _ = Configuration ( yaml: yamlContents) {
122- queuedPrintError ( " Loading configuration from ' \( path) ' " )
123- self . init ( yaml: yamlContents) !
138+ if let yamlConfig = Configuration . loadYaml ( yamlContents) {
139+ if !silent {
140+ queuedPrintError ( " Loading configuration from ' \( path) ' " )
141+ }
142+ self . init ( yamlConfig: yamlConfig) !
143+ configPath = fullPath
124144 return
125145 } else {
126146 failIfRequired ( )
@@ -188,4 +208,55 @@ public struct Configuration {
188208 let allPaths = self . lintablePathsForPath ( path)
189209 return allPaths. flatMap { File ( path: $0) }
190210 }
211+
212+ public func configForFile( file: File ) -> Configuration {
213+ if useNestedConfigs,
214+ let containingDir = ( file. path as NSString ? ) ? . stringByDeletingLastPathComponent {
215+ return configForPath ( containingDir)
216+ }
217+ return self
218+ }
219+ }
220+
221+ // MARK: - Nested Configurations Extension
222+
223+ public extension Configuration {
224+ func configForPath( path: String ) -> Configuration {
225+ let path = path as NSString
226+ let configSearchPath = path. stringByAppendingPathComponent ( " .swiftlint.yml " )
227+
228+ // If a config exists and it isn't us, load and merge the configs
229+ if configSearchPath != configPath &&
230+ NSFileManager . defaultManager ( ) . fileExistsAtPath ( configSearchPath) {
231+ return merge ( Configuration ( path: configSearchPath, optional: false , silent: true ) )
232+ }
233+
234+ // If we are not at the root path, continue down the tree
235+ if path != rootPath {
236+ return configForPath ( path. stringByDeletingLastPathComponent)
237+ }
238+
239+ // If nothing else, return self
240+ return self
241+ }
242+
243+ // Currently merge simply overrides the current configuration with the new configuration.
244+ // This requires that all config files be fully specified. In the future this will be changed
245+ // to do a more intelligent merge allowing for partial nested configs.
246+ func merge( config: Configuration ) -> Configuration {
247+ return config
248+ }
249+ }
250+
251+ // Mark - == Implementation
252+
253+ public func == ( lhs: Configuration , rhs: Configuration ) -> Bool {
254+ return ( lhs. disabledRules == rhs. disabledRules) &&
255+ ( lhs. excluded == rhs. excluded) &&
256+ ( lhs. included == rhs. included) &&
257+ ( lhs. reporter == rhs. reporter) &&
258+ ( lhs. useNestedConfigs == rhs. useNestedConfigs) &&
259+ ( lhs. configPath == rhs. configPath) &&
260+ ( lhs. rootPath == lhs. rootPath) &&
261+ ( lhs. rules == rhs. rules)
191262}
0 commit comments