Files
sourcekit-lsp/Sources/SourceKitLSP/Swift/ClosureCompletionFormat.swift
Josh Caswell 60beed85ae Customize closure expansion behavior
This resolves <https://github.com/swiftlang/sourcekit-lsp/issues/1788>,
following the discussion of alternatives on
<https://github.com/swiftlang/sourcekit-lsp/pulls/1789>. The bulk of the
change updates the translation from SourceKit placeholders to LSP
placeholders to handle nesting. The `CodeCompletionSession` also passes
a new custom formatter to the swift-syntax expansion routine, which
disables the transformation to trailing closures.
2024-12-05 08:57:41 -08:00

74 lines
2.4 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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
//
//===----------------------------------------------------------------------===//
#if compiler(>=6.0)
public import SwiftBasicFormat
public import SwiftSyntax
#else
import SwiftBasicFormat
import SwiftSyntax
#endif
/// A specialization of `BasicFormat` for closure literals in a code completion
/// context.
///
/// This is more conservative about newline insertion: unless the closure has
/// multiple statements in its body it will not be reformatted to multiple
/// lines.
@_spi(Testing)
public class ClosureCompletionFormat: BasicFormat {
@_spi(Testing)
public override func requiresNewline(
between first: TokenSyntax?,
and second: TokenSyntax?
) -> Bool {
if let first, isEndOfSmallClosureSignature(first) {
return false
} else if let first, isSmallClosureDelimiter(first, kind: \.leftBrace) {
return false
} else if let second, isSmallClosureDelimiter(second, kind: \.rightBrace) {
return false
} else {
return super.requiresNewline(between: first, and: second)
}
}
/// Returns `true` if `token` is an opening or closing brace (according to
/// `kind`) of a closure, and that closure has no more than one statement in
/// its body.
private func isSmallClosureDelimiter(
_ token: TokenSyntax,
kind: KeyPath<ClosureExprSyntax, TokenSyntax>
) -> Bool {
guard token.keyPathInParent == kind,
let closure = token.parent?.as(ClosureExprSyntax.self)
else {
return false
}
return closure.statements.count <= 1
}
/// Returns `true` if `token` is the last token in the signature of a closure,
/// and that closure has no more than one statement in its body.
private func isEndOfSmallClosureSignature(_ token: TokenSyntax) -> Bool {
guard
token.keyPathInParent == \ClosureSignatureSyntax.inKeyword,
let closure = token.ancestorOrSelf(mapping: { $0.as(ClosureExprSyntax.self) })
else {
return false
}
return closure.statements.count <= 1
}
}