Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] Compute beta platform information for non-symbol documentation #972

Merged
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 @@ -230,7 +230,7 @@ public class DocumentationContentRenderer {
}

// Verify that the current platform is in beta and the version number matches the introduced platform version.
guard current.beta && introduced.isEqualToVersionTriplet(current.version) else {
guard current.beta && SemanticVersion(introduced).isEqualToVersionTriplet(current.version) else {
return false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import Foundation
import SymbolKit

extension SymbolGraph.SemanticVersion {
extension SemanticVersion {
enum Precision: Int {
case all = 0, patch, minor

Expand Down Expand Up @@ -45,6 +45,14 @@ extension SymbolGraph.SemanticVersion {
.joined(separator: ".")
}

init(_ semanticVersion: SymbolGraph.SemanticVersion) {
self.major = semanticVersion.major
self.minor = semanticVersion.minor
self.patch = semanticVersion.patch
self.prerelease = semanticVersion.prerelease
self.buildMetadata = semanticVersion.buildMetadata
}

/// Compares a version triplet to a semantic version.
/// - Parameter version: A version triplet to compare to this semantic version.
/// - Returns: Returns whether the given triple represents the same version as the current version.
Expand Down Expand Up @@ -125,29 +133,32 @@ public struct AvailabilityRenderItem: Codable, Hashable, Equatable {
init(_ availability: SymbolGraph.Symbol.Availability.AvailabilityItem, current: PlatformVersion?) {
let platformName = availability.domain.map({ PlatformName(operatingSystemName: $0.rawValue) })
name = platformName?.displayName
introduced = availability.introducedVersion?.stringRepresentation(precisionUpToNonsignificant: .minor)
deprecated = availability.deprecatedVersion?.stringRepresentation(precisionUpToNonsignificant: .minor)
obsoleted = availability.obsoletedVersion?.stringRepresentation(precisionUpToNonsignificant: .minor)

let introducedVersion = availability.introducedVersion.flatMap { SemanticVersion($0) }
introduced = introducedVersion?.stringRepresentation(precisionUpToNonsignificant: .minor)
deprecated = availability.deprecatedVersion.flatMap { SemanticVersion($0).stringRepresentation(precisionUpToNonsignificant: .minor) }
obsoleted = availability.obsoletedVersion.flatMap { SemanticVersion($0).stringRepresentation(precisionUpToNonsignificant: .minor) }
message = availability.message
renamed = availability.renamed
unconditionallyUnavailable = availability.isUnconditionallyUnavailable
unconditionallyDeprecated = availability.isUnconditionallyDeprecated

if let introducedVersion = availability.introducedVersion, let current, current.beta, introducedVersion.isEqualToVersionTriplet(current.version) {
isBeta = true
} else {
isBeta = false
}
isBeta = AvailabilityRenderItem.isBeta(introduced: introducedVersion, current: current)
}

init?(_ availability: Metadata.Availability, current: PlatformVersion?) {
// FIXME: Deprecated/Beta markings need platform versions to display properly in Swift-DocC-Render (rdar://56897597)
// Fill in the appropriate values here when that's fixed (https://github.com/apple/swift-docc/issues/441)

let platformName = PlatformName(metadataPlatform: availability.platform)
name = platformName?.displayName
introduced = availability.introduced
deprecated = availability.deprecated
introduced = availability.introduced.stringRepresentation(precisionUpToNonsignificant: .minor)
deprecated = availability.deprecated.flatMap { $0.stringRepresentation(precisionUpToNonsignificant: .minor) }
isBeta = AvailabilityRenderItem.isBeta(introduced: availability.introduced, current: current)
}

private static func isBeta(introduced: SemanticVersion?, current: PlatformVersion?) -> Bool {
guard let introduced, let current, current.beta, introduced.isEqualToVersionTriplet(current.version) else {
return false
}

return true
}

/// Creates a new item with the given platform name and version string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ extension AutomaticDirectiveConvertible {
severityIfNotFound: reflectedArgument.required ? .warning : nil,
argumentName: reflectedArgument.name,
allowedValues: reflectedArgument.allowedValues,
expectedFormat: reflectedArgument.expectedFormat,
convert: { argumentValue in
return reflectedArgument.parseArgument(bundle, argumentValue)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ protocol DirectiveArgumentValueConvertible {
init?(rawDirectiveArgumentValue: String)

static func allowedValues() -> [String]?
static func expectedFormat() -> String?
}

extension DirectiveArgumentValueConvertible {
static func expectedFormat() -> String? {
return nil
}
}

extension RawRepresentable where Self: DirectiveArgumentValueConvertible, RawValue == String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ protocol _DirectiveArgumentProtocol {
var required: Bool { get }
var name: _DirectiveArgumentName { get }
var allowedValues: [String]? { get }
var expectedFormat: String? { get }
var hiddenFromDocumentation: Bool { get }

var parseArgument: (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Any?) { get }
Expand Down Expand Up @@ -64,6 +65,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
let name: _DirectiveArgumentName
let typeDisplayName: String
let allowedValues: [String]?
let expectedFormat: String?
let hiddenFromDocumentation: Bool

let parseArgument: (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Any?)
Expand Down Expand Up @@ -99,13 +101,15 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: _DirectiveArgumentName = .inferredFromPropertyName,
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
allowedValues: [String]? = nil,
expectedFormat: String? = nil,
hiddenFromDocumentation: Bool = false
) {
self.init(
value: wrappedValue,
name: name,
transform: parseArgument,
allowedValues: allowedValues,
expectedFormat: expectedFormat,
required: nil,
hiddenFromDocumentation: hiddenFromDocumentation
)
Expand All @@ -116,13 +120,15 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: _DirectiveArgumentName = .inferredFromPropertyName,
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
allowedValues: [String]? = nil,
expectedFormat: String? = nil,
hiddenFromDocumentation: Bool = false
) {
self.init(
value: nil,
name: name,
transform: parseArgument,
allowedValues: allowedValues,
expectedFormat: expectedFormat,
required: nil,
hiddenFromDocumentation: hiddenFromDocumentation
)
Expand All @@ -133,6 +139,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: _DirectiveArgumentName,
transform: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
allowedValues: [String]?,
expectedFormat: String?,
required: Bool?,
hiddenFromDocumentation: Bool
) {
Expand All @@ -143,6 +150,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
self.typeDisplayName = typeDisplayNameDescription(defaultValue: value, required: required)
self.parseArgument = transform
self.allowedValues = allowedValues
self.expectedFormat = expectedFormat
self.required = required
self.hiddenFromDocumentation = hiddenFromDocumentation
}
Expand All @@ -166,6 +174,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: _DirectiveArgumentName = .inferredFromPropertyName,
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
allowedValues: [String]? = nil,
expectedFormat: String? = nil,
required: Bool,
hiddenFromDocumentation: Bool = false
) {
Expand All @@ -174,6 +183,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: name,
transform: parseArgument,
allowedValues: allowedValues,
expectedFormat: expectedFormat,
required: required,
hiddenFromDocumentation: hiddenFromDocumentation
)
Expand All @@ -185,6 +195,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: _DirectiveArgumentName = .inferredFromPropertyName,
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
allowedValues: [String]? = nil,
expectedFormat: String? = nil,
required: Bool,
hiddenFromDocumentation: Bool = false
) {
Expand All @@ -193,6 +204,7 @@ public struct DirectiveArgumentWrapped<Value>: _DirectiveArgumentProtocol {
name: name,
transform: parseArgument,
allowedValues: allowedValues,
expectedFormat: expectedFormat,
required: required,
hiddenFromDocumentation: hiddenFromDocumentation
)
Expand Down Expand Up @@ -229,6 +241,7 @@ extension DirectiveArgumentWrapped where Value: DirectiveArgumentValueConvertibl
Value.init(rawDirectiveArgumentValue: argument)
}
self.allowedValues = Value.allowedValues()
self.expectedFormat = Value.expectedFormat()
self.required = required
self.hiddenFromDocumentation = hiddenFromDocumentation
}
Expand Down Expand Up @@ -335,13 +348,15 @@ extension DirectiveArgumentWrapped where Value: _OptionalDirectiveArgument {
name: _DirectiveArgumentName,
parseArgument: @escaping (_ bundle: DocumentationBundle, _ argumentValue: String) -> (Value?),
allowedValues: [String]? = nil,
expectedFormat: String? = nil,
hiddenFromDocumentation: Bool = false
) {
self.name = name
self.defaultValue = value
self.typeDisplayName = typeDisplayNameDescription(optionalDefaultValue: value, required: false)
self.parseArgument = parseArgument
self.allowedValues = allowedValues
self.expectedFormat = expectedFormat
self.required = false
self.hiddenFromDocumentation = hiddenFromDocumentation
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct DirectiveMirror {
name: argumentName,
unnamed: unnamed,
allowedValues: argument.allowedValues,
expectedFormat: argument.expectedFormat,
propertyLabel: label,
argument: argument,
parseArgument: argument.parseArgument
Expand Down Expand Up @@ -166,6 +167,7 @@ extension DirectiveMirror {
let unnamed: Bool

let allowedValues: [String]?
let expectedFormat: String?

let propertyLabel: String
let argument: _DirectiveArgumentProtocol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ protocol DirectiveArgument<ArgumentValue> {
/// If non-`nil`, the list of allowed values the argument can take on,
/// suggested to the author as possible solutions
static func allowedValues() -> [String]?

/// If non-`nil`, a string describing the expected format for the argument value,
/// shown to the author as part of the diagnostic summary when an invalid value is provided.
static func expectedFormat() -> String?
}

extension DirectiveArgument {
static func allowedValues() -> [String]? {
return nil
}
static func expectedFormat() -> String? {
return nil
}
static func convert(_ argument: String) -> ArgumentValue? {
return ArgumentValue(rawDirectiveArgumentValue: argument)
}
Expand Down Expand Up @@ -52,6 +59,7 @@ extension Semantic.Analyses {
severityIfNotFound: severityIfNotFound,
argumentName: Converter.argumentName,
allowedValues: Converter.allowedValues(),
expectedFormat: Converter.expectedFormat(),
convert: Converter.convert(_:),
valueTypeDiagnosticName: String(describing: Converter.ArgumentValue.self)
).analyze(directive, arguments: arguments, problems: &problems) as? Converter.ArgumentValue
Expand All @@ -62,6 +70,7 @@ extension Semantic.Analyses {
let severityIfNotFound: DiagnosticSeverity?
let argumentName: String
let allowedValues: [String]?
let expectedFormat: String?
let convert: (String) -> (Any?)
let valueTypeDiagnosticName: String

Expand All @@ -73,33 +82,45 @@ extension Semantic.Analyses {
let arguments = directive.arguments(problems: &problems)
let source = directive.range?.lowerBound.source
let diagnosticArgumentName = argumentName.isEmpty ? "unlabeled" : argumentName

let diagnosticArgumentDescription = if argumentName.isEmpty {
"an unnamed parameter"
} else {
"the \(argumentName.singleQuoted) parameter"
}
let diagnosticExplanation = if let expectedFormat {
"""
\(Parent.directiveName) expects an argument for \(diagnosticArgumentDescription) \
that's convertible to \(expectedFormat)
"""
} else {
"""
\(Parent.directiveName) expects an argument for \(diagnosticArgumentDescription) \
that's convertible to \(valueTypeDiagnosticName.singleQuoted)
"""
}
guard let argument = arguments[argumentName] else {
if let severity = severityIfNotFound {
let argumentDiagnosticDescription: String
if argumentName.isEmpty {
argumentDiagnosticDescription = "an unnamed parameter"
} else {
argumentDiagnosticDescription = "the \(argumentName.singleQuoted) parameter"
}

let diagnostic = Diagnostic(
source: source,
severity: severity,
range: directive.range,
identifier: "org.swift.docc.HasArgument.\(diagnosticArgumentName)",
summary: "Missing argument for \(diagnosticArgumentName) parameter",
explanation: """
\(Parent.directiveName) expects an argument for \(argumentDiagnosticDescription) \
that's convertible to \(valueTypeDiagnosticName.singleQuoted)
"""
explanation: diagnosticExplanation
)
problems.append(Problem(diagnostic: diagnostic, possibleSolutions: []))
}
return nil
}
guard let value = convert(argument.value) else {
let diagnostic = Diagnostic(source: source, severity: .warning, range: argument.valueRange, identifier: "org.swift.docc.HasArgument.\(diagnosticArgumentName).ConversionFailed", summary: "Cannot convert \(argument.value.singleQuoted) to type \(valueTypeDiagnosticName.singleQuoted)")
let diagnostic = Diagnostic(
source: source,
severity: .warning,
range: argument.valueRange,
identifier: "org.swift.docc.HasArgument.\(diagnosticArgumentName).ConversionFailed",
summary: "Cannot convert \(argument.value.singleQuoted) to type \(valueTypeDiagnosticName.singleQuoted)",
explanation: diagnosticExplanation
)
let solutions = allowedValues.map { allowedValues -> [Solution] in
return allowedValues.compactMap { allowedValue -> Solution? in
guard let range = argument.valueRange else {
Expand Down
Loading