Skip to content

Commit a5d78af

Browse files
authored
Support JDK17 style code snippets; {@snippet is only since JDK18 (#702)
1 parent af2a727 commit a5d78af

7 files changed

Lines changed: 253 additions & 7 deletions

File tree

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import CodePrinting
16+
import SwiftJavaConfigurationShared
1617
import SwiftJavaJNICore
1718

1819
extension FFMSwift2JavaGenerator {
@@ -65,9 +66,9 @@ extension FFMSwift2JavaGenerator {
6566
printer.printBraceBlock(
6667
"""
6768
/**
68-
* {@snippet lang=c :
69+
* \(config.javadocCodeSnippetStart(lang: "c"))
6970
* \(cFunc.description)
70-
* }
71+
* \(config.javadocCodeSnippetEnd)
7172
*/
7273
private static class \(cFunc.name)
7374
"""
@@ -398,6 +399,7 @@ extension FFMSwift2JavaGenerator {
398399
TranslatedDocumentation.printDocumentation(
399400
importedFunc: decl,
400401
translatedDecl: translated,
402+
config: config,
401403
in: &printer,
402404
)
403405
printer.printBraceBlock(

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import CodePrinting
1616
import Foundation
1717
import OrderedCollections
18+
import SwiftJavaConfigurationShared
1819
import SwiftJavaJNICore
1920

2021
// MARK: Defaults
@@ -693,6 +694,7 @@ extension JNISwift2JavaGenerator {
693694
TranslatedDocumentation.printDocumentation(
694695
importedFunc: importedFunc,
695696
translatedDecl: translatedDecl,
697+
config: config,
696698
in: &printer,
697699
)
698700
}
@@ -725,6 +727,7 @@ extension JNISwift2JavaGenerator {
725727
TranslatedDocumentation.printDocumentation(
726728
importedFunc: importedFunc,
727729
translatedDecl: translatedDecl,
730+
config: config,
728731
in: &printer,
729732
)
730733
}

Sources/JExtractSwiftLib/TranslatedDocumentation.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import CodePrinting
16+
import SwiftJavaConfigurationShared
1617
import SwiftSyntax
1718

1819
enum TranslatedDocumentation {
1920
static func printDocumentation(
2021
importedFunc: ImportedFunc,
2122
translatedDecl: FFMSwift2JavaGenerator.TranslatedFunctionDecl,
23+
config: Configuration,
2224
in printer: inout CodePrinter
2325
) {
2426
var documentation = SwiftDocumentationParser.parse(importedFunc.swiftDecl)
@@ -32,12 +34,13 @@ enum TranslatedDocumentation {
3234
)
3335
}
3436

35-
printDocumentation(documentation, syntax: importedFunc.swiftDecl, in: &printer)
37+
printDocumentation(documentation, syntax: importedFunc.swiftDecl, config: config, in: &printer)
3638
}
3739

3840
static func printDocumentation(
3941
importedFunc: ImportedFunc,
4042
translatedDecl: JNISwift2JavaGenerator.TranslatedFunctionDecl,
43+
config: Configuration,
4144
in printer: inout CodePrinter
4245
) {
4346
var documentation = SwiftDocumentationParser.parse(importedFunc.swiftDecl)
@@ -51,12 +54,13 @@ enum TranslatedDocumentation {
5154
)
5255
}
5356

54-
printDocumentation(documentation, syntax: importedFunc.swiftDecl, in: &printer)
57+
printDocumentation(documentation, syntax: importedFunc.swiftDecl, config: config, in: &printer)
5558
}
5659

5760
private static func printDocumentation(
5861
_ parsedDocumentation: SwiftDocumentation?,
5962
syntax: some DeclSyntaxProtocol,
63+
config: Configuration,
6064
in printer: inout CodePrinter
6165
) {
6266
var groups = [String]()
@@ -71,12 +75,13 @@ enum TranslatedDocumentation {
7175
}
7276
}
7377

78+
let signatureString = syntax.signatureString
7479
groups.append(
7580
"""
7681
\(parsedDocumentation != nil ? "<p>" : "")Downcall to Swift:
77-
{@snippet lang=swift :
78-
\(syntax.signatureString)
79-
}
82+
\(config.javadocCodeSnippetStart(lang: "swift"))
83+
\(signatureString)
84+
\(config.javadocCodeSnippetEnd)
8085
"""
8186
)
8287

Sources/SwiftJavaConfigurationShared/Configuration.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,42 @@ public struct Configuration: Codable {
7777
asyncFuncMode ?? .default
7878
}
7979

80+
public var javaSourceLevel: JavaSourceLevel?
81+
public var effectiveJavaSourceLevel: JavaSourceLevel {
82+
javaSourceLevel ?? .default
83+
}
84+
85+
/// Check whether the effective Java source level supports the given feature
86+
public func supports(_ feature: JavaSourceFeature) -> Bool {
87+
effectiveJavaSourceLevel >= feature.minimumJavaSourceLevel
88+
}
89+
90+
/// Opening tag for a JavaDoc code snippet block.
91+
///
92+
/// - JDK 18+: `{@snippet lang=<lang> :` (https://openjdk.org/jeps/413)
93+
/// - JDK 17 and below: `<pre>{@code`
94+
public func javadocCodeSnippetStart(lang: String) -> String {
95+
// TODO: also handle ``` once we support /// style comments in JDK22+
96+
if supports(.javadocSnippets) {
97+
return "{@snippet lang=\(lang) :"
98+
} else {
99+
return "<pre>{@code"
100+
}
101+
}
102+
103+
/// Closing tag for a JavaDoc code snippet block.
104+
///
105+
/// - JDK 18+: `}` (https://openjdk.org/jeps/413)
106+
/// - JDK 17 and below: `}</pre>`
107+
public var javadocCodeSnippetEnd: String {
108+
// TODO: also handle ``` once we support /// style comments in JDK22+
109+
if supports(.javadocSnippets) {
110+
return "}"
111+
} else {
112+
return "}</pre>"
113+
}
114+
}
115+
80116
public var enableJavaCallbacks: Bool?
81117
public var effectiveEnableJavaCallbacks: Bool {
82118
enableJavaCallbacks ?? false
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
/// A feature that requires a minimum Java source level.
16+
///
17+
/// Use with ``Configuration/supports(_:)`` to conditionally emit
18+
/// source-level-dependent constructs.
19+
public struct JavaSourceFeature: Sendable {
20+
/// The minimum Java source level required for this feature
21+
public let minimumJavaSourceLevel: JavaSourceLevel
22+
23+
/// Human-readable description of the feature
24+
public let description: String
25+
}
26+
27+
extension JavaSourceFeature {
28+
/// JavaDoc `{@snippet}` tag support (JEP 413, JDK 18+)
29+
public static let javadocSnippets = JavaSourceFeature(
30+
minimumJavaSourceLevel: .jdk18,
31+
description: "JavaDoc {@snippet} tag (JEP 413)"
32+
)
33+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
/// The Java source level to target when generating Java code.
16+
///
17+
/// Controls which Java language features may appear in generated output.
18+
/// Encoded as a plain integer in JSON (e.g. `"javaSourceLevel": 17`).
19+
public enum JavaSourceLevel: Int, Comparable, Sendable {
20+
case jdk17 = 17
21+
case jdk18 = 18
22+
case jdk21 = 21
23+
case jdk22 = 22
24+
case jdk24 = 24
25+
26+
public static var `default`: Self { .jdk22 }
27+
28+
public static func < (lhs: Self, rhs: Self) -> Bool {
29+
lhs.rawValue < rhs.rawValue
30+
}
31+
}
32+
33+
// ==== -----------------------------------------------------------------------
34+
// MARK: Codable
35+
36+
extension JavaSourceLevel: Codable {
37+
public init(from decoder: any Decoder) throws {
38+
let container = try decoder.singleValueContainer()
39+
let rawValue = try container.decode(Int.self)
40+
guard let level = JavaSourceLevel(rawValue: rawValue) else {
41+
throw DecodingError.dataCorruptedError(
42+
in: container,
43+
debugDescription: "Unknown JavaSourceLevel: \(rawValue). Supported values: \(JavaSourceLevel.allCases.map(\.rawValue))"
44+
)
45+
}
46+
self = level
47+
}
48+
49+
public func encode(to encoder: any Encoder) throws {
50+
var container = encoder.singleValueContainer()
51+
try container.encode(rawValue)
52+
}
53+
}
54+
55+
extension JavaSourceLevel: CaseIterable {}

Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,4 +520,116 @@ struct SwiftDocumentationParsingTests {
520520
expectedChunks: expectedJavaChunks
521521
)
522522
}
523+
524+
@Test(
525+
"JDK 17 fallback: <pre>{@code} instead of {@snippet}",
526+
arguments: [
527+
(
528+
JExtractGenerationMode.jni,
529+
[
530+
"""
531+
/**
532+
* Simple summary
533+
*
534+
* <p>Downcall to Swift:
535+
* <pre>{@code
536+
* public func f()
537+
* }</pre>
538+
*/
539+
public static void f() {
540+
"""
541+
]
542+
),
543+
(
544+
JExtractGenerationMode.ffm,
545+
[
546+
"""
547+
/**
548+
* Simple summary
549+
*
550+
* <p>Downcall to Swift:
551+
* <pre>{@code
552+
* public func f()
553+
* }</pre>
554+
*/
555+
public static void f() {
556+
"""
557+
]
558+
),
559+
]
560+
)
561+
func jdk17Fallback(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
562+
let text =
563+
"""
564+
/// Simple summary
565+
public func f() {}
566+
"""
567+
568+
var config = Configuration()
569+
config.javaSourceLevel = .jdk17
570+
571+
try assertOutput(
572+
input: text,
573+
config: config,
574+
mode,
575+
.java,
576+
expectedChunks: expectedJavaChunks
577+
)
578+
}
579+
580+
@Test(
581+
"JDK 22 uses {@snippet} tags",
582+
arguments: [
583+
(
584+
JExtractGenerationMode.jni,
585+
[
586+
"""
587+
/**
588+
* Simple summary
589+
*
590+
* <p>Downcall to Swift:
591+
* {@snippet lang=swift :
592+
* public func f()
593+
* }
594+
*/
595+
public static void f() {
596+
"""
597+
]
598+
),
599+
(
600+
JExtractGenerationMode.ffm,
601+
[
602+
"""
603+
/**
604+
* Simple summary
605+
*
606+
* <p>Downcall to Swift:
607+
* {@snippet lang=swift :
608+
* public func f()
609+
* }
610+
*/
611+
public static void f() {
612+
"""
613+
]
614+
),
615+
]
616+
)
617+
func jdk22Snippets(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
618+
let text =
619+
"""
620+
/// Simple summary
621+
public func f() {}
622+
"""
623+
624+
var config = Configuration()
625+
config.javaSourceLevel = .jdk22
626+
627+
try assertOutput(
628+
input: text,
629+
config: config,
630+
mode,
631+
.java,
632+
expectedChunks: expectedJavaChunks
633+
)
634+
}
523635
}

0 commit comments

Comments
 (0)