-
Notifications
You must be signed in to change notification settings - Fork 441
Merge missing token diagnostics if they occur at the same source location #838
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
Conversation
9a20d2f
to
3ad5464
Compare
fixIts = [] | ||
} | ||
|
||
// Find the previous sibling, skipping over siblings that don't actually contain any source text. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just use .sourceAccurate
here rather than checking for missing?
Rather than grabbing previous siblings and replacing previous diagnostics, could we instead grab next siblings and just create the one diagnostic at this point? That would avoid looking through all diagnostics each time. IMO we also want to merge the fix-its, which that change would also make easier.
We may want to start splitting these files up as well. Ie. the generation + message for this one could be defined in a MissingSyntaxDiagnostic.swift
instead (it's a fair amount of code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just use
.sourceAccurate
here rather than checking for missing?
No, because then we wouldn’t find missing tokens, which are skipped in the .sourceAccurate
traversal mode.
Rather than grabbing previous siblings and replacing previous diagnostics, could we instead grab next siblings and just create the one diagnostic at this point? That would avoid looking through all diagnostics each time. IMO we also want to merge the fix-its, which that change would also make easier.
Good idea. I thought that this would be more difficult than looking behind but it turns out that it’s actually simpler.
We may want to start splitting these files up as well. Ie. the generation + message for this one could be defined in a
MissingSyntaxDiagnostic.swift
instead (it's a fair amount of code).
Good idea.
} | ||
private extension String { | ||
/// Remove any leading or trailing whitespace | ||
func trimmingWhitespaces() -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoiding a Foundation
import I assume? Crazy that we have drop
and trimPrefix
but not a trimSuffix
even though drop
and trimPrefix
are basically equivalent. Also note that this isn't "whitespace" but rather "spaces".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly. I wished we had functionality like this in the stdlib but apparently we don’t 😬
Good catch with spaces vs whitespace
init(missingNodes: [Syntax]) { | ||
assert(!missingNodes.isEmpty) | ||
self.missingNodes = missingNodes | ||
self.commonParent = missingNodes.first!.parent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the spirit of not-crashing IMO it would be nice to avoid !
here and just return some generic message if they're empty.
let (rawKind, text) = missingToken.tokenKind.decomposeToRaw() | ||
if let text = text, !text.isEmpty { | ||
let presentToken = TokenSyntax(missingToken.tokenKind, presence: .present) | ||
newPart = .sourceText("\(Syntax(Format().format(syntax: presentToken)))") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to have an API where you don't have to instantiate Format
first. IMO this could even just be on the syntax node's themselves (ie. presentToken.formatted()
).
|
||
var description: String { | ||
switch self { | ||
case .sourceText(let content): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could take a TokenSyntax
instead. Both uses pass the same "\(Syntax(Format().format(syntax: presentToken)))"
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can’t take a TokenSyntax
because we are merging these further below. But [TokenSyntax]
is possible. I changed it to that.
switch missingToken.tokenKind { | ||
case .rightBrace, .rightAngle, .rightParen, .rightSquareBracket: | ||
if commonParent.children(viewMode: .fixedUp).last?.as(TokenSyntax.self) == missingToken { | ||
if nodePosition == .toStart { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in
is the default, just if nodePosition != .toStart
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored this logic quite a bit to make it simpler so it doesn’t use ContextType
anymore.
} | ||
} | ||
return message | ||
if let commonParent = commonParent, let parentTypeName = commonParent.nodeTypeNameForDiagnostics(allowSourceFile: false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove this check with the guard above (and then all the commonParent
checks as well)
public let attributeName: TokenSyntax | ||
|
||
public var message: String { | ||
return "Expected argument for '@\(attributeName)' attribute" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument
or arguments
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think based on the way we currently model attributes we don’t really know whether a particular attribute requires a single argument or multiple… Might be something to tackle in a follow-up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument(s)
😅
Sources/SwiftSyntax/Syntax.swift
Outdated
let nextSiblingIndex = siblings.index(after: self.index) | ||
if let previousAbsoluteRawSibling = siblings[nextSiblingIndex...].last { | ||
return Syntax(SyntaxData(previousAbsoluteRawSibling, parent: parent)) | ||
} else { | ||
return nil | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth adding some quick tests for these - this should be .first
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After making some changes, it turns out that we don’t need previousSibling
and nextSibling
anymore.
DiagnosticSpec(message: "Expected 'set)' to end modifier"), | ||
DiagnosticSpec(message: "Unexpected text 'get, didSet' in variable") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth specializing this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you expect there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally 'private' only accepts 'set'
with a replacement for get, didSet
-> set
and then a Expected ')' to end modifier
. And I'd only expect that if the unexpected was one of get/didSet/willSet
.
This is fine for now, could add an issue for it though if you agree.
DiagnosticSpec(locationMarker: "EXPECTED_DECL", message: "Expected declaration after '{' in struct"), | ||
DiagnosticSpec(locationMarker: "EXPECTED_DECL", message: "Expected declaration in struct"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I agree with doing this, especially since the location would be the "after" anyway (though I suppose not all IDEs display the actual column).
@@ -395,8 +395,8 @@ final class ExpressionTests: XCTestCase { | |||
\#^AFTER_SLASH^#\(#^AFTER_PAREN^# | |||
"""##, | |||
diagnostics: [ | |||
DiagnosticSpec(locationMarker: "AFTER_SLASH", message: #"Expected root and expression after '\' in key path"#), | |||
DiagnosticSpec(locationMarker: "AFTER_PAREN", message: "Expected value after '(' in tuple"), | |||
DiagnosticSpec(locationMarker: "AFTER_SLASH", message: #"Expected root and expression in key path"#), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an issue up for key paths in general? root
isn't required, but it looks like we're calling parsePostfixExpression
if there's no .
, that returns missing, and thus we get an expected "root" here where that makes no sense (the type isn't required, really I'd expect "Expected '.' and expression in key path" here).
@@ -467,7 +467,7 @@ final class ExpressionTests: XCTestCase { | |||
diagnostics: [ | |||
DiagnosticSpec(message: "Expected identifier in '#keyPath' expression"), | |||
DiagnosticSpec(message: "Expected ')' to end '#keyPath' expression"), | |||
DiagnosticSpec(locationMarker: "MISSING_VALUE", message: "Expected value after ':' in function call"), | |||
DiagnosticSpec(locationMarker: "MISSING_VALUE", message: "Expected value in function call"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally this would be "argument" but that's not particularly easy with the current scheme 🤔.
@@ -476,7 +476,7 @@ final class ExpressionTests: XCTestCase { | |||
"[(Int) -> #^DIAG^#throws Int]()", | |||
diagnostics: [ | |||
// FIXME: We should suggest to move 'throws' in front of '->' | |||
DiagnosticSpec(message: "Expected expression after '->' in array element"), | |||
DiagnosticSpec(message: "Expected expression in array element"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignoring the throws problem, this not being something like "expected return type" is also an issue.
DiagnosticSpec(locationMarker: "DIAG_1", message: "Unexpected text '..' in function type"), | ||
DiagnosticSpec(locationMarker: "END", message: "Expected type after '->' in function type"), | ||
DiagnosticSpec(locationMarker: "END", message: "Expected type in function type"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the above, without after '->'
this would really need to be expected return type
.
106d379
to
d4bab52
Compare
DiagnosticSpec(message: "Expected 'set)' to end modifier"), | ||
DiagnosticSpec(message: "Unexpected text 'get, didSet' in variable") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally 'private' only accepts 'set'
with a replacement for get, didSet
-> set
and then a Expected ')' to end modifier
. And I'd only expect that if the unexpected was one of get/didSet/willSet
.
This is fine for now, could add an issue for it though if you agree.
public let attributeName: TokenSyntax | ||
|
||
public var message: String { | ||
return "Expected argument for '@\(attributeName)' attribute" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument(s)
😅
@@ -6,16 +6,19 @@ final class AttributeTests: XCTestCase { | |||
func testMissingArgumentToAttribute() { | |||
AssertParse( | |||
""" | |||
@_dynamicReplacement(#^DIAG_1^# | |||
@_dynamicReplacement(#^DIAG^# | |||
func #^DIAG_2^#test_dynamic_replacement_for2() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove DIAG_2
DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected ')' to end attribute", fixIts: ["Insert ')'"]), | ||
], | ||
fixedSource: """ | ||
@differentiable(reverse wrt: <#syntax#>,where T = <#type#>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be 'parameters'? Not much better than 'syntax` though 🤔
DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected member block in class"), | ||
], | ||
fixedSource: """ | ||
}class C {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be removing the first }
?
@@ -0,0 +1,38 @@ | |||
//===--- DiagnosticExtensions.swift ---------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add all the new files to CMakeLists.txt?
d4bab52
to
1289d74
Compare
@swift-ci Please test |
No description provided.