Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f868e0e
Separate out SwiftExtract, a reusable Swift analysis module
ktoso May 27, 2026
8163665
SwiftExtract: move java bean conventions out of SwiftExtract
ktoso Jun 1, 2026
83bd17f
SwiftExtract: remove swiftjavaerror special handling from swiftextract
ktoso Jun 1, 2026
386a749
SwiftExtract: change naming scheme; types are "extracted" since Swift…
ktoso Jun 1, 2026
f8d39a0
SwiftExtract: move typealiases
ktoso Jun 1, 2026
de8c8b5
SwiftExtract: decouple from Java config + optional operator extraction
ktoso Jun 2, 2026
0d21d11
SwiftExtract: include a few general-purpose `is...` checks
ktoso Jun 2, 2026
a282d7b
SwiftExtract: configurable importable modules + generic-type initiali…
ktoso Jun 3, 2026
b3d4de3
SwiftExtract: include URL in the Foundation known-module overlay
ktoso Jun 3, 2026
6c56f75
SwiftExtract: cover extractsGenericTypeInitializers & availableImport…
ktoso Jun 3, 2026
dd0d77a
SwiftExtract: document the dummy.json placeholder in Package.swift
ktoso Jun 3, 2026
14b7ede
SwiftExtract: doc cleanup, drop default knob impls, drop swift-5 mode…
ktoso Jun 4, 2026
a49b07a
SwiftExtract: extract AccessLevelMode into SwiftExtractConfigurationS…
ktoso Jun 4, 2026
7c9d99b
CodePrinting: Sendable conformance + emitSourceLocations toggle
ktoso Jun 8, 2026
78edf7d
SwiftExtract: permitsUnresolvedTypeReferences + SwiftSyntheticTypes
ktoso Jun 9, 2026
759a17a
SwiftExtract: registerSpecialization hook for downstream config-drive…
ktoso Jun 9, 2026
3db5a09
SwiftExtract: per-decl extraction policy belongs to each ExtractDecider
ktoso Jun 12, 2026
bead7ac
SwiftExtract: support InlineArray<N, T> / [N of T]
ktoso Jun 12, 2026
ed5fbf5
SwiftExtract: add makeSwiftJavaAnalyzer
ktoso Jun 15, 2026
6f790e5
SwiftExtract: drop language-specific notes from SwiftAnalyzer doc
ktoso Jun 15, 2026
6db83d9
SwiftExtract: make extractDecider non-optional on SwiftAnalyzer
ktoso Jun 15, 2026
4eda915
SwiftExtract: rename swiftExtractAccessLevel to effectiveMinimumInput…
ktoso Jun 15, 2026
fee125a
CodePrinting: add CodePrinter.inlineCommentStyle (.slashSlash | .hash)
ktoso Jun 15, 2026
cf06810
SwiftExtract: rename translator -> analyzer, imported -> extracted
ktoso Jun 15, 2026
ea82caa
SwiftExtract: drop extractsGenericTypeInitializers, fold into JavaExt…
ktoso Jun 15, 2026
2c0e695
SwiftExtract: rename effectiveOutputName -> effectiveTypeName, Defaul…
ktoso Jun 15, 2026
8bc4524
SwiftExtract: replace __SwiftExtractSynthesized magic module with isU…
ktoso Jun 15, 2026
a0d1540
Merge branch 'main' into wip-swift-extract
ktoso Jun 15, 2026
0a9d178
CodePrinting: CodePrinter init takes optional inlineCommentStyle
ktoso Jun 16, 2026
9d08e3d
SwiftExtract: move LogLevel into SwiftExtractConfigurationShared, dro…
ktoso Jun 16, 2026
6908ff4
SwiftExtract: drop @_spi(Testing) from SwiftNominalTypeDeclaration.sy…
ktoso Jun 16, 2026
9ebaddd
JExtractSwiftLib: rename isSwiftJavaMacro -> isJavaKitMacro, factor k…
ktoso Jun 16, 2026
714020f
SwiftExtract: rename WithModifiersSyntax.passesAccessLevel -> isAtLeast
ktoso Jun 16, 2026
98ea199
SwiftExtract: ExtractDecider owns its own logger (drops log: param)
ktoso Jun 16, 2026
53fa1d3
SwiftExtract: type extractedTypes via SwiftTypeName typealias
ktoso Jun 16, 2026
3299380
SwiftExtract: collapse analyze() overloads, default hook to no-op, fi…
ktoso Jun 16, 2026
deea424
SwiftExtract: move Foundation overlay logic into SwiftAnalyzer+Founda…
ktoso Jun 16, 2026
439a625
SwiftExtract: rename permitsUnresolvedTypeReferences -> allowUnresolv…
ktoso Jun 16, 2026
1a3b3c8
SwiftExtract: drop @_spi(Testing) from SwiftNominalTypeDeclaration.init
ktoso Jun 16, 2026
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
75 changes: 68 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ let package = Package(
targets: ["SwiftJavaConfigurationShared"]
),

.library(
name: "SwiftExtractConfigurationShared",
targets: ["SwiftExtractConfigurationShared"]
),

.library(
name: "JavaUtil",
targets: ["JavaUtil"]
Expand Down Expand Up @@ -108,6 +113,16 @@ let package = Package(
targets: ["SwiftRuntimeFunctions"]
),

.library(
name: "SwiftExtract",
targets: ["SwiftExtract"]
),

.library(
name: "CodePrinting",
targets: ["CodePrinting"]
),

.library(
name: "JExtractSwiftLib",
targets: ["JExtractSwiftLib"]
Expand Down Expand Up @@ -271,7 +286,14 @@ let package = Package(
),

.target(
name: "SwiftJavaConfigurationShared"
name: "SwiftJavaConfigurationShared",
dependencies: [
"SwiftExtractConfigurationShared"
]
),

.target(
name: "SwiftExtractConfigurationShared"
),

.target(
Expand Down Expand Up @@ -336,6 +358,33 @@ let package = Package(
]
),

.target(
name: "SwiftExtract",
dependencies: [
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftIfConfig", package: "swift-syntax"),
.product(name: "SwiftLexicalLookup", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "Logging", package: "swift-log"),
"SwiftExtractConfigurationShared",
],
path: "Sources/SwiftExtract",
resources: [
// Holds the `dummy.json` placeholder so SwiftPM emits a `Bundle.module`
// for this target. The real `static-build-config.json` is generated at
// build time by the `_StaticBuildConfigPlugin` build tool below.
.process("Resources")
],
swiftSettings: [
.swiftLanguageMode(.v5)
],
plugins: [
.plugin(name: "_StaticBuildConfigPlugin")
]
),

.target(
name: "JExtractSwiftLib",
dependencies: [
Expand All @@ -347,19 +396,14 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "OrderedCollections", package: "swift-collections"),
.product(name: "SwiftJavaJNICore", package: "swift-java-jni-core"),
"SwiftExtract",
"SwiftJavaShared",
"SwiftJavaConfigurationShared",
"CodePrinting",
],
resources: [
.process("Resources")
],
swiftSettings: [
.swiftLanguageMode(.v5),
.enableUpcomingFeature("BareSlashRegexLiterals"),
],
plugins: [
.plugin(name: "_StaticBuildConfigPlugin")
]
),

Expand Down Expand Up @@ -435,13 +479,30 @@ let package = Package(
name: "JExtractSwiftTests",
dependencies: [
"JExtractSwiftLib",
"SwiftExtract",
"CodePrinting",
],
swiftSettings: [
.swiftLanguageMode(.v5)
]
),

.testTarget(
name: "SwiftExtractTests",
dependencies: [
"SwiftExtract",
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
]
),

.testTarget(
name: "CodePrintingTests",
dependencies: [
"CodePrinting"
]
),

.testTarget(
name: "SwiftRuntimeFunctionsTests",
dependencies: [
Expand Down
1 change: 1 addition & 0 deletions Plugins/PluginsShared/SwiftExtractConfigurationShared
56 changes: 45 additions & 11 deletions Sources/CodePrinting/CodePrinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,23 @@ import Foundation
// ==== -----------------------------------------------------------------------
// MARK: CodePrinter

public struct CodePrinter {
public struct CodePrinter: Sendable {
public var contents: String = ""

public var verbose: Bool = false

/// When true, terminators of `.sloc` append a `// function @ file:line`
/// trailer to each line. Useful for debugging the generator. Downstream targets that compare
/// generated output against goldens can flip this off to get a clean
/// terminator equivalent to `.newLine`.
public var emitSourceLocations: Bool = true

/// Token used to start an inline comment. Defaults to `//` for Swift- and
/// Java-style output. Hash-comment languages can flip this to `.hash` so
/// source-location trailers, `printSeparator` banners, and echo-mode
/// headers render with the right comment lead.
public var inlineCommentStyle: InlineCommentStyle = .slashSlash

public var indentationDepth: Int = 0 {
didSet {
indentationText = String(repeating: indentationPart, count: indentationDepth)
Expand All @@ -49,12 +61,16 @@ public struct CodePrinter {
}

public var mode: PrintMode
public enum PrintMode {
public enum PrintMode: Sendable {
case accumulateAll
case flushToFileOnWrite
}
public init(mode: PrintMode = .flushToFileOnWrite) {
public init(
mode: PrintMode = .flushToFileOnWrite,
inlineCommentStyle: InlineCommentStyle = .slashSlash
) {
self.mode = mode
self.inlineCommentStyle = inlineCommentStyle
}

public mutating func append(_ text: String) {
Expand Down Expand Up @@ -167,7 +183,11 @@ public struct CodePrinter {
}

if terminator == .sloc {
append(" // \(function) @ \(file):\(line)\n")
if emitSourceLocations {
append(" \(inlineCommentStyle.rawValue) \(function) @ \(file):\(line)\n")
} else {
append("\n")
}
atNewline = true
} else {
append(terminator.rawValue)
Expand All @@ -181,11 +201,12 @@ public struct CodePrinter {

public mutating func printSeparator(_ text: String) {
assert(!text.contains(where: \.isNewline))
let lead = inlineCommentStyle.rawValue
print(
"""

// ==== --------------------------------------------------
// \(text)
\(lead) ==== --------------------------------------------------
\(lead) \(text)

"""
)
Expand Down Expand Up @@ -215,10 +236,22 @@ public struct CodePrinter {
}
}

// ==== -----------------------------------------------------------------------
// MARK: InlineCommentStyle

/// Inline comment lead used by `CodePrinter` for source-location trailers,
/// `printSeparator` banners, and echo-mode headers. Swift, Java, and other
/// `//`-comment languages use `.slashSlash`; hash-comment languages use
/// `.hash`.
public enum InlineCommentStyle: String, Sendable {
case slashSlash = "//"
case hash = "#"
}

// ==== -----------------------------------------------------------------------
// MARK: PrinterTerminator

public enum PrinterTerminator: String {
public enum PrinterTerminator: String, Sendable {
case newLine = "\n"
case space = " "
case commaSpace = ", "
Expand Down Expand Up @@ -285,19 +318,20 @@ extension CodePrinter {
// if we're accumulating everything, we don't want to finalize/flush
// any contents; let's mark that this is where a write would have
// happened though:
print("// ^^^^ Contents of: \(outputDirectory)\(PATH_SEPARATOR)\(filename)")
print("\(inlineCommentStyle.rawValue) ^^^^ Contents of: \(outputDirectory)\(PATH_SEPARATOR)\(filename)")
return nil
}

let contents = finalize()
if outputDirectory == "-" {
let lead = inlineCommentStyle.rawValue
print(
"// ==== ---------------------------------------------------------------------------------------------------"
"\(lead) ==== ---------------------------------------------------------------------------------------------------"
)
if let javaPackagePath {
print("// \(javaPackagePath)\(PATH_SEPARATOR)\(filename)")
print("\(lead) \(javaPackagePath)\(PATH_SEPARATOR)\(filename)")
} else {
print("// \(filename)")
print("\(lead) \(filename)")
}
print(contents)
return nil
Expand Down
19 changes: 0 additions & 19 deletions Sources/JExtractSwiftLib/AnalysisResult.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import SwiftExtract
import SwiftJavaConfigurationShared
import SwiftJavaJNICore

Expand Down
25 changes: 25 additions & 0 deletions Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024-2026 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
//
//===----------------------------------------------------------------------===//

import SwiftExtract
import SwiftJavaConfigurationShared

/// `Configuration` already exposes every property the analysis layer needs
/// (`swiftModule`, `swiftFilterInclude`, `effectiveMinimumInputAccessLevelMode`,
/// `logLevel`, …) — `LogLevel` and `AccessLevelMode` are the same enums on
/// both sides, both pulled in from `SwiftExtractConfigurationShared`. The
/// protocol extension on `SwiftExtractConfiguration` defaults
/// `availableImportModules` and `allowUnresolvedTypeReferences`, so this
/// conformance is empty.
extension Configuration: SwiftExtractConfiguration {}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import SwiftExtract
import SwiftJavaJNICore

extension JavaType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,7 @@
import SwiftJavaJNICore

extension String {

var firstCharacterUppercased: String {
guard let f = first else {
return self
}

return "\(f.uppercased())\(String(dropFirst()))"
}

var firstCharacterLowercased: String {
guard let f = first else {
return self
}

return "\(f.lowercased())\(String(dropFirst()))"
}

/// Returns whether the string is of the format `isX`
var hasJavaBooleanNamingConvention: Bool {
guard self.hasPrefix("is"), self.count > 2 else {
return false
}

let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2)
return self[thirdCharacterIndex].isUppercase
}

/// Returns a version of the string correctly escaped for a JNI
/// Returns a version of the string correctly escaped for a JNI identifier
var escapedJNIIdentifier: String {
self.map {
if $0 == "_" {
Expand All @@ -66,15 +39,6 @@ extension String {
.joined()
}

/// If the string ends with `.swift`, return it without that suffix;
/// otherwise return self unchanged
func dropSwiftFileSuffix() -> String {
if hasSuffix(".swift") {
return String(dropLast(".swift".count))
}
return self
}

/// Looks up self as a SwiftJava wrapped class name and converts it
/// into a `JavaType.class` if it exists in `lookupTable`.
func parseJavaClassFromSwiftJavaName(in lookupTable: [String: String]) -> JavaType? {
Expand All @@ -87,14 +51,6 @@ extension String {

return .class(package: javaPackageName, name: javaClassName)
}

/// Unescapes the name if it is surrounded by backticks.
var unescapedSwiftName: String {
if count >= 2 && hasPrefix("`") && hasSuffix("`") {
return String(dropFirst().dropLast())
}
return self
}
}

extension Array where Element == String {
Expand Down
Loading
Loading