Skip to content
Open
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: 24 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ let package = Package(
.target(
name: "SwiftDocC",
dependencies: [
.target(name: "Common"),
.product(name: "Markdown", package: "swift-markdown"),
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
.product(name: "CLMDB", package: "swift-lmdb"),
Expand All @@ -55,6 +56,7 @@ let package = Package(
name: "SwiftDocCTests",
dependencies: [
.target(name: "SwiftDocC"),
.target(name: "Common"),
.target(name: "TestUtilities"),
],
resources: [
Expand All @@ -70,6 +72,7 @@ let package = Package(
name: "CommandLine",
dependencies: [
.target(name: "SwiftDocC"),
.target(name: "Common"),
.product(name: "NIOHTTP1", package: "swift-nio", condition: .when(platforms: [.macOS, .iOS, .linux, .android])),
.product(name: "ArgumentParser", package: "swift-argument-parser")
],
Expand All @@ -81,6 +84,7 @@ let package = Package(
dependencies: [
.target(name: "CommandLine"),
.target(name: "SwiftDocC"),
.target(name: "Common"),
.target(name: "TestUtilities"),
],
resources: [
Expand All @@ -95,6 +99,7 @@ let package = Package(
name: "TestUtilities",
dependencies: [
.target(name: "SwiftDocC"),
.target(name: "Common"),
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
],
swiftSettings: swiftSettings
Expand All @@ -109,6 +114,25 @@ let package = Package(
exclude: ["CMakeLists.txt"],
swiftSettings: swiftSettings
),

// A few common types and core functionality that's useable by all other targets.
.target(
name: "Common",
dependencies: [
// This target shouldn't have any local dependencies so that all other targets can depend on it.
// We can add dependencies on SymbolKit and Markdown here but they're not needed yet.
],
swiftSettings: [.swiftLanguageMode(.v6)]
),

.testTarget(
name: "CommonTests",
dependencies: [
.target(name: "Common"),
.target(name: "TestUtilities"),
],
swiftSettings: [.swiftLanguageMode(.v6)]
),

// Test app for CommandLine
.executableTarget(
Expand Down
176 changes: 176 additions & 0 deletions Sources/Common/SourceLanguage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2021-2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

/// A programming language.
public struct SourceLanguage: Hashable, Codable, Comparable, Sendable {
/// The display name of the programming language.
public var name: String
/// A globally unique identifier for the language.
public var id: String
/// Aliases for the language's identifier.
public var idAliases: [String] = []
/// The identifier to use for link disambiguation purposes.
public var linkDisambiguationID: String

/// Creates a new language with a given name and identifier.
/// - Parameters:
/// - name: The display name of the programming language.
/// - id: A globally unique identifier for the language.
/// - idAliases: Aliases for the language's identifier.
/// - linkDisambiguationID: The identifier to use for link disambiguation purposes.
public init(name: String, id: String, idAliases: [String] = [], linkDisambiguationID: String? = nil) {
self.name = name
self.id = id
self.idAliases = idAliases
self.linkDisambiguationID = linkDisambiguationID ?? id
}

/// Finds the programming language that matches a given identifier, or creates a new one if it finds no existing language.
/// - Parameter id: The identifier of the programming language.
public init(id: String) {
switch id {
case "swift": self = .swift
case "occ", "objc", "objective-c", "c": self = .objectiveC
// FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/swiftlang/swift-docc/issues/767)
case "occ++", "objc++", "objective-c++", "c++": self = .objectiveC
case "javascript": self = .javaScript
case "data": self = .data
case "metal": self = .metal
default:
self.name = id
self.id = id
self.linkDisambiguationID = id
}
}

/// Finds the programming language that matches a given display name, or creates a new one if it finds no existing language.
///
/// - Parameter name: The display name of the programming language.
public init(name: String) {
if let knownLanguage = SourceLanguage.firstKnownLanguage(withName: name) {
self = knownLanguage
} else {
self.name = name

let id = name.lowercased()
self.id = id
self.linkDisambiguationID = id
}
}

/// Finds the programming language that matches a given display name.
///
/// If the language name doesn't match any known language, this initializer returns `nil`.
///
/// - Parameter knownLanguageName: The display name of the programming language.
public init?(knownLanguageName: String) {
if let knownLanguage = SourceLanguage.firstKnownLanguage(withName: knownLanguageName) {
self = knownLanguage
} else {
return nil
}
}

/// Finds the programming language that matches a given identifier.
///
/// If the language identifier doesn't match any known language, this initializer returns `nil`.
///
/// - Parameter knownLanguageIdentifier: The identifier name of the programming language.
public init?(knownLanguageIdentifier: String) {
if let knownLanguage = SourceLanguage.firstKnownLanguage(withIdentifier: knownLanguageIdentifier) {
self = knownLanguage
} else {
return nil
}
}

private static func firstKnownLanguage(withName name: String) -> SourceLanguage? {
SourceLanguage.knownLanguages.first { $0.name.lowercased() == name.lowercased() }
}

private static func firstKnownLanguage(withIdentifier id: String) -> SourceLanguage? {
SourceLanguage.knownLanguages.first { knownLanguage in
([knownLanguage.id] + knownLanguage.idAliases)
.map { $0.lowercased() }
.contains(id)
}
}

/// The Swift programming language.
public static let swift = SourceLanguage(name: "Swift", id: "swift")

/// The Objective-C programming language.
public static let objectiveC = SourceLanguage(
name: "Objective-C",
id: "occ",
idAliases: [
"objective-c",
"objc",
"c", // FIXME: DocC should display C as its own language (github.com/swiftlang/swift-docc/issues/169).
"c++", // FIXME: DocC should display C++ and Objective-C++ as their own languages (https://github.com/swiftlang/swift-docc/issues/767)
"objective-c++",
"objc++",
"occ++",
],
linkDisambiguationID: "c"
)

/// The JavaScript programming language or another language that conforms to the ECMAScript specification.
public static let javaScript = SourceLanguage(name: "JavaScript", id: "javascript")
/// Miscellaneous data, that's not a programming language.
///
/// For example, use this to represent JSON or XML content.
public static let data = SourceLanguage(name: "Data", id: "data")
/// The Metal programming language.
public static let metal = SourceLanguage(name: "Metal", id: "metal")

/// The list of programming languages that are known to DocC.
public static let knownLanguages: [SourceLanguage] = [.swift, .objectiveC, .javaScript, .data, .metal]

enum CodingKeys: CodingKey {
case name
case id
case idAliases
case linkDisambiguationID
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: SourceLanguage.CodingKeys.self)

let name = try container.decode(String.self, forKey: SourceLanguage.CodingKeys.name)
let id = try container.decode(String.self, forKey: SourceLanguage.CodingKeys.id)
let idAliases = try container.decodeIfPresent([String].self, forKey: SourceLanguage.CodingKeys.idAliases) ?? []
let linkDisambiguationID = try container.decodeIfPresent(String.self, forKey: SourceLanguage.CodingKeys.linkDisambiguationID)

self.init(name: name, id: id, idAliases: idAliases, linkDisambiguationID: linkDisambiguationID)
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: SourceLanguage.CodingKeys.self)

try container.encode(self.name, forKey: SourceLanguage.CodingKeys.name)
try container.encode(self.id, forKey: SourceLanguage.CodingKeys.id)
if !self.idAliases.isEmpty {
try container.encode(self.idAliases, forKey: SourceLanguage.CodingKeys.idAliases)
}
try container.encode(self.linkDisambiguationID, forKey: SourceLanguage.CodingKeys.linkDisambiguationID)
}

public static func < (lhs: SourceLanguage, rhs: SourceLanguage) -> Bool {
// Sort Swift before other languages.
if lhs == .swift {
return true
} else if rhs == .swift {
return false
}
// Otherwise, sort by ID for a stable order.
return lhs.id < rhs.id
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import Foundation
import Markdown
import SymbolKit


private let automaticSeeAlsoLimit: Int = {
ProcessInfo.processInfo.environment["DOCC_AUTOMATIC_SEE_ALSO_LIMIT"].flatMap { Int($0) } ?? 15
}()
Expand Down
Loading