Skip to content

Handle DeinitializerDeclSyntax errors in a single diagnostic #2185

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

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
22 changes: 17 additions & 5 deletions Sources/SwiftParserDiagnostics/MissingNodesError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,27 @@ func nodesDescriptionAndCommonParent(_ nodes: [some SyntaxProtocol], format: Boo
}

let partDescriptions = NodesDescriptionPart.descriptionParts(for: missingSyntaxNodes).map({ $0.description(format: format) ?? "syntax" })
switch partDescriptions.count {

return (nil, formatDescriptions(partDescriptions))
}

/// Formats an array of descriptions into a single string.
///
/// This function takes an array of descriptions and formats them into a single string. Depending on the number
/// of descriptions in the array, it returns different formatted strings.
///
/// - Parameter descriptions: An array of descriptions to be formatted.
/// - Returns: A formatted string representing the descriptions.
func formatDescriptions(_ descriptions: [String]) -> String {
switch descriptions.count {
case 0:
return (nil, "syntax")
return "syntax"
case 1:
return (nil, "\(partDescriptions.first!)")
return descriptions.first!
case 2:
return (nil, "\(partDescriptions.first!) and \(partDescriptions.last!)")
return "\(descriptions.first!) and \(descriptions.last!)"
default:
return (nil, "\(partDescriptions[0..<partDescriptions.count - 1].joined(separator: ", ")), and \(partDescriptions.last!)")
return "\(descriptions[0..<descriptions.count - 1].joined(separator: ", ")), and \(descriptions.last!)"
}
}

Expand Down
58 changes: 27 additions & 31 deletions Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -788,40 +788,36 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
if shouldSkip(node) {
return .skipChildren
}
if let unexpected = node.unexpectedBetweenDeinitKeywordAndEffectSpecifiers,
let name = unexpected.presentTokens(satisfying: { $0.tokenKind.isIdentifier == true }).only
{
addDiagnostic(
name,
.deinitCannotHaveName,
fixIts: [
FixIt(message: RemoveNodesFixIt(name), changes: .makeMissing(name))
],
handledNodes: [name.id]
)
}
if let unexpected = node.unexpectedBetweenDeinitKeywordAndEffectSpecifiers,
let params = unexpected.compactMap({ $0.as(FunctionParameterClauseSyntax.self) }).only
{
addDiagnostic(
params,
.deinitCannotHaveParameters,
fixIts: [
FixIt(message: RemoveNodesFixIt(params), changes: .makeMissing(params))
],
handledNodes: [params.id]
)
}
if let unexpected = node.unexpectedBetweenEffectSpecifiersAndBody,
let returnType = unexpected.compactMap({ $0.as(ReturnClauseSyntax.self) }).only
{

let name: TokenSyntax? = node
.unexpectedBetweenDeinitKeywordAndEffectSpecifiers?
.presentTokens(satisfying: \.tokenKind.isIdentifier)
.only

let params: FunctionParameterClauseSyntax? = node
.unexpectedBetweenDeinitKeywordAndEffectSpecifiers?
.compactMap({ $0.as(FunctionParameterClauseSyntax.self) })
.only

let returnType: ReturnClauseSyntax? = node
.unexpectedBetweenEffectSpecifiersAndBody?
.compactMap({ $0.as(ReturnClauseSyntax.self) })
.only

let nodes = [Syntax(name), Syntax(params), Syntax(returnType)].compactMap { $0 }

if let firstNode = nodes.first {
addDiagnostic(
returnType,
.deinitCannotHaveReturnType,
firstNode,
DeinitializerSignatureError(name: name, params: params, returnClause: returnType),
highlights: nodes,
fixIts: [
FixIt(message: RemoveNodesFixIt(returnType), changes: .makeMissing(returnType))
FixIt(
message: RemoveNodesFixIt(nodes),
changes: nodes.map { .makeMissing($0) }
)
],
handledNodes: [returnType.id]
handledNodes: nodes.map { $0.id }
)
}

Expand Down
39 changes: 30 additions & 9 deletions Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,9 @@ extension DiagnosticMessage where Self == StaticParserError {
public static var defaultOutsideOfSwitch: Self {
.init("'default' label can only appear inside a 'switch' statement")
}
public static var deinitCannotHaveName: Self {
.init("deinitializers cannot have a name")
}
public static var deinitCannotHaveParameters: Self {
.init("deinitializers cannot have parameters")
}
public static var deinitCannotThrow: Self {
.init("deinitializers cannot throw")
}
public static var deinitCannotHaveReturnType: Self {
.init("deinitializers cannot have return type")
}
public static var editorPlaceholderInSourceFile: Self {
.init("editor placeholder in source file")
}
Expand Down Expand Up @@ -305,6 +296,36 @@ public struct CannotParseVersionTuple: ParserError {
}
}

public struct DeinitializerSignatureError: ParserError {
public let name: TokenSyntax?
public let params: FunctionParameterClauseSyntax?
public let returnClause: ReturnClauseSyntax?

public var message: String {
var descriptions: [String] = []

if name != nil {
descriptions.append("a name")
}

if params != nil {
descriptions.append("parameters")
}

if returnClause != nil {
var message = "return clause"

if descriptions.isEmpty {
message = "a " + message
}

descriptions.append(message)
}

return "deinitializers cannot have \(formatDescriptions(descriptions))"
}
}

public struct DuplicateEffectSpecifiers: ParserError {
public let correctSpecifier: TokenSyntax
public let unexpectedSpecifier: TokenSyntax
Expand Down
Loading