Skip to content

Commit b7260f0

Browse files
authored
Merge pull request #580 from allevato/separable-config
Move the default `Configuration.init()` into a separate file.
2 parents c6484ec + 18d598e commit b7260f0

31 files changed

+276
-162
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
extension Configuration {
14+
/// Creates a new `Configuration` with default values.
15+
///
16+
/// This initializer is isolated to its own file to make it easier for users who are forking or
17+
/// building swift-format themselves to hardcode a different default configuration. To do this,
18+
/// simply replace this file with your own default initializer that sets the values to whatever
19+
/// you want.
20+
///
21+
/// When swift-format reads a configuration file from disk, any values that are not specified in
22+
/// the JSON will be populated from this default configuration.
23+
public init() {
24+
self.rules = Self.defaultRuleEnablements
25+
self.maximumBlankLines = 1
26+
self.lineLength = 100
27+
self.tabWidth = 8
28+
self.indentation = .spaces(2)
29+
self.respectsExistingLineBreaks = true
30+
self.lineBreakBeforeControlFlowKeywords = false
31+
self.lineBreakBeforeEachArgument = false
32+
self.lineBreakBeforeEachGenericRequirement = false
33+
self.prioritizeKeepingFunctionOutputTogether = false
34+
self.indentConditionalCompilationBlocks = true
35+
self.lineBreakAroundMultilineExpressionChainComponents = false
36+
self.fileScopedDeclarationPrivacy = FileScopedDeclarationPrivacyConfiguration()
37+
self.indentSwitchCaseLabels = false
38+
self.spacesAroundRangeFormationOperators = false
39+
self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
40+
}
41+
}

Sources/SwiftFormatConfiguration/Configuration.swift

Lines changed: 83 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -14,7 +14,12 @@ import Foundation
1414

1515
/// A version number that can be specified in the configuration file, which allows us to change the
1616
/// format in the future if desired and still support older files.
17-
fileprivate let highestSupportedConfigurationVersion = 1
17+
///
18+
/// Note that *adding* new configuration values is not a version-breaking change; swift-format will
19+
/// use default values when loading older configurations that don't contain the new settings. This
20+
/// value only needs to be updated if the configuration changes in a way that would be incompatible
21+
/// with the previous format.
22+
internal let highestSupportedConfigurationVersion = 1
1823

1924
/// Holds the complete set of configured values and defaults.
2025
public struct Configuration: Codable, Equatable {
@@ -39,30 +44,36 @@ public struct Configuration: Codable, Equatable {
3944
case noAssignmentInExpressions
4045
}
4146

47+
/// A dictionary containing the default enabled/disabled states of rules, keyed by the rules'
48+
/// names.
49+
///
50+
/// This value is generated by `generate-pipeline` based on the `isOptIn` value of each rule.
51+
public static let defaultRuleEnablements: [String: Bool] = RuleRegistry.rules
52+
4253
/// The version of this configuration.
43-
private let version: Int
54+
private var version: Int = highestSupportedConfigurationVersion
4455

4556
/// MARK: Common configuration
4657

4758
/// The dictionary containing the rule names that we wish to run on. A rule is not used if it is
4859
/// marked as `false`, or if it is missing from the dictionary.
49-
public var rules: [String: Bool] = RuleRegistry.rules
60+
public var rules: [String: Bool]
5061

5162
/// The maximum number of consecutive blank lines that may appear in a file.
52-
public var maximumBlankLines = 1
63+
public var maximumBlankLines: Int
5364

5465
/// The maximum length of a line of source code, after which the formatter will break lines.
55-
public var lineLength = 100
66+
public var lineLength: Int
5667

5768
/// The width of the horizontal tab in spaces.
5869
///
5970
/// This value is used when converting indentation types (for example, from tabs into spaces).
60-
public var tabWidth = 8
71+
public var tabWidth: Int
6172

6273
/// A value representing a single level of indentation.
6374
///
6475
/// All indentation will be conducted in multiples of this configuration.
65-
public var indentation: Indent = .spaces(2)
76+
public var indentation: Indent
6677

6778
/// Indicates that the formatter should try to respect users' discretionary line breaks when
6879
/// possible.
@@ -71,7 +82,7 @@ public struct Configuration: Codable, Equatable {
7182
/// line, but for readability the user might break it inside the curly braces. If this setting is
7283
/// true, those line breaks will be kept. If this setting is false, the formatter will act more
7384
/// "opinionated" and collapse the statement onto a single line.
74-
public var respectsExistingLineBreaks = true
85+
public var respectsExistingLineBreaks: Bool
7586

7687
/// MARK: Rule-specific configuration
7788

@@ -81,23 +92,23 @@ public struct Configuration: Codable, Equatable {
8192
/// If true, a line break will be added before the keyword, forcing it onto its own line. If
8293
/// false (the default), the keyword will be placed after the closing brace (separated by a
8394
/// space).
84-
public var lineBreakBeforeControlFlowKeywords = false
95+
public var lineBreakBeforeControlFlowKeywords: Bool
8596

8697
/// Determines the line-breaking behavior for generic arguments and function arguments when a
8798
/// declaration is wrapped onto multiple lines.
8899
///
89100
/// If false (the default), arguments will be laid out horizontally first, with line breaks only
90101
/// being fired when the line length would be exceeded. If true, a line break will be added before
91102
/// each argument, forcing the entire argument list to be laid out vertically.
92-
public var lineBreakBeforeEachArgument = false
103+
public var lineBreakBeforeEachArgument: Bool
93104

94105
/// Determines the line-breaking behavior for generic requirements when the requirements list
95106
/// is wrapped onto multiple lines.
96107
///
97108
/// If true, a line break will be added before each requirement, forcing the entire requirements
98109
/// list to be laid out vertically. If false (the default), requirements will be laid out
99110
/// horizontally first, with line breaks only being fired when the line length would be exceeded.
100-
public var lineBreakBeforeEachGenericRequirement = false
111+
public var lineBreakBeforeEachGenericRequirement: Bool
101112

102113
/// Determines if function-like declaration outputs should be prioritized to be together with the
103114
/// function signature right (closing) parenthesis.
@@ -107,21 +118,21 @@ public struct Configuration: Codable, Equatable {
107118
/// a line break will be fired after the function signature first, indenting the declaration output
108119
/// one additional level. If true, A line break will be fired further up in the function's
109120
/// declaration (e.g. generic parameters, parameters) before breaking on the function's output.
110-
public var prioritizeKeepingFunctionOutputTogether = false
121+
public var prioritizeKeepingFunctionOutputTogether: Bool
111122

112123
/// Determines the indentation behavior for `#if`, `#elseif`, and `#else`.
113-
public var indentConditionalCompilationBlocks = true
124+
public var indentConditionalCompilationBlocks: Bool
114125

115126
/// Determines whether line breaks should be forced before and after multiline components of
116127
/// dot-chained expressions, such as function calls and subscripts chained together through member
117128
/// access (i.e. "." expressions). When any component is multiline and this option is true, a line
118129
/// break is forced before the "." of the component and after the component's closing delimiter
119130
/// (i.e. right paren, right bracket, right brace, etc.).
120-
public var lineBreakAroundMultilineExpressionChainComponents = false
131+
public var lineBreakAroundMultilineExpressionChainComponents: Bool
121132

122133
/// Determines the formal access level (i.e., the level specified in source code) for file-scoped
123134
/// declarations whose effective access level is private to the containing file.
124-
public var fileScopedDeclarationPrivacy = FileScopedDeclarationPrivacyConfiguration()
135+
public var fileScopedDeclarationPrivacy: FileScopedDeclarationPrivacyConfiguration
125136

126137
/// Determines if `case` statements should be indented compared to the containing `switch` block.
127138
///
@@ -142,19 +153,14 @@ public struct Configuration: Codable, Equatable {
142153
/// ...
143154
/// }
144155
///```
145-
public var indentSwitchCaseLabels = false
156+
public var indentSwitchCaseLabels: Bool
146157

147158
/// Determines whether whitespace should be forced before and after the range formation operators
148159
/// `...` and `..<`.
149-
public var spacesAroundRangeFormationOperators = false
160+
public var spacesAroundRangeFormationOperators: Bool
150161

151162
/// Contains exceptions for the `NoAssignmentInExpressions` rule.
152-
public var noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
153-
154-
/// Constructs a Configuration with all default values.
155-
public init() {
156-
self.version = highestSupportedConfigurationVersion
157-
}
163+
public var noAssignmentInExpressions: NoAssignmentInExpressionsConfiguration
158164

159165
/// Constructs a Configuration by loading it from a configuration file.
160166
public init(contentsOf url: URL) throws {
@@ -165,11 +171,6 @@ public struct Configuration: Codable, Equatable {
165171
public init(from decoder: Decoder) throws {
166172
let container = try decoder.container(keyedBy: CodingKeys.self)
167173

168-
// Unfortunately, to allow the user to leave out configuration options in the JSON, we would
169-
// have to make them optional properties, but that makes using the type in the rest of the code
170-
// more annoying because we'd have to unwrap everything. So, we override this initializer and
171-
// provide the defaults ourselves if needed.
172-
173174
// If the version number is not present, assume it is 1.
174175
self.version = try container.decodeIfPresent(Int.self, forKey: .version) ?? 1
175176
guard version <= highestSupportedConfigurationVersion else {
@@ -182,47 +183,70 @@ public struct Configuration: Codable, Equatable {
182183
// If we ever introduce a new version, this is where we should switch on the decoded version
183184
// number and dispatch to different decoding methods.
184185

185-
self.maximumBlankLines
186-
= try container.decodeIfPresent(Int.self, forKey: .maximumBlankLines) ?? 1
187-
self.lineLength = try container.decodeIfPresent(Int.self, forKey: .lineLength) ?? 100
188-
self.tabWidth = try container.decodeIfPresent(Int.self, forKey: .tabWidth) ?? 8
189-
self.indentation
190-
= try container.decodeIfPresent(Indent.self, forKey: .indentation) ?? .spaces(2)
191-
self.respectsExistingLineBreaks
192-
= try container.decodeIfPresent(Bool.self, forKey: .respectsExistingLineBreaks) ?? true
193-
self.lineBreakBeforeControlFlowKeywords
194-
= try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowKeywords) ?? false
195-
self.lineBreakBeforeEachArgument
196-
= try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachArgument) ?? false
197-
self.lineBreakBeforeEachGenericRequirement
198-
= try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement) ?? false
199-
self.prioritizeKeepingFunctionOutputTogether
200-
= try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether) ?? false
201-
self.indentConditionalCompilationBlocks
202-
= try container.decodeIfPresent(Bool.self, forKey: .indentConditionalCompilationBlocks) ?? true
186+
// Unfortunately, to allow the user to leave out configuration options in the JSON, we would
187+
// have to make them optional properties, but that makes using the type in the rest of the code
188+
// more annoying because we'd have to unwrap everything. So, we override this initializer and
189+
// provide the defaults ourselves if needed. We get those defaults by pulling them from a
190+
// default-initialized instance.
191+
let defaults = Configuration()
192+
193+
self.maximumBlankLines =
194+
try container.decodeIfPresent(Int.self, forKey: .maximumBlankLines)
195+
?? defaults.maximumBlankLines
196+
self.lineLength =
197+
try container.decodeIfPresent(Int.self, forKey: .lineLength)
198+
?? defaults.lineLength
199+
self.tabWidth =
200+
try container.decodeIfPresent(Int.self, forKey: .tabWidth)
201+
?? defaults.tabWidth
202+
self.indentation =
203+
try container.decodeIfPresent(Indent.self, forKey: .indentation)
204+
?? defaults.indentation
205+
self.respectsExistingLineBreaks =
206+
try container.decodeIfPresent(Bool.self, forKey: .respectsExistingLineBreaks)
207+
?? defaults.respectsExistingLineBreaks
208+
self.lineBreakBeforeControlFlowKeywords =
209+
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowKeywords)
210+
?? defaults.lineBreakBeforeControlFlowKeywords
211+
self.lineBreakBeforeEachArgument =
212+
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachArgument)
213+
?? defaults.lineBreakBeforeEachArgument
214+
self.lineBreakBeforeEachGenericRequirement =
215+
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement)
216+
?? defaults.lineBreakBeforeEachGenericRequirement
217+
self.prioritizeKeepingFunctionOutputTogether =
218+
try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether)
219+
?? defaults.prioritizeKeepingFunctionOutputTogether
220+
self.indentConditionalCompilationBlocks =
221+
try container.decodeIfPresent(Bool.self, forKey: .indentConditionalCompilationBlocks)
222+
?? defaults.indentConditionalCompilationBlocks
203223
self.lineBreakAroundMultilineExpressionChainComponents =
204224
try container.decodeIfPresent(
205-
Bool.self, forKey: .lineBreakAroundMultilineExpressionChainComponents) ?? false
225+
Bool.self, forKey: .lineBreakAroundMultilineExpressionChainComponents)
226+
?? defaults.lineBreakAroundMultilineExpressionChainComponents
206227
self.spacesAroundRangeFormationOperators =
207228
try container.decodeIfPresent(
208-
Bool.self, forKey: .spacesAroundRangeFormationOperators) ?? false
229+
Bool.self, forKey: .spacesAroundRangeFormationOperators)
230+
?? defaults.spacesAroundRangeFormationOperators
209231
self.fileScopedDeclarationPrivacy =
210232
try container.decodeIfPresent(
211233
FileScopedDeclarationPrivacyConfiguration.self, forKey: .fileScopedDeclarationPrivacy)
212-
?? FileScopedDeclarationPrivacyConfiguration()
213-
self.indentSwitchCaseLabels
214-
= try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels) ?? false
234+
?? defaults.fileScopedDeclarationPrivacy
235+
self.indentSwitchCaseLabels =
236+
try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels)
237+
?? defaults.indentSwitchCaseLabels
215238
self.noAssignmentInExpressions =
216239
try container.decodeIfPresent(
217240
NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions)
218-
?? NoAssignmentInExpressionsConfiguration()
241+
?? defaults.noAssignmentInExpressions
219242

220243
// If the `rules` key is not present at all, default it to the built-in set
221244
// so that the behavior is the same as if the configuration had been
222245
// default-initialized. To get an empty rules dictionary, one can explicitly
223246
// set the `rules` key to `{}`.
224-
self.rules
225-
= try container.decodeIfPresent([String: Bool].self, forKey: .rules) ?? RuleRegistry.rules
247+
self.rules =
248+
try container.decodeIfPresent([String: Bool].self, forKey: .rules)
249+
?? defaults.rules
226250
}
227251

228252
public func encode(to encoder: Encoder) throws {
@@ -295,6 +319,8 @@ public struct FileScopedDeclarationPrivacyConfiguration: Codable, Equatable {
295319
/// The formal access level to use when encountering a file-scoped declaration with effective
296320
/// private access.
297321
public var accessLevel: AccessLevel = .private
322+
323+
public init() {}
298324
}
299325

300326
/// Configuration for the `NoAssignmentInExpressions` rule.
@@ -307,4 +333,6 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable {
307333
// in the test.
308334
"XCTAssertNoThrow"
309335
]
336+
337+
public init() {}
310338
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftFormatConfiguration
14+
15+
extension Configuration {
16+
/// The default configuration to be used during unit tests.
17+
///
18+
/// This configuration is separate from `Configuration.init()` so that that configuration can be
19+
/// replaced without breaking tests that implicitly rely on it. Unfortunately, since this is in a
20+
/// different module than where `Configuration` is defined, we can't make this an initializer that
21+
/// would enforce that every field of `Configuration` is initialized here (we're forced to
22+
/// delegate to another initializer first, which defeats the purpose). So, users adding new
23+
/// configuration settings shouls be sure to supply a default here for testing, otherwise they
24+
/// will be implicitly relying on the real default.
25+
public static var forTesting: Configuration {
26+
var config = Configuration()
27+
config.rules = Configuration.defaultRuleEnablements
28+
config.maximumBlankLines = 1
29+
config.lineLength = 100
30+
config.tabWidth = 8
31+
config.indentation = .spaces(2)
32+
config.respectsExistingLineBreaks = true
33+
config.lineBreakBeforeControlFlowKeywords = false
34+
config.lineBreakBeforeEachArgument = false
35+
config.lineBreakBeforeEachGenericRequirement = false
36+
config.prioritizeKeepingFunctionOutputTogether = false
37+
config.indentConditionalCompilationBlocks = true
38+
config.lineBreakAroundMultilineExpressionChainComponents = false
39+
config.fileScopedDeclarationPrivacy = FileScopedDeclarationPrivacyConfiguration()
40+
config.indentSwitchCaseLabels = false
41+
config.spacesAroundRangeFormationOperators = false
42+
config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
43+
return config
44+
}
45+
}

Tests/SwiftFormatPrettyPrintTests/AssignmentExprTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ final class AssignmentExprTests: PrettyPrintTestCase {
8484
8585
"""
8686

87-
var config = Configuration()
87+
var config = Configuration.forTesting
8888
config.lineBreakBeforeEachArgument = false
8989
assertPrettyPrintEqual(
9090
input: input, expected: expectedWithArgBinPacking, linelength: 35, configuration: config)
@@ -166,7 +166,7 @@ final class AssignmentExprTests: PrettyPrintTestCase {
166166
167167
"""
168168

169-
var config = Configuration()
169+
var config = Configuration.forTesting
170170
config.lineBreakBeforeEachArgument = false
171171
assertPrettyPrintEqual(
172172
input: input, expected: expectedWithArgBinPacking, linelength: 35, configuration: config)

0 commit comments

Comments
 (0)