Skip to content

Commit b00a40e

Browse files
committed
Default to separating tokens by a space in BasicFormat
Instead of having to opt into separating tokens by spaces, assume that all tokens need to be separated by spaces and explicitly opt-out of space separtion of tokens.
1 parent 00cbee2 commit b00a40e

18 files changed

+280
-344
lines changed

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftbasicformat/BasicFormatExtensionsFile.swift

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -76,62 +76,6 @@ let basicFormatExtensionsFile = SourceFileSyntax(leadingTrivia: copyrightHeader)
7676
}
7777
"""
7878
)
79-
80-
try VariableDeclSyntax("var requiresLeadingSpace: Bool") {
81-
StmtSyntax(
82-
"""
83-
if let keyPath = keyPathInParent, let requiresLeadingSpace = keyPath.requiresLeadingSpace {
84-
return requiresLeadingSpace
85-
}
86-
"""
87-
)
88-
89-
try SwitchExprSyntax("switch tokenKind") {
90-
for token in SYNTAX_TOKENS {
91-
if token.requiresLeadingSpace {
92-
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
93-
StmtSyntax("return true")
94-
}
95-
}
96-
}
97-
for keyword in KEYWORDS where keyword.requiresLeadingSpace {
98-
SwitchCaseSyntax("case .keyword(.\(raw: keyword.escapedName)):") {
99-
StmtSyntax("return true")
100-
}
101-
}
102-
SwitchCaseSyntax("default:") {
103-
StmtSyntax("return false")
104-
}
105-
}
106-
}
107-
108-
try VariableDeclSyntax("var requiresTrailingSpace: Bool") {
109-
StmtSyntax(
110-
"""
111-
if let keyPath = keyPathInParent, let requiresTrailingSpace = keyPath.requiresTrailingSpace {
112-
return requiresTrailingSpace
113-
}
114-
"""
115-
)
116-
117-
try SwitchExprSyntax("switch tokenKind") {
118-
for token in SYNTAX_TOKENS {
119-
if token.requiresTrailingSpace {
120-
SwitchCaseSyntax("case .\(raw: token.swiftKind):") {
121-
StmtSyntax("return true")
122-
}
123-
}
124-
}
125-
for keyword in KEYWORDS where keyword.requiresTrailingSpace {
126-
SwitchCaseSyntax("case .keyword(.\(raw: keyword.escapedName)):") {
127-
StmtSyntax("return true")
128-
}
129-
}
130-
SwitchCaseSyntax("default:") {
131-
StmtSyntax("return false")
132-
}
133-
}
134-
}
13579
}
13680

13781
try! ExtensionDeclSyntax("fileprivate extension AnyKeyPath") {

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,28 @@ open class BasicFormat: SyntaxRewriter {
3535
/// This is used as a reference-point to indent user-indented code.
3636
private var anchorPoints: [TokenSyntax: Trivia] = [:]
3737

38+
public let viewMode: SyntaxTreeViewMode
39+
3840
/// The previously visited token. This is faster than accessing
3941
/// `token.previousToken` inside `visit(_:TokenSyntax)`. `nil` if no token has
4042
/// been visited yet.
4143
private var previousToken: TokenSyntax? = nil
4244

43-
public init(indentationWidth: Trivia = .spaces(4), initialIndentation: Trivia = []) {
45+
public init(
46+
indentationWidth: Trivia = .spaces(4),
47+
initialIndentation: Trivia = [],
48+
viewMode: SyntaxTreeViewMode = .sourceAccurate
49+
) {
4450
self.indentationWidth = indentationWidth
4551
self.indentationStack = [(indentation: initialIndentation, isUserDefined: false)]
52+
self.viewMode = viewMode
4653
}
4754

4855
// MARK: - Updating indentation level
4956

5057
public func increaseIndentationLevel(to userDefinedIndentation: Trivia? = nil) {
5158
if let userDefinedIndentation = userDefinedIndentation {
52-
indentationStack.append((indentation: userDefinedIndentation, isUserDefined: false))
59+
indentationStack.append((indentation: userDefinedIndentation, isUserDefined: true))
5360
} else {
5461
indentationStack.append((indentation: currentIndentationLevel + indentationWidth, isUserDefined: false))
5562
}
@@ -65,7 +72,7 @@ open class BasicFormat: SyntaxRewriter {
6572

6673
open override func visitPre(_ node: Syntax) {
6774
if requiresIndent(node) {
68-
if let firstToken = node.firstToken(viewMode: .sourceAccurate),
75+
if let firstToken = node.firstToken(viewMode: viewMode),
6976
let tokenIndentation = firstToken.leadingTrivia.indentation(isOnNewline: false),
7077
!tokenIndentation.isEmpty,
7178
let lastNonUserDefinedIndentation = indentationStack.last(where: { !$0.isUserDefined })?.indentation
@@ -103,6 +110,8 @@ open class BasicFormat: SyntaxRewriter {
103110
return true
104111
case .codeBlockItemList:
105112
return true
113+
case .ifConfigClauseList:
114+
return true
106115
case .memberDeclList:
107116
return true
108117
case .switchCaseList:
@@ -118,7 +127,7 @@ open class BasicFormat: SyntaxRewriter {
118127
var ancestor: Syntax = Syntax(token)
119128
while let parent = ancestor.parent {
120129
ancestor = parent
121-
if let firstToken = parent.firstToken(viewMode: .sourceAccurate),
130+
if let firstToken = parent.firstToken(viewMode: viewMode),
122131
let anchorPointIndentation = anchorPoints[firstToken]
123132
{
124133
return anchorPointIndentation
@@ -148,41 +157,113 @@ open class BasicFormat: SyntaxRewriter {
148157
var ancestor: Syntax = Syntax(token)
149158
while let parent = ancestor.parent {
150159
ancestor = parent
151-
if ancestor.position != token.position {
160+
if ancestor.position != token.position || ancestor.firstToken(viewMode: viewMode) != token {
152161
break
153162
}
154163
if let ancestorsParent = ancestor.parent, childrenSeparatedByNewline(ancestorsParent) {
155164
return true
156165
}
166+
switch ancestor.keyPathInParent {
167+
case \IfConfigClauseSyntax.elements:
168+
return true
169+
default:
170+
break
171+
}
157172
}
158173

159174
return false
160175
}
161176

162177
open func requiresWhitespace(between first: TokenSyntax?, and second: TokenSyntax?) -> Bool {
163178
switch (first?.tokenKind, second?.tokenKind) {
164-
case (.leftParen, .leftBrace), // Ensures there is not a space in `.map({ $0.foo })`
165-
(.exclamationMark, .leftParen), // Ensures there is not a space in `myOptionalClosure!()`
166-
(.exclamationMark, .period), // Ensures there is not a space in `myOptionalBar!.foo()`
167-
(.keyword(.as), .exclamationMark), // Ensures there is not a space in `as!`
168-
(.keyword(.as), .postfixQuestionMark), // Ensures there is not a space in `as?`
169-
(.keyword(.try), .exclamationMark), // Ensures there is not a space in `try!`
170-
(.keyword(.try), .postfixQuestionMark), // Ensures there is not a space in `try?`:
171-
(.postfixQuestionMark, .leftParen), // Ensures there is not a space in `init?()` or `myOptionalClosure?()`s
172-
(.postfixQuestionMark, .rightAngle), // Ensures there is not a space in `ContiguousArray<RawSyntax?>`
173-
(.postfixQuestionMark, .rightParen): // Ensures there is not a space in `myOptionalClosure?()`
179+
case (.atSign, _),
180+
(.backslash, _),
181+
(.backtick, _),
182+
(.dollarIdentifier, .period), // a.b
183+
(.eof, _),
184+
(.exclamationMark, .leftParen), // myOptionalClosure!()
185+
(.exclamationMark, .period), // myOptionalBar!.foo()
186+
(.extendedRegexDelimiter, .leftParen), // opening extended regex delimiter should never be separate by a space
187+
(.extendedRegexDelimiter, .regexSlash), // opening extended regex delimiter should never be separate by a space
188+
(.identifier, .leftAngle), // MyType<Int>
189+
(.identifier, .leftParen), // foo()
190+
(.identifier, .leftSquareBracket), // myArray[1]
191+
(.identifier, .period), // a.b
192+
(.integerLiteral, .period), // macOS 11.2.1
193+
(.keyword(.`init`), .leftAngle), // init<T>()
194+
(.keyword(.`init`), .leftParen), // init()
195+
(.keyword(.self), .period), // self.someProperty
196+
(.keyword(.Self), .period), // self.someProperty
197+
(.keyword(.set), .leftParen), // var mYar: Int { set(value) {} }
198+
(.keyword(.subscript), .leftParen), // subscript(x: Int)
199+
(.keyword(.super), .period), // super.someProperty
200+
(.leftBrace, _),
201+
(.leftParen, _),
202+
(.leftSquareBracket, _),
203+
(.multilineStringQuote, .rawStringDelimiter), // closing raw string delimiter should never be separate by a space
204+
(.period, _),
205+
(.postfixQuestionMark, .leftAngle), // init?<T>()
206+
(.postfixQuestionMark, .leftParen), // init?() or myOptionalClosure?()
207+
(.postfixQuestionMark, .period), // someOptional?.someProperty
208+
(.pound, _),
209+
(.poundUnavailableKeyword, .leftParen), // #unavailable(...)
210+
(.prefixAmpersand, _),
211+
(.prefixOperator, _),
212+
(.rawStringDelimiter, .leftParen), // opening raw string delimiter should never be separate by a space
213+
(.rawStringDelimiter, .multilineStringQuote), // opening raw string delimiter should never be separate by a space
214+
(.rawStringDelimiter, .singleQuote), // opening raw string delimiter should never be separate by a space
215+
(.rawStringDelimiter, .stringQuote), // opening raw string delimiter should never be separate by a space
216+
(.regexLiteralPattern, _),
217+
(.regexSlash, .extendedRegexDelimiter), // closing extended regex delimiter should never be separate by a space
218+
(.rightAngle, .leftParen), // func foo<T>(x: T)
219+
(.rightParen, .leftParen), // returnsClosure()()
220+
(.rightParen, .period), // foo().bar
221+
(.rightSquareBracket, .period), // myArray[1].someProperty
222+
(.singleQuote, .rawStringDelimiter), // closing raw string delimiter should never be separate by a space
223+
(.stringQuote, .rawStringDelimiter), // closing raw string delimiter should never be separate by a space
224+
(.stringSegment, _),
225+
(_, .colon),
226+
(_, .comma),
227+
(_, .ellipsis),
228+
(_, .eof),
229+
(_, .exclamationMark),
230+
(_, .postfixOperator),
231+
(_, .postfixQuestionMark),
232+
(_, .rightBrace),
233+
(_, .rightParen),
234+
(_, .rightSquareBracket),
235+
(_, .semicolon),
236+
(_, nil),
237+
(nil, _):
238+
return false
239+
case (.leftAngle, _) where second?.tokenKind != .rightAngle: // `<` and `>` need to be separated by a space because otherwise they become an operator
240+
return false
241+
case (_, .rightAngle) where first?.tokenKind != .leftAngle: // `<` and `>` need to be separated by a space because otherwise they become an operator
174242
return false
175243
default:
176244
break
177245
}
178246

179-
if first?.requiresTrailingSpace ?? false {
180-
return true
181-
}
182-
if second?.requiresLeadingSpace ?? false {
183-
return true
247+
switch first?.keyPathInParent {
248+
case \ExpressionSegmentSyntax.backslash,
249+
\ExpressionSegmentSyntax.rightParen,
250+
\DeclNameArgumentSyntax.colon,
251+
\StringLiteralExprSyntax.openQuote,
252+
\RegexLiteralExprSyntax.openSlash:
253+
return false
254+
default:
255+
break
184256
}
185-
return false
257+
258+
return true
259+
}
260+
261+
/// Whether the formatter should consider this token as being mutable.
262+
/// This allows the diagnostic generator to only assume that missing nodes
263+
/// will be mutated. Thus, if two tokens need to be separated by a space, it
264+
/// will not be assumed that the space is added to an immutable previous node.
265+
open func isMutable(_ token: TokenSyntax) -> Bool {
266+
return true
186267
}
187268

188269
// MARK: - Formatting a token
@@ -191,15 +272,15 @@ open class BasicFormat: SyntaxRewriter {
191272
defer {
192273
self.previousToken = token
193274
}
194-
let previousToken = self.previousToken ?? token.previousToken(viewMode: .sourceAccurate)
195-
let nextToken = token.nextToken(viewMode: .sourceAccurate)
275+
let previousToken = self.previousToken ?? token.previousToken(viewMode: viewMode)
276+
let nextToken = token.nextToken(viewMode: viewMode)
196277

197278
lazy var previousTokenWillEndWithWhitespace: Bool = {
198279
guard let previousToken = previousToken else {
199280
return false
200281
}
201282
return previousToken.trailingTrivia.endsWithWhitespace
202-
|| requiresWhitespace(between: previousToken, and: token)
283+
|| (requiresWhitespace(between: previousToken, and: token) && isMutable(previousToken))
203284
}()
204285

205286
lazy var previousTokenWillEndWithNewline: Bool = {
@@ -234,7 +315,7 @@ open class BasicFormat: SyntaxRewriter {
234315
return false
235316
}
236317
return nextToken.leadingTrivia.startsWithNewline
237-
|| requiresLeadingNewline(nextToken)
318+
|| (requiresLeadingNewline(nextToken) && isMutable(nextToken))
238319
}()
239320

240321
/// This token's trailing trivia + any spaces or tabs at the start of the

0 commit comments

Comments
 (0)