Files
sourcekit-lsp/Sources/SwiftLanguageService/IndentationRemover.swift
Karan Lokchandani 55d75954e5 Add if let to guard transform (#2420)
fixes: https://github.com/swiftlang/sourcekit-lsp/issues/1569

mostly works but not sure what to do with many edge cases and has a todo
for switch statements, also this will probably have conflicts with
https://github.com/swiftlang/sourcekit-lsp/pull/2406 marking as draft
till that merges and i can resolve the conflicts.


https://github.com/user-attachments/assets/a6d07f9d-6f09-4330-8cd0-2d24bd6973fb

---------

Signed-off-by: Karan <karanlokchandani@protonmail.com>
2026-01-13 22:46:54 +01:00

97 lines
3.2 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
@_spi(RawSyntax) @_spi(SwiftSyntax) import SwiftSyntax
/// SyntaxRewriter that removes indentation for lines starting with a newline.
class IndentationRemover: SyntaxRewriter {
private let indentation: [TriviaPiece]
private var shouldUnindent: Bool
init(indentation: Trivia, indentFirstLine: Bool = false) {
self.indentation = indentation.decomposed.pieces
self.shouldUnindent = indentFirstLine
super.init(viewMode: .sourceAccurate)
}
private func unindentAfterNewlines(_ content: String, unindentFirstLine: Bool = false) -> String {
let lines = content.components(separatedBy: .newlines)
var result: [String] = []
if let first = lines.first {
if unindentFirstLine && first.hasPrefix(Trivia(pieces: indentation).description) {
result.append(String(first.dropFirst(Trivia(pieces: indentation).description.count)))
} else {
result.append(first)
}
}
let pattern = Trivia(pieces: indentation).description
for line in lines.dropFirst() {
if line.hasPrefix(pattern) {
result.append(String(line.dropFirst(pattern.count)))
} else {
result.append(line)
}
}
return result.joined(separator: "\n")
}
private func unindent(_ trivia: Trivia) -> Trivia {
var result: [TriviaPiece] = []
result.reserveCapacity(trivia.count)
var remainingPieces = trivia.decomposed.pieces
while let piece = remainingPieces.first {
remainingPieces.removeFirst()
switch piece {
case .newlines, .carriageReturns, .carriageReturnLineFeeds:
shouldUnindent = true
result.append(piece)
case .blockComment(let content):
result.append(.blockComment(unindentAfterNewlines(content)))
case .docBlockComment(let content):
result.append(.docBlockComment(unindentAfterNewlines(content)))
case .unexpectedText(let content):
result.append(.unexpectedText(unindentAfterNewlines(content)))
default:
result.append(piece)
}
if shouldUnindent {
if remainingPieces.starts(with: indentation) {
remainingPieces.removeFirst(indentation.count)
}
if !remainingPieces.isEmpty {
shouldUnindent = false
}
}
}
// We do not reset shouldUnindent to false here.
// Explicitly, if we end with a newline, shouldUnindent remains true.
return Trivia(pieces: result)
}
override func visit(_ token: TokenSyntax) -> TokenSyntax {
let indentedLeadingTrivia = unindent(token.leadingTrivia)
let newToken = token.with(\.leadingTrivia, indentedLeadingTrivia)
if token.text.last?.isNewline ?? false {
shouldUnindent = true
}
return newToken.with(\.trailingTrivia, unindent(token.trailingTrivia))
}
}