Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions Sources/JExtractSwiftLib/SwiftDocumentationParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct SwiftDocumentation: Equatable {
var discussion: String?
var parameters: [Parameter] = []
var returns: String?
var throwsDescription: String?
}

enum SwiftDocumentationParser {
Expand All @@ -33,6 +34,7 @@ enum SwiftDocumentationParser {
case discussion
case parameter(Int)
case returns
case throwsDescription
}

// TODO: Replace with Regex
Expand All @@ -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 }
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions Sources/JExtractSwiftLib/TranslatedDocumentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
Expand Down
175 changes: 175 additions & 0 deletions Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,181 @@ import Testing

@Suite
struct SwiftDocumentationParsingTests {
@Test(
"Indented Swift func documentation (inside extension)",
arguments: [
(
JExtractGenerationMode.jni,
[
"""
/**
* Simple summary
*
* <p>Downcall to Swift:
* {@snippet lang=swift :
* public static func f()
* }
*/
public static void f() {
"""
]
),
(
JExtractGenerationMode.ffm,
[
"""
/**
* Simple summary
*
* <p>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
*
* <p>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
*
* <p>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
*
* <p>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
*
* <p>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: [
Expand Down
Loading