-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Compute TextChange based on syntax instead of text #67120
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
|
@jasonmalinowski @CyrusNajmabadi Not sure how to trigger this exception in test. Any suggestion? |
|
@genlu Off the top of my head no idea -- although I hit it again today. :-/ Do we have dumps from telemetry? Those might let you look at the edit history of the buffer and see if there was a certain lead up of edits. |
|
@jasonmalinowski Looks like this is not a completely new issue. I saw same stacktrace from 17.2 during last week. |
| { | ||
| var formattingChangeRanges = formattingChanges.SelectAsArray(f => f.ToTextChangeRange()); | ||
| if (formattingChangeRanges.IsEmpty) | ||
| return ImmutableArray.Create(newLineEdit); |
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.
@jasonmalinowski tbh, I'm not sure how could we make the assumption that calling FormatTrackingSpan with a newline inserted would always return a non-empty change. Wouldn't it depend on user settings?
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.
@genlu I'm not sure I follow the question.
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.
We add a newline between brackets in some cases, and then pass the changed document to format the span between brackets. The assumption is the format change will never be empty as long as we inserted a newline, which I don't think it's guaranteed.
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.
yeah, i don't expect we would have that guarantee. tbh, i really don't get what this code (including prior to your change) is trying to do and how it is going about trying to do it. Do you feel confident we have good test coverage here to catch issues?
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.
OK, after a few attempts on tweaking the options, I finally get a test to repro the crash in both 17.5 branch (which is before #66575) and main
[WpfFact]
public void Test()
{
var code = @"namespace NS1
$$";
var expected = @"namespace NS1
{}";
var expectedAfterReturn = @"namespace NS1
{
}";
var globalOptions = new OptionsCollection(LanguageNames.CSharp)
{
{ FormattingOptions2.SmartIndent, FormattingOptions2.IndentStyle.None },
{ AutoFormattingOptionsStorage.FormatOnCloseBrace, false },
};
using var session = CreateSession(code, globalOptions);
Assert.NotNull(session);
CheckStart(session.Session);
Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText());
CheckReturn(session.Session, 0, expectedAfterReturn);
}[xUnit.net 00:00:05.25] Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AutomaticCompletion.AutomaticBraceCompletionTests.Test [FAIL]
[xUnit.net 00:00:05.25] System.ArgumentException : newChanges
[xUnit.net 00:00:05.25] Stack Trace:
[xUnit.net 00:00:05.25] src\Compilers\Core\Portable\InternalUtilities\TextChangeRangeExtensions.cs(129,0): at Roslyn.Utilities.TextChangeRangeExtensions.Merge(ImmutableArray`1 oldChanges, ImmutableArray`1 newChanges)
[xUnit.net 00:00:05.25] src\Features\CSharp\Portable\BraceCompletion\AbstractCurlyBraceOrBracketCompletionService.cs(176,0): at Microsoft.CodeAnalysis.CSharp.BraceCompletion.AbstractCurlyBraceOrBracketCompletionService.<GetTextChangeAfterReturn>g__GetMergedChanges|5_2(TextChange newLineEdit, ImmutableArray`1 formattingChanges, SourceText formattedText)
[xUnit.net 00:00:05.25] src\Features\CSharp\Portable\BraceCompletion\AbstractCurlyBraceOrBracketCompletionService.cs(153,0): at Microsoft.CodeAnalysis.CSharp.BraceCompletion.AbstractCurlyBraceOrBracketCompletionService.GetTextChangeAfterReturn(BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken)
[xUnit.net 00:00:05.25] src\EditorFeatures\Core\AutomaticCompletion\BraceCompletionSessionProvider.BraceCompletionSession.cs(290,0): at Microsoft.CodeAnalysis.AutomaticCompletion.BraceCompletionSessionProvider.BraceCompletionSession.PostReturn()
[xUnit.net 00:00:05.25] src\EditorFeatures\TestUtilities\AutomaticCompletion\AbstractAutomaticBraceCompletionTests.cs(86,0): at Microsoft.CodeAnalysis.Editor.UnitTests.AutomaticCompletion.AbstractAutomaticBraceCompletionTests.CheckReturn(IBraceCompletionSession session, Int32 indentation, String result)
[xUnit.net 00:00:05.25] src\EditorFeatures\CSharpTest\AutomaticCompletion\AutomaticBraceCompletionTests.cs(1694,0): at Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AutomaticCompletion.AutomaticBraceCompletionTests.Test()
src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs
Outdated
Show resolved
Hide resolved
| var newLineString = options.FormattingOptions.NewLine; | ||
| newLineEdit = new TextChange(new TextSpan(closingToken.FullSpan.Start, 0), newLineString); | ||
| var closingToken = document.Root.FindToken(closingPoint - 1); | ||
| Debug.Assert(IsValidClosingBraceToken(closingToken)); |
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 dont' love that thsi is only an assert. maybe bail out here since if we don't find the close token, everything is going to go bad?
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 don't think these are actually needed. If we can't find the token here then something outside this method must be seriously wrong. In that case I guess we'd like this to crash so we can get dump?
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 guess my question is: how do we know we'll have a close token here? I don't know this code well enough, so this is a question to you about what code ran prior to this that absolutely made us certain we have a good tree, and the tree will think there's a clsoe brace at this position :)
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.
Ah, I see. From what I saw, the code prior to this handles the insertion of closing brace here. Only then this method would be called when 'return' is typed in between the braces. In other words, the close brace is added as apart of session start.
src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs
Outdated
Show resolved
Hide resolved
|
@CyrusNajmabadi Any more comments? |
|
looking. |
| CheckStart(session.Session); | ||
| Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText()); | ||
|
|
||
| CheckReturn(session.Session, 0, expectedAfterReturn); |
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 you clarify what is was about this prior code that was causing the problem?
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.
We previous assume calling formatter on the brace pair (including added newline in-between) would always cause text changes. But it's not true with certain option combination, which would cause us the throw.
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.
thanks!
| document = document.WithChangedRoot(rootToFormat, cancellationToken); | ||
| var rootToFormat = document.Root.ReplaceToken(closingToken, newClosingToken); | ||
|
|
||
| newClosingToken = rootToFormat.FindToken(closingPoint - 1); |
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.
instead of this, use an annotation. that will be much safer and more correct. i really just do not trust thsi 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.
Sure. Fixed
| closingPoint = newClosingToken.Span.End; | ||
|
|
||
| var textChangeLength = newClosingToken.Span.End - closingToken.Span.End; | ||
| newLineEdit = new TextChange(new TextSpan(closingToken.FullSpan.Start, 0), newClosingToken.ToFullString()[..textChangeLength]); |
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.
ok. trying to reason about this. we're saying that we go to the start of where the close token was. then we figure out the distance the close token moved. Then we grab that distance from the start of teh new closing token?
I'm not really getting it. Can we be explicit about what text we're trying to grab here? It looks like it's just supposed to be the potential newlines at the start of the close token? Can we just grab this from it's trivia instead? Or, even better, can we just grab from the source-text?
The reason i don't like this is that ..TextChangeLength looks like it could grab random stuff (including the token contents itself).
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.
Fixed
src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs
Outdated
Show resolved
Hide resolved
| // Depending on options, we might not get any formatting change. | ||
| // In this case, the newline edit is the only change. | ||
| if (formattingChanges.IsEmpty) | ||
| return ImmutableArray.Create(newLineEdit.Value); |
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.
consdier moving this into GetMergedChanges.
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.
Done
and check for empty change range before merge.
An attempt to fix the error reported by @jasonmalinowski
https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1758005