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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//

import CodePrinting
import SwiftJavaConfigurationShared
import SwiftJavaJNICore

extension FFMSwift2JavaGenerator {
Expand Down Expand Up @@ -65,9 +66,9 @@ extension FFMSwift2JavaGenerator {
printer.printBraceBlock(
"""
/**
* {@snippet lang=c :
* \(config.javadocCodeSnippetStart(lang: "c"))
* \(cFunc.description)
* }
* \(config.javadocCodeSnippetEnd)
*/
private static class \(cFunc.name)
"""
Expand Down Expand Up @@ -398,6 +399,7 @@ extension FFMSwift2JavaGenerator {
TranslatedDocumentation.printDocumentation(
importedFunc: decl,
translatedDecl: translated,
config: config,
in: &printer,
)
printer.printBraceBlock(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import CodePrinting
import Foundation
import OrderedCollections
import SwiftJavaConfigurationShared
import SwiftJavaJNICore

// MARK: Defaults
Expand Down Expand Up @@ -693,6 +694,7 @@ extension JNISwift2JavaGenerator {
TranslatedDocumentation.printDocumentation(
importedFunc: importedFunc,
translatedDecl: translatedDecl,
config: config,
in: &printer,
)
}
Expand Down Expand Up @@ -725,6 +727,7 @@ extension JNISwift2JavaGenerator {
TranslatedDocumentation.printDocumentation(
importedFunc: importedFunc,
translatedDecl: translatedDecl,
config: config,
in: &printer,
)
}
Expand Down
15 changes: 10 additions & 5 deletions Sources/JExtractSwiftLib/TranslatedDocumentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
//===----------------------------------------------------------------------===//

import CodePrinting
import SwiftJavaConfigurationShared
import SwiftSyntax

enum TranslatedDocumentation {
static func printDocumentation(
importedFunc: ImportedFunc,
translatedDecl: FFMSwift2JavaGenerator.TranslatedFunctionDecl,
config: Configuration,
in printer: inout CodePrinter
) {
var documentation = SwiftDocumentationParser.parse(importedFunc.swiftDecl)
Expand All @@ -32,12 +34,13 @@ enum TranslatedDocumentation {
)
}

printDocumentation(documentation, syntax: importedFunc.swiftDecl, in: &printer)
printDocumentation(documentation, syntax: importedFunc.swiftDecl, config: config, in: &printer)
}

static func printDocumentation(
importedFunc: ImportedFunc,
translatedDecl: JNISwift2JavaGenerator.TranslatedFunctionDecl,
config: Configuration,
in printer: inout CodePrinter
) {
var documentation = SwiftDocumentationParser.parse(importedFunc.swiftDecl)
Expand All @@ -51,12 +54,13 @@ enum TranslatedDocumentation {
)
}

printDocumentation(documentation, syntax: importedFunc.swiftDecl, in: &printer)
printDocumentation(documentation, syntax: importedFunc.swiftDecl, config: config, in: &printer)
}

private static func printDocumentation(
_ parsedDocumentation: SwiftDocumentation?,
syntax: some DeclSyntaxProtocol,
config: Configuration,
in printer: inout CodePrinter
) {
var groups = [String]()
Expand All @@ -71,12 +75,13 @@ enum TranslatedDocumentation {
}
}

let signatureString = syntax.signatureString
groups.append(
"""
\(parsedDocumentation != nil ? "<p>" : "")Downcall to Swift:
{@snippet lang=swift :
\(syntax.signatureString)
}
\(config.javadocCodeSnippetStart(lang: "swift"))
\(signatureString)
\(config.javadocCodeSnippetEnd)
"""
)

Expand Down
36 changes: 36 additions & 0 deletions Sources/SwiftJavaConfigurationShared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,42 @@ public struct Configuration: Codable {
asyncFuncMode ?? .default
}

public var javaSourceLevel: JavaSourceLevel?
public var effectiveJavaSourceLevel: JavaSourceLevel {
javaSourceLevel ?? .default
}

/// Check whether the effective Java source level supports the given feature
public func supports(_ feature: JavaSourceFeature) -> Bool {
effectiveJavaSourceLevel >= feature.minimumJavaSourceLevel
}

/// Opening tag for a JavaDoc code snippet block.
///
/// - JDK 18+: `{@snippet lang=<lang> :` (https://openjdk.org/jeps/413)
/// - JDK 17 and below: `<pre>{@code`
public func javadocCodeSnippetStart(lang: String) -> String {
// TODO: also handle ``` once we support /// style comments in JDK22+
if supports(.javadocSnippets) {
return "{@snippet lang=\(lang) :"
} else {
return "<pre>{@code"
}
}

/// Closing tag for a JavaDoc code snippet block.
///
/// - JDK 18+: `}` (https://openjdk.org/jeps/413)
/// - JDK 17 and below: `}</pre>`
public var javadocCodeSnippetEnd: String {
// TODO: also handle ``` once we support /// style comments in JDK22+
if supports(.javadocSnippets) {
return "}"
} else {
return "}</pre>"
}
}

public var enableJavaCallbacks: Bool?
public var effectiveEnableJavaCallbacks: Bool {
enableJavaCallbacks ?? false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// A feature that requires a minimum Java source level.
///
/// Use with ``Configuration/supports(_:)`` to conditionally emit
/// source-level-dependent constructs.
public struct JavaSourceFeature: Sendable {
/// The minimum Java source level required for this feature
public let minimumJavaSourceLevel: JavaSourceLevel

/// Human-readable description of the feature
public let description: String
}

extension JavaSourceFeature {
/// JavaDoc `{@snippet}` tag support (JEP 413, JDK 18+)
public static let javadocSnippets = JavaSourceFeature(
minimumJavaSourceLevel: .jdk18,
description: "JavaDoc {@snippet} tag (JEP 413)"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// The Java source level to target when generating Java code.
///
/// Controls which Java language features may appear in generated output.
/// Encoded as a plain integer in JSON (e.g. `"javaSourceLevel": 17`).
public enum JavaSourceLevel: Int, Comparable, Sendable {
case jdk17 = 17
case jdk18 = 18
case jdk21 = 21
case jdk22 = 22
case jdk24 = 24

public static var `default`: Self { .jdk22 }

public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}

// ==== -----------------------------------------------------------------------
// MARK: Codable

extension JavaSourceLevel: Codable {
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(Int.self)
guard let level = JavaSourceLevel(rawValue: rawValue) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unknown JavaSourceLevel: \(rawValue). Supported values: \(JavaSourceLevel.allCases.map(\.rawValue))"
)
}
self = level
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}

extension JavaSourceLevel: CaseIterable {}
112 changes: 112 additions & 0 deletions Tests/JExtractSwiftTests/SwiftDocumentationParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,116 @@ struct SwiftDocumentationParsingTests {
expectedChunks: expectedJavaChunks
)
}

@Test(
"JDK 17 fallback: <pre>{@code} instead of {@snippet}",
arguments: [
(
JExtractGenerationMode.jni,
[
"""
/**
* Simple summary
*
* <p>Downcall to Swift:
* <pre>{@code
* public func f()
* }</pre>
*/
public static void f() {
"""
]
),
(
JExtractGenerationMode.ffm,
[
"""
/**
* Simple summary
*
* <p>Downcall to Swift:
* <pre>{@code
* public func f()
* }</pre>
*/
public static void f() {
"""
]
),
]
)
func jdk17Fallback(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
let text =
"""
/// Simple summary
public func f() {}
"""

var config = Configuration()
config.javaSourceLevel = .jdk17

try assertOutput(
input: text,
config: config,
mode,
.java,
expectedChunks: expectedJavaChunks
)
}

@Test(
"JDK 22 uses {@snippet} tags",
arguments: [
(
JExtractGenerationMode.jni,
[
"""
/**
* Simple summary
*
* <p>Downcall to Swift:
* {@snippet lang=swift :
* public func f()
* }
*/
public static void f() {
"""
]
),
(
JExtractGenerationMode.ffm,
[
"""
/**
* Simple summary
*
* <p>Downcall to Swift:
* {@snippet lang=swift :
* public func f()
* }
*/
public static void f() {
"""
]
),
]
)
func jdk22Snippets(mode: JExtractGenerationMode, expectedJavaChunks: [String]) throws {
let text =
"""
/// Simple summary
public func f() {}
"""

var config = Configuration()
config.javaSourceLevel = .jdk22

try assertOutput(
input: text,
config: config,
mode,
.java,
expectedChunks: expectedJavaChunks
)
}
}
Loading