Skip to content

Commit bb70bc2

Browse files
authored
Merge pull request #646 from allevato/new-trivia-behavior
Remove the legacy trivia workaround.
2 parents 8707af0 + 2556e08 commit bb70bc2

17 files changed

+439
-203
lines changed

Sources/SwiftFormat/Core/LegacyTriviaBehavior.swift

-44
This file was deleted.

Sources/SwiftFormat/Core/Parsing.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ func parseAndEmitDiagnostics(
6363
guard !hasErrors else {
6464
throw SwiftFormatError.fileContainsInvalidSyntax
6565
}
66-
67-
return restoringLegacyTriviaBehavior(sourceFile)
66+
return sourceFile
6867
}
6968

7069
// Wraps a `DiagnosticMessage` but forces its severity to be that of a warning instead of an error.

Sources/SwiftFormat/Core/Rule.swift

+35-12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ public protocol Rule {
2929
init(context: Context)
3030
}
3131

32+
/// The part of a node where an emitted finding should be anchored.
33+
@_spi(Rules)
34+
public enum FindingAnchor {
35+
/// The finding is anchored at the beginning of the node's actual content, skipping any leading
36+
/// trivia.
37+
case start
38+
39+
/// The finding is anchored at the beginning of the trivia piece at the given index in the node's
40+
/// leading trivia.
41+
case leadingTrivia(Trivia.Index)
42+
43+
/// The finding is anchored at the beginning of the trivia piece at the given index in the node's
44+
/// trailing trivia.
45+
case trailingTrivia(Trivia.Index)
46+
}
47+
3248
extension Rule {
3349
/// By default, the `ruleName` is just the name of the implementing rule class.
3450
public static var ruleName: String { String("\(self)".split(separator: ".").last!) }
@@ -40,30 +56,37 @@ extension Rule {
4056
/// - node: The syntax node to which the finding should be attached. The finding's location will
4157
/// be set to the start of the node (excluding leading trivia, unless `leadingTriviaIndex` is
4258
/// provided).
43-
/// - leadingTriviaIndex: If non-nil, the index of a trivia piece in the node's leading trivia
44-
/// that should be used to determine the location of the finding. Otherwise, the finding's
45-
/// location will be the start of the node after any leading trivia.
59+
/// - anchor: The part of the node where the finding should be anchored. Defaults to the start
60+
/// of the node's content (after any leading trivia).
4661
/// - notes: An array of notes that provide additional detail about the finding.
4762
public func diagnose<SyntaxType: SyntaxProtocol>(
4863
_ message: Finding.Message,
4964
on node: SyntaxType?,
5065
severity: Finding.Severity? = nil,
51-
leadingTriviaIndex: Trivia.Index? = nil,
66+
anchor: FindingAnchor = .start,
5267
notes: [Finding.Note] = []
5368
) {
5469
let syntaxLocation: SourceLocation?
55-
if let leadingTriviaIndex = leadingTriviaIndex {
56-
syntaxLocation = node?.startLocation(
57-
ofLeadingTriviaAt: leadingTriviaIndex, converter: context.sourceLocationConverter)
70+
if let node = node {
71+
switch anchor {
72+
case .start:
73+
syntaxLocation = node.startLocation(converter: context.sourceLocationConverter)
74+
case .leadingTrivia(let index):
75+
syntaxLocation = node.startLocation(
76+
ofLeadingTriviaAt: index, converter: context.sourceLocationConverter)
77+
case .trailingTrivia(let index):
78+
syntaxLocation = node.startLocation(
79+
ofTrailingTriviaAt: index, converter: context.sourceLocationConverter)
80+
}
5881
} else {
59-
syntaxLocation = node?.startLocation(converter: context.sourceLocationConverter)
82+
syntaxLocation = nil
6083
}
6184

6285
let category = RuleBasedFindingCategory(ruleType: type(of: self), severity: severity)
6386
context.findingEmitter.emit(
64-
message,
65-
category: category,
66-
location: syntaxLocation.flatMap(Finding.Location.init),
67-
notes: notes)
87+
message,
88+
category: category,
89+
location: syntaxLocation.flatMap(Finding.Location.init),
90+
notes: notes)
6891
}
6992
}

Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift

+93
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ extension SyntaxProtocol {
3636
return self.position + offset
3737
}
3838

39+
/// Returns the absolute position of the trivia piece at the given index in the receiver's
40+
/// trailing trivia collection.
41+
///
42+
/// If the trivia piece spans multiple characters, the value returned is the position of the first
43+
/// character.
44+
///
45+
/// - Precondition: `index` is a valid index in the receiver's trailing trivia collection.
46+
///
47+
/// - Parameter index: The index of the trivia piece in the trailing trivia whose position should
48+
/// be returned.
49+
/// - Returns: The absolute position of the trivia piece.
50+
func position(ofTrailingTriviaAt index: Trivia.Index) -> AbsolutePosition {
51+
guard trailingTrivia.indices.contains(index) else {
52+
preconditionFailure("Index was out of bounds in the node's trailing trivia.")
53+
}
54+
55+
var offset = SourceLength.zero
56+
for currentIndex in trailingTrivia.startIndex..<index {
57+
offset += trailingTrivia[currentIndex].sourceLength
58+
}
59+
return self.endPositionBeforeTrailingTrivia + offset
60+
}
61+
3962
/// Returns the source location of the trivia piece at the given index in the receiver's leading
4063
/// trivia collection.
4164
///
@@ -56,6 +79,76 @@ extension SyntaxProtocol {
5679
) -> SourceLocation {
5780
return converter.location(for: position(ofLeadingTriviaAt: index))
5881
}
82+
83+
/// Returns the source location of the trivia piece at the given index in the receiver's trailing
84+
/// trivia collection.
85+
///
86+
/// If the trivia piece spans multiple characters, the value returned is the location of the first
87+
/// character.
88+
///
89+
/// - Precondition: `index` is a valid index in the receiver's trailing trivia collection.
90+
///
91+
/// - Parameters:
92+
/// - index: The index of the trivia piece in the trailing trivia whose location should be
93+
/// returned.
94+
/// - converter: The `SourceLocationConverter` that was previously initialized using the root
95+
/// tree of this node.
96+
/// - Returns: The source location of the trivia piece.
97+
func startLocation(
98+
ofTrailingTriviaAt index: Trivia.Index,
99+
converter: SourceLocationConverter
100+
) -> SourceLocation {
101+
return converter.location(for: position(ofTrailingTriviaAt: index))
102+
}
103+
104+
/// The collection of all contiguous trivia preceding this node; that is, the trailing trivia of
105+
/// the node before it and the leading trivia of the node itself.
106+
var allPrecedingTrivia: Trivia {
107+
var result: Trivia
108+
if let previousTrailingTrivia = previousToken(viewMode: .sourceAccurate)?.trailingTrivia {
109+
result = previousTrailingTrivia
110+
} else {
111+
result = Trivia()
112+
}
113+
result += leadingTrivia
114+
return result
115+
}
116+
117+
/// The collection of all contiguous trivia following this node; that is, the trailing trivia of
118+
/// the node and the leading trivia of the node after it.
119+
var allFollowingTrivia: Trivia {
120+
var result = trailingTrivia
121+
if let nextLeadingTrivia = nextToken(viewMode: .sourceAccurate)?.leadingTrivia {
122+
result += nextLeadingTrivia
123+
}
124+
return result
125+
}
126+
127+
/// Indicates whether the node has any preceding line comments.
128+
///
129+
/// Due to the way trivia is parsed, a preceding comment might be in either the leading trivia of
130+
/// the node or the trailing trivia of the previous token.
131+
var hasPrecedingLineComment: Bool {
132+
if let previousTrailingTrivia = previousToken(viewMode: .sourceAccurate)?.trailingTrivia,
133+
previousTrailingTrivia.hasLineComment
134+
{
135+
return true
136+
}
137+
return leadingTrivia.hasLineComment
138+
}
139+
140+
/// Indicates whether the node has any preceding comments of any kind.
141+
///
142+
/// Due to the way trivia is parsed, a preceding comment might be in either the leading trivia of
143+
/// the node or the trailing trivia of the previous token.
144+
var hasAnyPrecedingComment: Bool {
145+
if let previousTrailingTrivia = previousToken(viewMode: .sourceAccurate)?.trailingTrivia,
146+
previousTrailingTrivia.hasAnyComments
147+
{
148+
return true
149+
}
150+
return leadingTrivia.hasAnyComments
151+
}
59152
}
60153

61154
extension SyntaxCollection {

Sources/SwiftFormat/Core/Trivia+Convenience.swift

+8-6
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ extension Trivia {
3434

3535
/// Returns this set of trivia, without any leading spaces.
3636
func withoutLeadingSpaces() -> Trivia {
37-
return Trivia(
38-
pieces: Array(drop {
39-
if case .spaces = $0 { return false }
40-
if case .tabs = $0 { return false }
41-
return true
42-
}))
37+
return Trivia(pieces: self.pieces.drop(while: \.isSpaceOrTab))
38+
}
39+
40+
func withoutTrailingSpaces() -> Trivia {
41+
guard let lastNonSpaceIndex = self.pieces.lastIndex(where: \.isSpaceOrTab) else {
42+
return self
43+
}
44+
return Trivia(pieces: self[..<lastNonSpaceIndex])
4345
}
4446

4547
/// Returns this trivia, excluding the last newline and anything following it.

0 commit comments

Comments
 (0)