Skip to content

Commit 4f559a0

Browse files
authored
Merge pull request #556 from allevato/5.9-cherrypicks
[5.9] Various cherry-picks since the branch cut.
2 parents 6bbd310 + 7970ac3 commit 4f559a0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2320
-709
lines changed

Package.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ let package = Package(
143143
.product(name: "ArgumentParser", package: "swift-argument-parser"),
144144
.product(name: "SwiftSyntax", package: "swift-syntax"),
145145
.product(name: "SwiftParser", package: "swift-syntax"),
146-
.product(name: "TSCBasic", package: "swift-tools-support-core"),
147146
]
148147
),
149148

@@ -214,20 +213,17 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
214213
// Building standalone.
215214
package.dependencies += [
216215
.package(
217-
url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"),
216+
url: "https://github.com/apple/swift-argument-parser.git",
217+
from: "1.2.2"
218+
),
218219
.package(
219220
url: "https://github.com/apple/swift-syntax.git",
220221
branch: "release/5.9"
221222
),
222-
.package(
223-
url: "https://github.com/apple/swift-tools-support-core.git",
224-
exact: Version("0.4.0")
225-
),
226223
]
227224
} else {
228225
package.dependencies += [
229226
.package(path: "../swift-argument-parser"),
230227
.package(path: "../swift-syntax"),
231-
.package(path: "../swift-tools-support-core"),
232228
]
233229
}

README.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,26 @@ invoked via an [API](#api-usage).
1313
> and the code is provided so that it can be tested on real-world code and
1414
> experiments can be made by modifying it.
1515
16-
## Matching swift-format to Your Swift Version (Swift 5.7 and earlier)
16+
## Matching swift-format to Your Swift Version
1717

18-
> NOTE: `swift-format` on the `main` branch now uses a version of
19-
> [SwiftSyntax](https://github.com/apple/swift-syntax) whose parser has been
20-
> rewritten in Swift and no longer has dependencies on libraries in the
21-
> Swift toolchain. This allows `swift-format` to be built, developed, and
22-
> run using any version of Swift that can compile it, decoupling it from
23-
> the version that supported a particular syntax.
18+
### Swift 5.8 and later
19+
20+
As of Swift 5.8, swift-format depends on the version of
21+
[SwiftSyntax](https://github.com/apple/swift-syntax) whose parser has been
22+
rewritten in Swift and no longer has dependencies on libraries in the
23+
Swift toolchain.
24+
25+
This change allows `swift-format` to be built, developed, and run using
26+
any version of Swift that can compile it, decoupling it from the version
27+
that supported a particular syntax. However, earlier versions of swift-format
28+
will still not be able to recognize new syntax added in later versions of the
29+
language and parser.
30+
31+
Note also that the version numbering scheme has changed to match
32+
SwiftSyntax; the 5.8 release of swift-format is `508.0.0`, not `0.50800.0`,
33+
and future versions are also expressed this way.
34+
35+
### Swift 5.7 and earlier
2436

2537
`swift-format` versions 0.50700.0 and earlier depend on versions of
2638
[SwiftSyntax](https://github.com/apple/swift-syntax) that used a standalone
@@ -54,7 +66,7 @@ then once you have identified the version you need, you can check out the
5466
source and build it using the following commands:
5567

5668
```sh
57-
VERSION=0.50700.0 # replace this with the version you need
69+
VERSION=509.0.0 # replace this with the version you need
5870
git clone https://github.com/apple/swift-format.git
5971
cd swift-format
6072
git checkout "tags/$VERSION"

Sources/SwiftFormat/Parsing.swift

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,53 @@ func parseAndEmitDiagnostics(
4343
operatorTable.foldAll(Parser.parse(source: source)) { _ in }.as(SourceFileSyntax.self)!
4444

4545
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: sourceFile)
46+
var hasErrors = false
4647
if let parsingDiagnosticHandler = parsingDiagnosticHandler {
4748
let expectedConverter =
4849
SourceLocationConverter(file: url?.path ?? "<unknown>", tree: sourceFile)
4950
for diagnostic in diagnostics {
5051
let location = diagnostic.location(converter: expectedConverter)
51-
parsingDiagnosticHandler(diagnostic, location)
52+
53+
// Downgrade editor placeholders to warnings, because it is useful to support formatting
54+
// in-progress files that contain those.
55+
if diagnostic.diagnosticID == StaticTokenError.editorPlaceholder.diagnosticID {
56+
parsingDiagnosticHandler(downgradedToWarning(diagnostic), location)
57+
} else {
58+
parsingDiagnosticHandler(diagnostic, location)
59+
hasErrors = true
60+
}
5261
}
5362
}
5463

55-
guard diagnostics.isEmpty else {
64+
guard !hasErrors else {
5665
throw SwiftFormatError.fileContainsInvalidSyntax
5766
}
5867

5968
return restoringLegacyTriviaBehavior(sourceFile)
6069
}
70+
71+
// Wraps a `DiagnosticMessage` but forces its severity to be that of a warning instead of an error.
72+
struct DowngradedDiagnosticMessage: DiagnosticMessage {
73+
var originalDiagnostic: DiagnosticMessage
74+
75+
var message: String { originalDiagnostic.message }
76+
77+
var diagnosticID: SwiftDiagnostics.MessageID { originalDiagnostic.diagnosticID }
78+
79+
var severity: DiagnosticSeverity { .warning }
80+
}
81+
82+
/// Returns a new `Diagnostic` that is identical to the given diagnostic, except that its severity
83+
/// has been downgraded to a warning.
84+
func downgradedToWarning(_ diagnostic: Diagnostic) -> Diagnostic {
85+
// `Diagnostic` is immutable, so create a new one with the same values except for the
86+
// severity-downgraded message.
87+
return Diagnostic(
88+
node: diagnostic.node,
89+
position: diagnostic.position,
90+
message: DowngradedDiagnosticMessage(originalDiagnostic: diagnostic.diagMessage),
91+
highlights: diagnostic.highlights,
92+
notes: diagnostic.notes,
93+
fixIts: diagnostic.fixIts
94+
)
95+
}

Sources/SwiftFormatConfiguration/Configuration.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public struct Configuration: Codable, Equatable {
3636
case indentSwitchCaseLabels
3737
case rules
3838
case spacesAroundRangeFormationOperators
39+
case noAssignmentInExpressions
3940
}
4041

4142
/// The version of this configuration.
@@ -147,6 +148,9 @@ public struct Configuration: Codable, Equatable {
147148
/// `...` and `..<`.
148149
public var spacesAroundRangeFormationOperators = false
149150

151+
/// Contains exceptions for the `NoAssignmentInExpressions` rule.
152+
public var noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
153+
150154
/// Constructs a Configuration with all default values.
151155
public init() {
152156
self.version = highestSupportedConfigurationVersion
@@ -208,6 +212,10 @@ public struct Configuration: Codable, Equatable {
208212
?? FileScopedDeclarationPrivacyConfiguration()
209213
self.indentSwitchCaseLabels
210214
= try container.decodeIfPresent(Bool.self, forKey: .indentSwitchCaseLabels) ?? false
215+
self.noAssignmentInExpressions =
216+
try container.decodeIfPresent(
217+
NoAssignmentInExpressionsConfiguration.self, forKey: .noAssignmentInExpressions)
218+
?? NoAssignmentInExpressionsConfiguration()
211219

212220
// If the `rules` key is not present at all, default it to the built-in set
213221
// so that the behavior is the same as if the configuration had been
@@ -238,6 +246,7 @@ public struct Configuration: Codable, Equatable {
238246
spacesAroundRangeFormationOperators, forKey: .spacesAroundRangeFormationOperators)
239247
try container.encode(fileScopedDeclarationPrivacy, forKey: .fileScopedDeclarationPrivacy)
240248
try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels)
249+
try container.encode(noAssignmentInExpressions, forKey: .noAssignmentInExpressions)
241250
try container.encode(rules, forKey: .rules)
242251
}
243252

@@ -287,3 +296,15 @@ public struct FileScopedDeclarationPrivacyConfiguration: Codable, Equatable {
287296
/// private access.
288297
public var accessLevel: AccessLevel = .private
289298
}
299+
300+
/// Configuration for the `NoAssignmentInExpressions` rule.
301+
public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable {
302+
/// A list of function names where assignments are allowed to be embedded in expressions that are
303+
/// passed as parameters to that function.
304+
public var allowedFunctions: [String] = [
305+
// Allow `XCTAssertNoThrow` because `XCTAssertNoThrow(x = try ...)` is clearer about intent than
306+
// `x = try XCTUnwrap(try? ...)` or force-unwrapped if you need to use the value `x` later on
307+
// in the test.
308+
"XCTAssertNoThrow"
309+
]
310+
}

Sources/SwiftFormatCore/LegacyTriviaBehavior.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ private final class LegacyTriviaBehaviorRewriter: SyntaxRewriter {
2020
token = token.with(\.leadingTrivia, pendingLeadingTrivia + token.leadingTrivia)
2121
self.pendingLeadingTrivia = nil
2222
}
23-
if token.nextToken != nil,
23+
if token.nextToken(viewMode: .sourceAccurate) != nil,
2424
let firstIndexToMove = token.trailingTrivia.firstIndex(where: shouldTriviaPieceBeMoved)
2525
{
2626
pendingLeadingTrivia = Trivia(pieces: Array(token.trailingTrivia[firstIndexToMove...]))

Sources/SwiftFormatCore/RuleMask.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
136136
// MARK: - Syntax Visitation Methods
137137

138138
override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
139-
guard let firstToken = node.firstToken else {
139+
guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else {
140140
return .visitChildren
141141
}
142142
let comments = loneLineComments(in: firstToken.leadingTrivia, isFirstToken: true)
@@ -159,14 +159,14 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
159159
}
160160

161161
override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind {
162-
guard let firstToken = node.firstToken else {
162+
guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else {
163163
return .visitChildren
164164
}
165165
return appendRuleStatusDirectives(from: firstToken, of: Syntax(node))
166166
}
167167

168168
override func visit(_ node: MemberDeclListItemSyntax) -> SyntaxVisitorContinueKind {
169-
guard let firstToken = node.firstToken else {
169+
guard let firstToken = node.firstToken(viewMode: .sourceAccurate) else {
170170
return .visitChildren
171171
}
172172
return appendRuleStatusDirectives(from: firstToken, of: Syntax(node))
@@ -183,7 +183,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
183183
private func appendRuleStatusDirectives(from token: TokenSyntax, of node: Syntax)
184184
-> SyntaxVisitorContinueKind
185185
{
186-
let isFirstInFile = token.previousToken == nil
186+
let isFirstInFile = token.previousToken(viewMode: .sourceAccurate) == nil
187187
let matches = loneLineComments(in: token.leadingTrivia, isFirstToken: isFirstInFile)
188188
.compactMap(ruleStatusDirectiveMatch)
189189
let sourceRange = node.sourceRange(converter: sourceLocationConverter)

Sources/SwiftFormatPrettyPrint/PrettyPrint.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,16 @@ public class PrettyPrinter {
129129
private var activeBreakSuppressionCount = 0
130130

131131
/// Whether breaks are supressed from firing. When true, no breaks should fire and the only way to
132-
/// move to a new line is an explicit new line token.
133-
private var isBreakingSupressed: Bool {
132+
/// move to a new line is an explicit new line token. Discretionary breaks aren't suppressed
133+
/// if ``allowSuppressedDiscretionaryBreaks`` is true.
134+
private var isBreakingSuppressed: Bool {
134135
return activeBreakSuppressionCount > 0
135136
}
136137

138+
/// Indicates whether discretionary breaks should still be included even if break suppression is
139+
/// enabled (see ``isBreakingSuppressed``).
140+
private var allowSuppressedDiscretionaryBreaks = false
141+
137142
/// The computed indentation level, as a number of spaces, based on the state of any unclosed
138143
/// delimiters and whether or not the current line is a continuation line.
139144
private var currentIndentation: [Indent] {
@@ -469,15 +474,15 @@ public class PrettyPrinter {
469474
case .soft(_, let discretionary):
470475
// A discretionary newline (i.e. from the source) should create a line break even if the
471476
// rules for breaking are disabled.
472-
overrideBreakingSuppressed = discretionary
477+
overrideBreakingSuppressed = discretionary && allowSuppressedDiscretionaryBreaks
473478
mustBreak = true
474479
case .hard:
475480
// A hard newline must always create a line break, regardless of the context.
476481
overrideBreakingSuppressed = true
477482
mustBreak = true
478483
}
479484

480-
let suppressBreaking = isBreakingSupressed && !overrideBreakingSuppressed
485+
let suppressBreaking = isBreakingSuppressed && !overrideBreakingSuppressed
481486
if !suppressBreaking && ((!isAtStartOfLine && length > spaceRemaining) || mustBreak) {
482487
currentLineIsContinuation = isContinuationIfBreakFires
483488
writeNewlines(newline)
@@ -527,8 +532,14 @@ public class PrettyPrinter {
527532

528533
case .printerControl(let kind):
529534
switch kind {
530-
case .disableBreaking:
535+
case .disableBreaking(let allowDiscretionary):
531536
activeBreakSuppressionCount += 1
537+
// Override the supression of discretionary breaks if we're at the top level or
538+
// discretionary breaks are currently allowed (false should override true, but not the other
539+
// way around).
540+
if activeBreakSuppressionCount == 1 || allowSuppressedDiscretionaryBreaks {
541+
allowSuppressedDiscretionaryBreaks = allowDiscretionary
542+
}
532543
case .enableBreaking:
533544
activeBreakSuppressionCount -= 1
534545
}

Sources/SwiftFormatPrettyPrint/Token.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,10 @@ enum PrinterControlKind {
162162
/// control token is encountered.
163163
///
164164
/// It's valid to nest `disableBreaking` and `enableBreaking` tokens. Breaks will be suppressed
165-
/// long as there is at least 1 unmatched disable token.
166-
case disableBreaking
165+
/// long as there is at least 1 unmatched disable token. If `allowDiscretionary` is `true`, then
166+
/// discretionary breaks aren't effected. An `allowDiscretionary` value of true never overrides a
167+
/// value of false. Hard breaks are always inserted no matter what.
168+
case disableBreaking(allowDiscretionary: Bool)
167169

168170
/// A signal that break tokens should be allowed to fire following this token, as long as there
169171
/// are no other unmatched disable tokens.

0 commit comments

Comments
 (0)