Skip to content

Commit 725aacf

Browse files
authored
Merge pull request #118 from dylansturg/merge_newline_tokens
Merge newline token into break token.
2 parents 684a3cd + 63e6d2d commit 725aacf

File tree

5 files changed

+267
-236
lines changed

5 files changed

+267
-236
lines changed

Sources/SwiftFormatPrettyPrint/PrettyPrint.swift

Lines changed: 41 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,6 @@ public class PrettyPrinter {
9191
/// Indicates whether or not the printer is currently at the beginning of a line.
9292
private var isAtStartOfLine = true
9393

94-
/// Indicates whether the kind of the last break was one that triggers a continuation line (i.e.,
95-
/// a `.continue`, an `.open(.continuation)`, or a `.close` break that causes
96-
/// `currentLineIsContinuation` to become true).
97-
private var wasLastBreakKindContinue = false
98-
9994
/// Tracks how many printer control tokens to suppress firing breaks are active.
10095
private var activeBreakSuppressionCount = 0
10196

@@ -159,8 +154,8 @@ public class PrettyPrinter {
159154
outputBuffer.append(String(str))
160155
}
161156

162-
/// Ensures that the given number of newlines to the output stream (taking into account any
163-
/// pre-existing consecutive newlines).
157+
/// Writes newlines into the output stream, taking into account any pre-existing consecutive
158+
/// newlines and the maximum allowed number of blank lines.
164159
///
165160
/// This function does some implicit collapsing of consecutive newlines to ensure that the
166161
/// results are consistent when breaks and explicit newlines coincide. For example, imagine a
@@ -171,28 +166,20 @@ public class PrettyPrinter {
171166
/// subtract the previously written newlines during the second call so that we end up with the
172167
/// correct number overall.
173168
///
174-
/// - Parameters:
175-
/// - count: The number of newlines to write.
176-
/// - kind: Indicates whether the newlines are flexible, discretionary, or mandatory newlines.
177-
/// Refer to the documentation of `NewlineKind` for details on how each of these are printed.
178-
private func writeNewlines(_ count: Int, kind: NewlineKind) {
179-
// We add 1 because it takes 2 newlines to create a blank line.
169+
/// - Parameter newlines: The number and type of newlines to write.
170+
private func writeNewlines(_ newlines: NewlineBehavior) {
180171
let numberToPrint: Int
181-
if kind == .mandatory {
172+
switch newlines {
173+
case .elective:
174+
numberToPrint = consecutiveNewlineCount == 0 ? 1 : 0
175+
case .soft(let count, _):
176+
// We add 1 to the max blank lines because it takes 2 newlines to create the first blank line.
177+
numberToPrint = min(count - consecutiveNewlineCount, configuration.maximumBlankLines + 1)
178+
case .hard(let count):
182179
numberToPrint = count
183-
} else {
184-
let maximumNewlines = configuration.maximumBlankLines + 1
185-
if count <= maximumNewlines {
186-
numberToPrint = count - consecutiveNewlineCount
187-
} else {
188-
numberToPrint = maximumNewlines - consecutiveNewlineCount
189-
}
190-
191-
guard (kind == .discretionary && numberToPrint > 0) || consecutiveNewlineCount == 0 else {
192-
return
193-
}
194180
}
195181

182+
guard numberToPrint > 0 else { return }
196183
writeRaw(String(repeating: "\n", count: numberToPrint))
197184
lineNumber += numberToPrint
198185
isAtStartOfLine = true
@@ -259,8 +246,7 @@ public class PrettyPrinter {
259246

260247
// Create a line break if needed. Calculate the indentation required and adjust spaceRemaining
261248
// accordingly.
262-
case .break(let kind, let size, _):
263-
wasLastBreakKindContinue = false
249+
case .break(let kind, let size, let newline):
264250
var mustBreak = forceBreakStack.last ?? false
265251

266252
// Tracks whether the current line should be considered a continuation line, *if and only if
@@ -304,12 +290,6 @@ public class PrettyPrinter {
304290

305291
continuationStack.append(currentLineIsContinuation)
306292

307-
// If the open break kind is a continuation and it fired, then we don't want to set this
308-
// flag because the active open break will provide the continuation indentation for the
309-
// remaining lines. If the break doesn't fire, we need to set it so that the remaining lines
310-
// get the appropriate indentation.
311-
wasLastBreakKindContinue = openKind == .continuation && !continuationBreakWillFire
312-
313293
// Once we've reached an open break and preserved the continuation state, the "scope" we now
314294
// enter is *not* a continuation scope. If it was one, we'll re-enter it when we reach the
315295
// corresponding close.
@@ -382,11 +362,9 @@ public class PrettyPrinter {
382362

383363
// Restore the continuation state of the scope we were in before the open break occurred.
384364
currentLineIsContinuation = currentLineIsContinuation || wasContinuationWhenOpened
385-
wasLastBreakKindContinue = wasContinuationWhenOpened
386365
isContinuationIfBreakFires = wasContinuationWhenOpened
387366

388367
case .continue:
389-
wasLastBreakKindContinue = true
390368
isContinuationIfBreakFires = true
391369

392370
case .same:
@@ -396,9 +374,24 @@ public class PrettyPrinter {
396374
mustBreak = currentLineIsContinuation
397375
}
398376

399-
if !isBreakingSupressed && ((!isAtStartOfLine && length > spaceRemaining) || mustBreak) {
377+
var overrideBreakingSuppressed = false
378+
switch newline {
379+
case .elective: break
380+
case .soft(_, let discretionary):
381+
// A discretionary newline (i.e. from the source) should create a line break even if the
382+
// rules for breaking are disabled.
383+
overrideBreakingSuppressed = discretionary
384+
mustBreak = true
385+
case .hard:
386+
// A hard newline must always create a line break, regardless of the context.
387+
overrideBreakingSuppressed = true
388+
mustBreak = true
389+
}
390+
391+
let suppressBreaking = isBreakingSupressed && !overrideBreakingSuppressed
392+
if !suppressBreaking && ((!isAtStartOfLine && length > spaceRemaining) || mustBreak) {
400393
currentLineIsContinuation = isContinuationIfBreakFires
401-
writeNewlines(1, kind: .flexible)
394+
writeNewlines(newline)
402395
lastBreak = true
403396
} else {
404397
if isAtStartOfLine {
@@ -417,24 +410,6 @@ public class PrettyPrinter {
417410
case .space(let size, _):
418411
enqueueSpaces(size)
419412

420-
// Apply `count` line breaks, calculate the indentation required, and adjust spaceRemaining.
421-
case .newlines(let count, let kind):
422-
// If a newline immediately followed an open-continue break, then this is effectively the
423-
// same as if it had fired. Activate it, and reset the last-break-kind flag so that the
424-
// indentation of subsequent lines is contributed by that break and not by inherited
425-
// continuation state.
426-
if let lastActiveOpenBreak = activeOpenBreaks.last,
427-
lastActiveOpenBreak.index == idx - 1,
428-
lastActiveOpenBreak.kind == .continuation
429-
{
430-
activeOpenBreaks[activeOpenBreaks.count - 1].contributesContinuationIndent = true
431-
wasLastBreakKindContinue = false
432-
}
433-
434-
currentLineIsContinuation = wasLastBreakKindContinue
435-
writeNewlines(count, kind: kind)
436-
lastBreak = true
437-
438413
// Print any indentation required, followed by the text content of the syntax token.
439414
case .syntax(let text):
440415
guard !text.isEmpty else { break }
@@ -515,42 +490,27 @@ public class PrettyPrinter {
515490

516491
// Break lengths are equal to its size plus the token or group following it. Calculate the
517492
// length of any prior break tokens.
518-
case .break(_, let size, _):
493+
case .break(_, let size, let newline):
519494
if let index = delimIndexStack.last, case .break = tokens[index] {
520495
lengths[index] += total
521496
delimIndexStack.removeLast()
522497
}
523-
524498
lengths.append(-total)
525499
delimIndexStack.append(i)
526-
total += size
500+
501+
if case .elective = newline {
502+
total += size
503+
} else {
504+
// `size` is never used in this case, because the break always fires. Use `maxLineLength`
505+
// to ensure enclosing groups are large enough to force preceding breaks to fire.
506+
total += maxLineLength
507+
}
527508

528509
// Space tokens have a length equal to its size.
529510
case .space(let size, _):
530511
lengths.append(size)
531512
total += size
532513

533-
// The length of newlines are equal to the maximum allowed line length. Calculate the length
534-
// of any prior break tokens.
535-
case .newlines:
536-
if let index = delimIndexStack.last, case .break = tokens[index] {
537-
if index == i - 1 {
538-
// A break immediately preceding a newline should have a length of zero, so that it
539-
// doesn't fire.
540-
lengths[index] = 0
541-
} else {
542-
lengths[index] += total
543-
}
544-
delimIndexStack.removeLast()
545-
}
546-
547-
// Since newlines must always cause a line-break, we set their length as the full allowed
548-
// width of the line. This causes any enclosing groups to have a length exceeding the line
549-
// limit, and so the group must break and indent. e.g. single-line versus multi-line
550-
// function bodies.
551-
lengths.append(maxLineLength)
552-
total += maxLineLength
553-
554514
// Syntax tokens have a length equal to the number of columns needed to print its contents.
555515
case .syntax(let text):
556516
lengths.append(text.count)
@@ -615,11 +575,9 @@ public class PrettyPrinter {
615575
printDebugIndent()
616576
print("[SYNTAX \"\(syntax)\" Length: \(length) Idx: \(idx)]")
617577

618-
case .break(let kind, let size, let ignoresDiscretionary):
578+
case .break(let kind, let size, let newline):
619579
printDebugIndent()
620-
print(
621-
"[BREAK Kind: \(kind) Size: \(size) Length: \(length) "
622-
+ "Ignores Discretionary NL: \(ignoresDiscretionary) Idx: \(idx)]")
580+
print("[BREAK Kind: \(kind) Size: \(size) Length: \(length) NL: \(newline) Idx: \(idx)]")
623581

624582
case .open(let breakstyle):
625583
printDebugIndent()
@@ -636,10 +594,6 @@ public class PrettyPrinter {
636594
printDebugIndent()
637595
print("[CLOSE Idx: \(idx)]")
638596

639-
case .newlines(let N, let required):
640-
printDebugIndent()
641-
print("[NEWLINES N: \(N) Required: \(required) Length: \(length) Idx: \(idx)]")
642-
643597
case .space(let size, let flexible):
644598
printDebugIndent()
645599
print("[SPACE Size: \(size) Flexible: \(flexible) Length: \(length) Idx: \(idx)]")

Sources/SwiftFormatPrettyPrint/Token.swift

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -118,27 +118,32 @@ enum BreakKind: Equatable {
118118
static let open = BreakKind.open(kind: .block)
119119
}
120120

121-
enum NewlineKind {
122-
/// A newline that has been inserted by the formatter independent of the source code given by the
123-
/// user (for example, between the getter and setter blocks of a computed property).
124-
///
125-
/// Flexible newlines are only printed if a discretionary or mandatory newline has not yet been
126-
/// printed at the same location, and only up to the maximum allowed by the formatter
127-
/// configuration.
128-
case flexible
129-
130-
/// A newline that was present in the source code given by the user (that is, at the user's
131-
/// discretion).
132-
///
133-
/// Discretionary newlines are printed after excluding any other consecutive newlines printed thus
134-
/// far at the same location, and only up to the maximum allowed by the formatter configuration.
135-
case discretionary
136-
137-
/// A mandatory newline that must always be printed (for example, in a multiline string literal).
138-
///
139-
/// Mandatory newlines are never omitted by the pretty printer, even if it would result in a
140-
/// number of consecutive newlines that exceeds that allowed by the formatter configuration.
141-
case mandatory
121+
/// Behaviors for creating newlines as part of a break, i.e. where breaking onto a newline is
122+
/// allowed.
123+
enum NewlineBehavior {
124+
/// Breaking onto a newline is allowed if necessary, but is not required. `ignoresDiscretionary`
125+
/// specifies whether a user-entered discretionary newline should be respected.
126+
case elective(ignoresDiscretionary: Bool)
127+
128+
/// Breaking onto a newline `count` times is required, unless it would create more blank lines
129+
/// than are allowed by the current configuration. Any blank lines over the configured limit are
130+
/// discarded. `discretionary` tracks whether these newlines were created based on user-entered
131+
/// discretionary newlines, from the source, or were inserted by the formatter.
132+
case soft(count: Int, discretionary: Bool)
133+
134+
/// Breaking onto a newline `count` times is required and any limits on blank lines are
135+
/// **ignored**. Exactly `count` newlines are always printed, regardless of existing consecutive
136+
/// newlines and the configured maximum number of blank lines.
137+
case hard(count: Int)
138+
139+
/// An elective newline that respects discretionary newlines from the user-entered text.
140+
static let elective = NewlineBehavior.elective(ignoresDiscretionary: false)
141+
142+
/// A single soft newline that is created by the formatter, i.e. *not* discretionary.
143+
static let soft = NewlineBehavior.soft(count: 1, discretionary: false)
144+
145+
/// A single hard newline.
146+
static let hard = NewlineBehavior.hard(count: 1)
142147
}
143148

144149
/// Kinds of printer control tokens that can be used to customize the pretty printer's behavior in
@@ -160,9 +165,8 @@ enum Token {
160165
case syntax(String)
161166
case open(GroupBreakStyle)
162167
case close
163-
case `break`(BreakKind, size: Int, ignoresDiscretionary: Bool)
168+
case `break`(BreakKind, size: Int, newlines: NewlineBehavior)
164169
case space(size: Int, flexible: Bool)
165-
case newlines(Int, kind: NewlineKind)
166170
case comment(Comment, wasEndOfLine: Bool)
167171
case verbatim(Verbatim)
168172
case printerControl(kind: PrinterControlKind)
@@ -174,24 +178,20 @@ enum Token {
174178
return Token.open(breakStyle)
175179
}
176180

177-
/// A single, flexible newline.
178-
static let newline = Token.newlines(1, kind: .flexible)
179-
180-
/// Returns a single newline with the given kind.
181-
static func newline(kind: NewlineKind) -> Token {
182-
return Token.newlines(1, kind: kind)
183-
}
184-
185181
static let space = Token.space(size: 1, flexible: false)
186182

187183
static func space(size: Int) -> Token {
188184
return .space(size: size, flexible: false)
189185
}
190186

191-
static let `break` = Token.break(.continue, size: 1, ignoresDiscretionary: false)
187+
static let `break` = Token.break(.continue, size: 1, newlines: .elective)
192188

193189
static func `break`(_ kind: BreakKind, size: Int = 1) -> Token {
194-
return .break(kind, size: size, ignoresDiscretionary: false)
190+
return .break(kind, size: size, newlines: .elective)
191+
}
192+
193+
static func `break`(_ kind: BreakKind, newlines: NewlineBehavior) -> Token {
194+
return .break(kind, size: 1, newlines: newlines)
195195
}
196196

197197
static func verbatim(text: String) -> Token {

0 commit comments

Comments
 (0)