diff --git a/Sources/JExtractSwiftLib/SwiftDocumentationParsing.swift b/Sources/JExtractSwiftLib/SwiftDocumentationParsing.swift index dd85a675..43ccb1ee 100644 --- a/Sources/JExtractSwiftLib/SwiftDocumentationParsing.swift +++ b/Sources/JExtractSwiftLib/SwiftDocumentationParsing.swift @@ -25,6 +25,7 @@ struct SwiftDocumentation: Equatable { var discussion: String? var parameters: [Parameter] = [] var returns: String? + var throwsDescription: String? } enum SwiftDocumentationParser { @@ -33,6 +34,7 @@ enum SwiftDocumentationParser { case discussion case parameter(Int) case returns + case throwsDescription } // TODO: Replace with Regex @@ -46,9 +48,18 @@ enum SwiftDocumentationParser { var comments = [String]() var pieces = syntax.leadingTrivia.pieces - // We always expect a newline follows a docline comment - while case .newlines(1) = pieces.popLast(), case .docLineComment(let text) = pieces.popLast() { + // Strip trailing indentation (spaces/tabs before the declaration keyword itself) + while case .spaces(_) = pieces.last { pieces.removeLast() } + while case .tabs(_) = pieces.last { pieces.removeLast() } + + // Walk backwards. The backwards pattern is: + // newlines(1), docLineComment, spaces/tabs(indent), newlines(1), docLineComment, spaces/tabs, … + // Spaces/tabs are stripped *after* consuming each docLineComment (they precede it in source order). + while case .newlines(1) = pieces.popLast() { + guard case .docLineComment(let text) = pieces.popLast() else { break } comments.append(text) + while case .spaces(_) = pieces.last { pieces.removeLast() } + while case .tabs(_) = pieces.last { pieces.removeLast() } } guard !comments.isEmpty else { return nil } @@ -81,7 +92,7 @@ enum SwiftDocumentationParser { description: content ) ) - state = .parameter(doc.parameters.count > 0 ? doc.parameters.count : 0) + state = .parameter(doc.parameters.count - 1) case "parameters": state = .parameter(0) @@ -90,6 +101,12 @@ enum SwiftDocumentationParser { doc.returns = content state = .returns + case "throws": + if !content.isEmpty { + append(&doc.throwsDescription, content) + } + state = .throwsDescription + default: // Parameter names are marked like // - myString: description @@ -133,6 +150,7 @@ enum SwiftDocumentationParser { case .summary: append(&doc.summary, line) case .discussion: append(&doc.discussion, line) case .returns: append(&doc.returns, line) + case .throwsDescription: append(&doc.throwsDescription, line) case .parameter(let index): if index < doc.parameters.count { append(&doc.parameters[index].description, line) diff --git a/Sources/JExtractSwiftLib/TranslatedDocumentation.swift b/Sources/JExtractSwiftLib/TranslatedDocumentation.swift index 2db9d327..bab21fdd 100644 --- a/Sources/JExtractSwiftLib/TranslatedDocumentation.swift +++ b/Sources/JExtractSwiftLib/TranslatedDocumentation.swift @@ -91,6 +91,10 @@ enum TranslatedDocumentation { annotationsGroup.append("@param \(param.name) \(param.description)") } + if let throwsDescription = parsedDocumentation?.throwsDescription { + annotationsGroup.append("@throws Exception \(throwsDescription)") + } + if let returns = parsedDocumentation?.returns { annotationsGroup.append("@return \(returns)") } diff --git a/Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift b/Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift index a605478e..1cd085e6 100644 --- a/Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift +++ b/Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift @@ -18,6 +18,181 @@ import Testing @Suite struct SwiftDocumentationParsingTests { + @Test( + "Indented Swift func documentation (inside extension)", + arguments: [ + ( + JExtractGenerationMode.jni, + [ + """ + /** + * Simple summary + * + *
Downcall to Swift: + * {@snippet lang=swift : + * public static func f() + * } + */ + public static void f() { + """ + ] + ), + ( + JExtractGenerationMode.ffm, + [ + """ + /** + * Simple summary + * + *
Downcall to Swift: + * {@snippet lang=swift : + * public static func f() + * } + */ + public static void f() { + """ + ] + ), + ] + ) + func indented(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws { + let text = + """ + public class MyClass { + /// Simple summary + public static func f() {} + } + """ + + try assertOutput( + input: text, + mode, + .java, + expectedChunks: expectedJavaChunks + ) + } + + @Test( + "Throws documentation", + arguments: [ + ( + JExtractGenerationMode.jni, + [ + """ + /** + * Summary + * + *
Downcall to Swift: + * {@snippet lang=swift : + * public func f() + * } + * + * @throws Exception - An error if something fails. + * - Another error case. + */ + public static void f() { + """ + ] + ), + ( + JExtractGenerationMode.ffm, + [ + """ + /** + * Summary + * + *
Downcall to Swift: + * {@snippet lang=swift : + * public func f() + * } + * + * @throws Exception - An error if something fails. + * - Another error case. + */ + public static void f() { + """ + ] + ), + ] + ) + func throwsDocumentation(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws { + let text = + """ + /// Summary + /// - Throws: + /// - An error if something fails. + /// - Another error case. + public func f() {} + """ + + try assertOutput( + input: text, + mode, + .java, + expectedChunks: expectedJavaChunks + ) + } + + @Test( + "Multi-line parameter description continuation", + arguments: [ + ( + JExtractGenerationMode.jni, + [ + """ + /** + * Summary + * + *
Downcall to Swift: + * {@snippet lang=swift : + * public func f(arg0: String) + * } + * + * @param arg0 First line of description. + * Continuation line. + */ + public static void f(java.lang.String arg0) { + """ + ] + ), + ( + JExtractGenerationMode.ffm, + [ + """ + /** + * Summary + * + *
Downcall to Swift: + * {@snippet lang=swift : + * public func f(arg0: String) + * } + * + * @param arg0 First line of description. + * Continuation line. + */ + public static void f(java.lang.String arg0) { + """ + ] + ), + ] + ) + func parameterContinuationLine(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws { + let text = + """ + /// Summary + /// - Parameter arg0: First line of description. + /// Continuation line. + public func f(arg0: String) {} + """ + + try assertOutput( + input: text, + mode, + .java, + expectedChunks: expectedJavaChunks + ) + } + @Test( "Simple Swift func documentation", arguments: [