Skip to content

.swift-format: Specify an exhaustive configuration #1057

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 11, 2025

Conversation

AnthonyLatsis
Copy link
Contributor

Mirrors swiftlang/swift-syntax#3117.


Unexpectedly, an omitted rule tells swift-format to ignore it altogether rather than assume the default setting.

This change applies the default configuration, retaining current settings and disabling 3 default-enabled rules: DoNotUseSemicolons, NoAccessLevelOnExtensionDeclaration, and DontRepeatTypeInStaticProperties. The latter is disabled due to undesired effect:

warning: [DontRepeatTypeInStaticProperties] remove the suffix 'Paths' from the name of the variable 'envSearchPaths'

Unexpectedly, an omitted rule tells swift-format to ignore it altogether
rather than assume the default setting.

This change applies the default configuration, retaining current
settings and disabling 3 default-enabled rules: `DoNotUseSemicolons`,
`NoAccessLevelOnExtensionDeclaration`, and
`DontRepeatTypeInStaticProperties`. The latter is disabled due to
undesired effect:

```
warning: [DontRepeatTypeInStaticProperties] remove the suffix 'Paths' from the name of the variable 'envSearchPaths'
```
Copy link
Member

@allevato allevato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration decoder explicitly uses decodeIfPresent and falls back to the default values if the key wasn't in the JSON:

// Unfortunately, to allow the user to leave out configuration options in the JSON, we would
// have to make them optional properties, but that makes using the type in the rest of the code
// more annoying because we'd have to unwrap everything. So, we override this initializer and
// provide the defaults ourselves if needed. We get those defaults by pulling them from a
// default-initialized instance.
let defaults = Configuration()
self.maximumBlankLines =
try container.decodeIfPresent(Int.self, forKey: .maximumBlankLines)
?? defaults.maximumBlankLines
self.lineLength =
try container.decodeIfPresent(Int.self, forKey: .lineLength)
?? defaults.lineLength
self.spacesBeforeEndOfLineComments =
try container.decodeIfPresent(Int.self, forKey: .spacesBeforeEndOfLineComments)
?? defaults.spacesBeforeEndOfLineComments
self.tabWidth =
try container.decodeIfPresent(Int.self, forKey: .tabWidth)
?? defaults.tabWidth
self.indentation =
try container.decodeIfPresent(Indent.self, forKey: .indentation)
?? defaults.indentation
self.respectsExistingLineBreaks =
try container.decodeIfPresent(Bool.self, forKey: .respectsExistingLineBreaks)
?? defaults.respectsExistingLineBreaks
self.lineBreakBeforeControlFlowKeywords =
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeControlFlowKeywords)
?? defaults.lineBreakBeforeControlFlowKeywords
self.lineBreakBeforeEachArgument =
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachArgument)
?? defaults.lineBreakBeforeEachArgument
self.lineBreakBeforeEachGenericRequirement =
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBeforeEachGenericRequirement)
?? defaults.lineBreakBeforeEachGenericRequirement
self.lineBreakBetweenDeclarationAttributes =
try container.decodeIfPresent(Bool.self, forKey: .lineBreakBetweenDeclarationAttributes)
?? defaults.lineBreakBetweenDeclarationAttributes
self.prioritizeKeepingFunctionOutputTogether =
try container.decodeIfPresent(Bool.self, forKey: .prioritizeKeepingFunctionOutputTogether)
?? defaults.prioritizeKeepingFunctionOutputTogether
self.indentConditionalCompilationBlocks =
try container.decodeIfPresent(Bool.self, forKey: .indentConditionalCompilationBlocks)
?? defaults.indentConditionalCompilationBlocks
self.lineBreakAroundMultilineExpressionChainComponents =
try container.decodeIfPresent(
Bool.self,
forKey: .lineBreakAroundMultilineExpressionChainComponents
)
?? defaults.lineBreakAroundMultilineExpressionChainComponents
self.spacesAroundRangeFormationOperators =
try container.decodeIfPresent(
Bool.self,
forKey: .spacesAroundRangeFormationOperators
)
?? defaults.spacesAroundRangeFormationOperators
self.fileScopedDeclarationPrivacy =
try container.decodeIfPresent(
FileScopedDeclarationPrivacyConfiguration.self,
forKey: .fileScopedDeclarationPrivacy
)
?? defaults.fileScopedDeclarationPrivacy
self.indentSwitchCaseLabels =
try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels)
?? defaults.indentSwitchCaseLabels
self.noAssignmentInExpressions =
try container.decodeIfPresent(
NoAssignmentInExpressionsConfiguration.self,
forKey: .noAssignmentInExpressions
)
?? defaults.noAssignmentInExpressions
self.multiElementCollectionTrailingCommas =
try container.decodeIfPresent(
Bool.self,
forKey: .multiElementCollectionTrailingCommas
)
?? defaults.multiElementCollectionTrailingCommas
self.reflowMultilineStringLiterals = try {
// Try to decode `reflowMultilineStringLiterals` as a string
// This handles configurations using the String raw value format (e.g. "never").
// If an error occurs, we'll silently bypass it and fall back to the legacy behavior.
if let behavior = try? container.decodeIfPresent(
MultilineStringReflowBehavior.self,
forKey: .reflowMultilineStringLiterals
) {
return behavior
}
// If the modern format fails, try to decode as an object with a single key.
// This handles configurations from swift-format v601.0.0 (e.g. { "never": {} }).
// If an error occurs in this step, we'll propagate it to the caller.
if let legacyBehavior = try container.decodeIfPresent(
LegacyMultilineStringReflowBehavior.self,
forKey: .reflowMultilineStringLiterals
) {
return legacyBehavior.toMultilineStringReflowBehavior()
}
// If the key is not present in the configuration at all, use the default value.
return defaults.reflowMultilineStringLiterals
}()
self.indentBlankLines =
try container.decodeIfPresent(
Bool.self,
forKey: .indentBlankLines
)
?? defaults.indentBlankLines
// If the `rules` key is not present at all, default it to the built-in set
// so that the behavior is the same as if the configuration had been
// default-initialized. To get an empty rules dictionary, one can explicitly
// set the `rules` key to `{}`.
self.rules =
try container.decodeIfPresent([String: Bool].self, forKey: .rules)
?? defaults.rules

...ah, but that still means that any rules dictionary being present will be used as the entire thing, rather than assuming the default values for any rules not present. We should probably fix that.

In the meantime, no reason to block this PR on that fix.

@allevato allevato merged commit df8de68 into swiftlang:main Aug 11, 2025
24 checks passed
@AnthonyLatsis AnthonyLatsis deleted the jepa branch August 11, 2025 17:11
@bnbarham
Copy link
Contributor

We should probably fix that.

Indeed. Though it's probably something we need to do under a new "style" given how breaking it could be 😓

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants