Skip to content

Commit 2b8aa85

Browse files
authored
Fix Swift documentation parsing and adds - Throws support (#716)
1 parent cffb411 commit 2b8aa85

3 files changed

Lines changed: 200 additions & 3 deletions

File tree

Sources/JExtractSwiftLib/SwiftDocumentationParsing.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct SwiftDocumentation: Equatable {
2525
var discussion: String?
2626
var parameters: [Parameter] = []
2727
var returns: String?
28+
var throwsDescription: String?
2829
}
2930

3031
enum SwiftDocumentationParser {
@@ -33,6 +34,7 @@ enum SwiftDocumentationParser {
3334
case discussion
3435
case parameter(Int)
3536
case returns
37+
case throwsDescription
3638
}
3739

3840
// TODO: Replace with Regex
@@ -46,9 +48,18 @@ enum SwiftDocumentationParser {
4648
var comments = [String]()
4749
var pieces = syntax.leadingTrivia.pieces
4850

49-
// We always expect a newline follows a docline comment
50-
while case .newlines(1) = pieces.popLast(), case .docLineComment(let text) = pieces.popLast() {
51+
// Strip trailing indentation (spaces/tabs before the declaration keyword itself)
52+
while case .spaces(_) = pieces.last { pieces.removeLast() }
53+
while case .tabs(_) = pieces.last { pieces.removeLast() }
54+
55+
// Walk backwards. The backwards pattern is:
56+
// newlines(1), docLineComment, spaces/tabs(indent), newlines(1), docLineComment, spaces/tabs, …
57+
// Spaces/tabs are stripped *after* consuming each docLineComment (they precede it in source order).
58+
while case .newlines(1) = pieces.popLast() {
59+
guard case .docLineComment(let text) = pieces.popLast() else { break }
5160
comments.append(text)
61+
while case .spaces(_) = pieces.last { pieces.removeLast() }
62+
while case .tabs(_) = pieces.last { pieces.removeLast() }
5263
}
5364

5465
guard !comments.isEmpty else { return nil }
@@ -81,7 +92,7 @@ enum SwiftDocumentationParser {
8192
description: content
8293
)
8394
)
84-
state = .parameter(doc.parameters.count > 0 ? doc.parameters.count : 0)
95+
state = .parameter(doc.parameters.count - 1)
8596

8697
case "parameters":
8798
state = .parameter(0)
@@ -90,6 +101,12 @@ enum SwiftDocumentationParser {
90101
doc.returns = content
91102
state = .returns
92103

104+
case "throws":
105+
if !content.isEmpty {
106+
append(&doc.throwsDescription, content)
107+
}
108+
state = .throwsDescription
109+
93110
default:
94111
// Parameter names are marked like
95112
// - myString: description
@@ -133,6 +150,7 @@ enum SwiftDocumentationParser {
133150
case .summary: append(&doc.summary, line)
134151
case .discussion: append(&doc.discussion, line)
135152
case .returns: append(&doc.returns, line)
153+
case .throwsDescription: append(&doc.throwsDescription, line)
136154
case .parameter(let index):
137155
if index < doc.parameters.count {
138156
append(&doc.parameters[index].description, line)

Sources/JExtractSwiftLib/TranslatedDocumentation.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ enum TranslatedDocumentation {
9191
annotationsGroup.append("@param \(param.name) \(param.description)")
9292
}
9393

94+
if let throwsDescription = parsedDocumentation?.throwsDescription {
95+
annotationsGroup.append("@throws Exception \(throwsDescription)")
96+
}
97+
9498
if let returns = parsedDocumentation?.returns {
9599
annotationsGroup.append("@return \(returns)")
96100
}

Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,181 @@ import Testing
1818

1919
@Suite
2020
struct SwiftDocumentationParsingTests {
21+
@Test(
22+
"Indented Swift func documentation (inside extension)",
23+
arguments: [
24+
(
25+
JExtractGenerationMode.jni,
26+
[
27+
"""
28+
/**
29+
* Simple summary
30+
*
31+
* <p>Downcall to Swift:
32+
* {@snippet lang=swift :
33+
* public static func f()
34+
* }
35+
*/
36+
public static void f() {
37+
"""
38+
]
39+
),
40+
(
41+
JExtractGenerationMode.ffm,
42+
[
43+
"""
44+
/**
45+
* Simple summary
46+
*
47+
* <p>Downcall to Swift:
48+
* {@snippet lang=swift :
49+
* public static func f()
50+
* }
51+
*/
52+
public static void f() {
53+
"""
54+
]
55+
),
56+
]
57+
)
58+
func indented(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
59+
let text =
60+
"""
61+
public class MyClass {
62+
/// Simple summary
63+
public static func f() {}
64+
}
65+
"""
66+
67+
try assertOutput(
68+
input: text,
69+
mode,
70+
.java,
71+
expectedChunks: expectedJavaChunks
72+
)
73+
}
74+
75+
@Test(
76+
"Throws documentation",
77+
arguments: [
78+
(
79+
JExtractGenerationMode.jni,
80+
[
81+
"""
82+
/**
83+
* Summary
84+
*
85+
* <p>Downcall to Swift:
86+
* {@snippet lang=swift :
87+
* public func f()
88+
* }
89+
*
90+
* @throws Exception - An error if something fails.
91+
* - Another error case.
92+
*/
93+
public static void f() {
94+
"""
95+
]
96+
),
97+
(
98+
JExtractGenerationMode.ffm,
99+
[
100+
"""
101+
/**
102+
* Summary
103+
*
104+
* <p>Downcall to Swift:
105+
* {@snippet lang=swift :
106+
* public func f()
107+
* }
108+
*
109+
* @throws Exception - An error if something fails.
110+
* - Another error case.
111+
*/
112+
public static void f() {
113+
"""
114+
]
115+
),
116+
]
117+
)
118+
func throwsDocumentation(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
119+
let text =
120+
"""
121+
/// Summary
122+
/// - Throws:
123+
/// - An error if something fails.
124+
/// - Another error case.
125+
public func f() {}
126+
"""
127+
128+
try assertOutput(
129+
input: text,
130+
mode,
131+
.java,
132+
expectedChunks: expectedJavaChunks
133+
)
134+
}
135+
136+
@Test(
137+
"Multi-line parameter description continuation",
138+
arguments: [
139+
(
140+
JExtractGenerationMode.jni,
141+
[
142+
"""
143+
/**
144+
* Summary
145+
*
146+
* <p>Downcall to Swift:
147+
* {@snippet lang=swift :
148+
* public func f(arg0: String)
149+
* }
150+
*
151+
* @param arg0 First line of description.
152+
* Continuation line.
153+
*/
154+
public static void f(java.lang.String arg0) {
155+
"""
156+
]
157+
),
158+
(
159+
JExtractGenerationMode.ffm,
160+
[
161+
"""
162+
/**
163+
* Summary
164+
*
165+
* <p>Downcall to Swift:
166+
* {@snippet lang=swift :
167+
* public func f(arg0: String)
168+
* }
169+
*
170+
* @param arg0 First line of description.
171+
* Continuation line.
172+
*/
173+
public static void f(java.lang.String arg0) {
174+
"""
175+
]
176+
),
177+
]
178+
)
179+
func parameterContinuationLine(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
180+
let text =
181+
"""
182+
/// Summary
183+
/// - Parameter arg0: First line of description.
184+
/// Continuation line.
185+
public func f(arg0: String) {}
186+
"""
187+
188+
try assertOutput(
189+
input: text,
190+
mode,
191+
.java,
192+
expectedChunks: expectedJavaChunks
193+
)
194+
}
195+
21196
@Test(
22197
"Simple Swift func documentation",
23198
arguments: [

0 commit comments

Comments
 (0)