Skip to content

Commit 9b54586

Browse files
committed
Add to match this opening <thing> notes to the diagnostics
1 parent 4998857 commit 9b54586

21 files changed

+371
-138
lines changed

CodeGeneration/Sources/SyntaxSupport/gyb_generated/StmtNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ public let STMT_NODES: [Node] = [
705705
]),
706706

707707
Node(name: "PoundAssertStmt",
708-
nameForDiagnostics: "'#assert' statement",
708+
nameForDiagnostics: "'#assert' directive",
709709
kind: "Stmt",
710710
children: [
711711
Child(name: "PoundAssert",

Sources/SwiftDiagnostics/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(SwiftDiagnostics STATIC
10+
Diagnostic.swift
1011
FixIt.swift
1112
Message.swift
12-
Diagnostic.swift
13+
Note.swift
1314
)
1415

1516
target_link_libraries(SwiftDiagnostics PUBLIC

Sources/SwiftDiagnostics/Diagnostic.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,26 @@ public struct Diagnostic: CustomDebugStringConvertible {
2626
/// Nodes that should be highlighted in the source code.
2727
public let highlights: [Syntax]
2828

29+
/// Notes that point to additional locations which are relevant for this diagnostic.
30+
public let notes: [Note]
31+
2932
/// Fix-Its that can be applied to resolve this diagnostic.
3033
/// Each Fix-It offers a different way to resolve the diagnostic. Usually, there's only one.
3134
public let fixIts: [FixIt]
3235

33-
public init(node: Syntax, position: AbsolutePosition? = nil, message: DiagnosticMessage, highlights: [Syntax] = [], fixIts: [FixIt] = []) {
36+
public init(
37+
node: Syntax,
38+
position: AbsolutePosition? = nil,
39+
message: DiagnosticMessage,
40+
highlights: [Syntax] = [],
41+
notes: [Note] = [],
42+
fixIts: [FixIt] = []
43+
) {
3444
self.node = node
3545
self.position = position ?? node.positionAfterSkippingLeadingTrivia
3646
self.diagMessage = message
3747
self.highlights = highlights
48+
self.notes = notes
3849
self.fixIts = fixIts
3950
}
4051

Sources/SwiftDiagnostics/FixIt.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SwiftSyntax
1616
/// shown to the client.
1717
/// The messages should describe the change that the Fix-It will perform
1818
public protocol FixItMessage {
19-
/// The diagnostic message that should be displayed in the client.
19+
/// The Fix-It message that should be displayed in the client.
2020
var message: String { get }
2121

2222
/// See ``MessageID``.
@@ -38,7 +38,7 @@ public struct FixIt {
3838
public let changes: [Change]
3939

4040
public init(message: FixItMessage, changes: [Change]) {
41-
assert(!changes.isEmpty, "A Fix-It must have at least one diagnostic associated with it")
41+
assert(!changes.isEmpty, "A Fix-It must have at least one change associated with it")
4242
self.message = message
4343
self.changes = changes
4444
}

Sources/SwiftDiagnostics/Note.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===--- Note.swift -------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// Types conforming to this protocol represent note messages that can be
16+
/// shown to the client.
17+
/// The messages should describe what the note is pointing at.
18+
public protocol NoteMessage {
19+
/// The message that should be displayed in the client.
20+
var message: String { get }
21+
22+
/// See ``MessageID``.
23+
var fixItID: MessageID { get }
24+
}
25+
26+
/// A note that points to another node that's relevant for a Diagnostic.
27+
public struct Note: CustomDebugStringConvertible {
28+
/// The node whose location the node is pointing.
29+
public let node: Syntax
30+
31+
/// The position at which the location should be anchored.
32+
/// By default, this is the start location of `node`.
33+
public let position: AbsolutePosition
34+
35+
/// A description of what this note is pointing at.
36+
public let noteMessage: NoteMessage
37+
38+
public init(
39+
node: Syntax,
40+
position: AbsolutePosition? = nil,
41+
message: NoteMessage
42+
) {
43+
self.node = node
44+
self.position = position ?? node.positionAfterSkippingLeadingTrivia
45+
self.noteMessage = message
46+
}
47+
48+
/// The message that should be displayed to the user.
49+
public var message: String {
50+
return noteMessage.message
51+
}
52+
53+
/// The location at which the note should be displayed.
54+
public func location(converter: SourceLocationConverter) -> SourceLocation {
55+
return converter.location(for: position)
56+
}
57+
58+
public var debugDescription: String {
59+
if let root = node.root.as(SourceFileSyntax.self) {
60+
let locationConverter = SourceLocationConverter(file: "", tree: root)
61+
let location = location(converter: locationConverter)
62+
return "\(location): \(message)"
63+
} else {
64+
return "<unknown>: \(message)"
65+
}
66+
}
67+
}
68+

Sources/SwiftParser/Diagnostics/MissingNodesError.swift

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,38 @@ private func missingNodesDescription(missingNodes: [Syntax], commonParent: Synta
102102
}
103103
}
104104

105+
fileprivate extension TokenKind {
106+
var isStartMarker: Bool {
107+
switch self {
108+
case .leftBrace, .leftAngle, .leftParen, .leftSquareBracket:
109+
return true
110+
default:
111+
return false
112+
}
113+
}
114+
115+
var isEndMarker: Bool {
116+
return matchingStartMarkerKind != nil
117+
}
118+
119+
var matchingStartMarkerKind: TokenKind? {
120+
switch self {
121+
case .rightBrace:
122+
return .leftBrace
123+
case .rightAngle:
124+
return .leftAngle
125+
case .rightParen:
126+
return .leftParen
127+
case .rightSquareBracket:
128+
return .leftSquareBracket
129+
case .stringQuote, .multilineStringQuote, .rawStringDelimiter:
130+
return self
131+
default:
132+
return nil
133+
}
134+
}
135+
}
136+
105137
// MARK: - Error
106138

107139
public struct MissingNodesError: ParserError {
@@ -142,22 +174,8 @@ public struct MissingNodesError: ParserError {
142174
return nil
143175
}
144176

145-
var isFirstTokenStartMarker: Bool
146-
switch missingNodes.first?.as(TokenSyntax.self)?.tokenKind {
147-
case .leftBrace, .leftAngle, .leftParen, .leftSquareBracket:
148-
isFirstTokenStartMarker = true
149-
default:
150-
isFirstTokenStartMarker = false
151-
}
152-
153-
var isLastTokenEndMarker: Bool
154-
switch missingNodes.last?.as(TokenSyntax.self)?.tokenKind {
155-
case .rightBrace, .rightAngle, .rightParen, .rightSquareBracket, .stringQuote, .multilineStringQuote, .rawStringDelimiter(_):
156-
isLastTokenEndMarker = true
157-
default:
158-
isLastTokenEndMarker = false
159-
}
160-
177+
let isFirstTokenStartMarker = missingNodes.first?.as(TokenSyntax.self)?.tokenKind.isStartMarker ?? false
178+
let isLastTokenEndMarker = missingNodes.last?.as(TokenSyntax.self)?.tokenKind.isEndMarker ?? false
161179
switch (isFirstTokenStartMarker, isLastTokenEndMarker) {
162180
case (true, false) where Syntax(anchorParent.firstToken(viewMode: .all)) == missingNodes.first:
163181
return "to start \(anchorTypeName)"
@@ -180,6 +198,14 @@ public struct MissingNodesError: ParserError {
180198
}
181199
}
182200

201+
// MARK: - Note
202+
203+
public struct MatchingOpeningTokenNote: ParserNote {
204+
let openingToken: TokenSyntax
205+
206+
public var message: String { "to match this opening '\(openingToken.text)'" }
207+
}
208+
183209
// MARK: - Fix-It
184210

185211
public struct InsertTokenFixIt: ParserFixIt {
@@ -233,10 +259,19 @@ extension ParseDiagnosticsGenerator {
233259
changes: missingNodes.map(FixIt.Change.makePresent)
234260
)
235261

262+
var notes: [Note] = []
263+
if missingNodes.count == 1,
264+
let token = missingNodes.last?.as(TokenSyntax.self),
265+
let matchingStartMarkerKind = token.tokenKind.matchingStartMarkerKind,
266+
let startToken = token.parent?.children(viewMode: .sourceAccurate).lazy.compactMap({ $0.as(TokenSyntax.self) }).first(where: { $0.tokenKind == matchingStartMarkerKind }) {
267+
notes.append(Note(node: Syntax(startToken), message: MatchingOpeningTokenNote(openingToken: startToken)))
268+
}
269+
236270
addDiagnostic(
237271
node,
238272
position: node.endPosition,
239273
MissingNodesError(missingNodes: missingNodes),
274+
notes: notes,
240275
fixIts: [fixIt],
241276
handledNodes: missingNodes.map(\.id)
242277
)

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,39 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
5151
// MARK: - Private helper functions
5252

5353
/// Produce a diagnostic.
54-
func addDiagnostic<T: SyntaxProtocol>(_ node: T, position: AbsolutePosition? = nil, _ message: DiagnosticMessage, highlights: [Syntax] = [], fixIts: [FixIt] = [], handledNodes: [SyntaxIdentifier] = []) {
54+
func addDiagnostic<T: SyntaxProtocol>(
55+
_ node: T,
56+
position: AbsolutePosition? = nil,
57+
_ message: DiagnosticMessage,
58+
highlights: [Syntax] = [],
59+
notes: [Note] = [],
60+
fixIts: [FixIt] = [],
61+
handledNodes: [SyntaxIdentifier] = []
62+
) {
5563
diagnostics.removeAll(where: { handledNodes.contains($0.node.id) })
56-
diagnostics.append(Diagnostic(node: Syntax(node), position: position, message: message, highlights: highlights, fixIts: fixIts))
64+
diagnostics.append(Diagnostic(node: Syntax(node), position: position, message: message, highlights: highlights, notes: notes, fixIts: fixIts))
5765
self.handledNodes.append(contentsOf: handledNodes)
5866
}
5967

6068
/// Produce a diagnostic.
61-
func addDiagnostic<T: SyntaxProtocol>(_ node: T,position: AbsolutePosition? = nil, _ message: StaticParserError, highlights: [Syntax] = [], fixIts: [FixIt] = [], handledNodes: [SyntaxIdentifier] = []) {
62-
addDiagnostic(node, position: position, message as DiagnosticMessage, highlights: highlights, fixIts: fixIts, handledNodes: handledNodes)
69+
func addDiagnostic<T: SyntaxProtocol>(
70+
_ node: T,
71+
position: AbsolutePosition? = nil,
72+
_ message: StaticParserError,
73+
highlights: [Syntax] = [],
74+
notes: [Note] = [],
75+
fixIts: [FixIt] = [],
76+
handledNodes: [SyntaxIdentifier] = []
77+
) {
78+
addDiagnostic(
79+
node,
80+
position: position,
81+
message as DiagnosticMessage,
82+
highlights: highlights,
83+
notes: notes,
84+
fixIts: fixIts,
85+
handledNodes: handledNodes
86+
)
6387
}
6488

6589
/// Whether the node should be skipped for diagnostic emission.

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ public extension ParserError {
3434
}
3535
}
3636

37+
public protocol ParserNote: NoteMessage {
38+
var fixItID: MessageID { get }
39+
}
40+
41+
public extension ParserNote {
42+
static var fixItID: MessageID {
43+
return MessageID(domain: diagnosticDomain, id: "\(self)")
44+
}
45+
46+
var fixItID: MessageID {
47+
return Self.fixItID
48+
}
49+
}
50+
3751
public protocol ParserFixIt: FixItMessage {
3852
var fixItID: MessageID { get }
3953
}

Sources/SwiftSyntax/gyb_generated/SyntaxEnum.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ public enum SyntaxEnum {
730730
case .catchClause:
731731
return "'catch' clause"
732732
case .poundAssertStmt:
733-
return "'#assert' statement"
733+
return "'#assert' directive"
734734
case .genericWhereClause:
735735
return "'where' clause"
736736
case .genericRequirementList:

0 commit comments

Comments
 (0)