From c7fe4988108b17415fcf965c71c4db95dc2ac507 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 5 Jun 2025 10:22:57 -0700 Subject: [PATCH] [JExtract] Santize trivia for declartion signature string Introduce `triviaSanitizedDescription` that prints tokens with trivia condensed into a single whitespace, or removed after opening or before closing parentheses. --- .../Convenience/SwiftSyntax+Extensions.swift | 54 ++++++++++++++++--- Sources/JExtractSwift/ImportedDecls.swift | 1 - .../MethodImportTests.swift | 9 +++- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift index 848797a4..b732b9f8 100644 --- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift @@ -177,17 +177,19 @@ extension DeclSyntaxProtocol { var signatureString: String { return switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) { case .functionDecl(let node): - node.with(\.body, nil).trimmedDescription + node.with(\.body, nil).triviaSanitizedDescription case .initializerDecl(let node): - node.with(\.body, nil).trimmedDescription + node.with(\.body, nil).triviaSanitizedDescription case .classDecl(let node): - node.with(\.memberBlock, "").trimmedDescription + node.with(\.memberBlock, "").triviaSanitizedDescription case .structDecl(let node): - node.with(\.memberBlock, "").trimmedDescription + node.with(\.memberBlock, "").triviaSanitizedDescription case .protocolDecl(let node): - node.with(\.memberBlock, "").trimmedDescription + node.with(\.memberBlock, "").triviaSanitizedDescription case .accessorDecl(let node): - node.with(\.body, nil).trimmedDescription + node.with(\.body, nil).triviaSanitizedDescription + case .subscriptDecl(let node): + node.with(\.accessorBlock, nil).triviaSanitizedDescription case .variableDecl(let node): node .with(\.bindings, PatternBindingListSyntax( @@ -197,9 +199,47 @@ extension DeclSyntaxProtocol { .with(\.initializer, nil) } )) - .trimmedDescription + .triviaSanitizedDescription default: fatalError("unimplemented \(self.kind)") } } + + /// Syntax text but without unnecessary trivia. + /// + /// Connective trivia are condensed into a single whitespace, but no space + /// after opening or before closing parentheses + var triviaSanitizedDescription: String { + let visitor = TriviaSanitizingDescriptionVisitor(viewMode: .sourceAccurate) + visitor.walk(self.trimmed) + return visitor.result + } +} + +class TriviaSanitizingDescriptionVisitor: SyntaxVisitor { + var result: String = "" + + var prevTokenKind: TokenKind = .endOfFile + var prevHadTrailingSpace: Bool = false + + override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { + let tokenKind = node.tokenKind + switch (prevTokenKind, tokenKind) { + case + // No whitespace after open parentheses. + (.leftAngle, _), (.leftParen, _), (.leftSquare, _), (.endOfFile, _), + // No whitespace before close parentheses. + (_, .rightAngle), (_, .rightParen), (_, .rightSquare): + break + default: + if prevHadTrailingSpace || !node.leadingTrivia.isEmpty { + result += " " + } + } + result += node.text + prevTokenKind = tokenKind + prevHadTrailingSpace = !node.trailingTrivia.isEmpty + + return .skipChildren + } } diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index c29e3d5a..04a28d9e 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -50,7 +50,6 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { var translatedSignature: TranslatedFunctionSignature public var signatureString: String { - // FIXME: Remove comments and normalize trivia. self.swiftDecl.signatureString } diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 526faece..25c028ce 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -28,11 +28,16 @@ final class MethodImportTests { import _StringProcessing import _SwiftConcurrencyShims - public func helloWorld() + /// Hello World! + public func /*comment*/helloWorld() public func globalTakeInt(i: Int) - public func globalTakeIntLongString(i32: Int32, l: Int64, s: String) + public func globalTakeIntLongString( + i32: Int32, + l: Int64, + s: String + ) public func globalReturnClass() -> MySwiftClass