- Notifications
You must be signed in to change notification settings - Fork4.1k
File-based program directive diagnostics in editor#79421
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Conversation
{ | ||
public int Line { get; set; } | ||
public int Character { get; set; } | ||
} |
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.
does this need to be mutable?
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.
deserialization should work if we use init accessors instead.
[Export(typeof(IDiagnosticSourceProvider)), Shared] | ||
[method: ImportingConstructor] | ||
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
internal class VirtualProjectXmlDiagnosticSourceProvider(VirtualProjectXmlProvider virtualProjectXmlProvider) : IDiagnosticSourceProvider |
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.
Sealed
{ | ||
public bool IsDocument => true; | ||
public const string FileBasedPrograms = nameof(FileBasedPrograms); |
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.
consts above everything.
return ValueTask.FromResult(sources); | ||
} | ||
private class DiagnosticSource(Document document, VirtualProjectXmlProvider virtualProjectXmlProvider) : IDiagnosticSource |
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.
sealed.
return ValueTask.FromResult(sources); | ||
} | ||
private class DiagnosticSource(Document document, VirtualProjectXmlProvider virtualProjectXmlProvider) : IDiagnosticSource |
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 name this something a little more specific. as this makes searching for types hard.
if (string.IsNullOrEmpty(document.FilePath)) | ||
return []; | ||
var simpleDiagnostics = await virtualProjectXmlProvider.GetCachedDiagnosticsAsync(document.FilePath, cancellationToken); |
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 asking for cached diags, why not just ask for the diags appropriate for Document. ANd if they have been cached they are returned, otherwise they are computed.
I'm very wary about this producing stale data that is then not updated properly as changed come in.
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.
Seems plausible, I am actually wondering what would happen if we simply changed this to "get the virtual project" again, using whatever content is in the current version of the doc, which will take a second or two usually, and forward the diagnostics. Then I guess this source could be marked as live.
In the medium term I think we also want to try to forward certain msbuild diagnostics (e.g. if a bad id or version is used with#:package
). But that could just be a different thing reported from a different source.
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.
...except that run-api wants to take a file path for the file-based program, which it will read from disk. That means the diagnostics coming back would still be stale until user hits save.
For the short term I think it is going to be better to start with the "non-live" diagnostics which are essentially driven by design time build, and in medium term make the necessary adjustments to make the subset of diagnostics live, which can be made live.
if (simpleDiagnostics.IsDefaultOrEmpty) | ||
return []; | ||
var diagnosticDatas = ImmutableArray.CreateBuilder<DiagnosticData>(simpleDiagnostics.Length); |
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.
vardiagnosticDatas=ImmutableArray.CreateBuilder<DiagnosticData>(simpleDiagnostics.Length); | |
vardiagnosticDatas=newFixedSizeArrayBuilder<DiagnosticData>(simpleDiagnostics.Length); |
{ | ||
var location = new FileLinePositionSpan(simpleDiagnostic.Location.Path, simpleDiagnostic.Location.Span.ToLinePositionSpan()); | ||
var diagnosticData = new DiagnosticData( | ||
id: "FBP", |
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.
this is... odd.
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.
The dotnet run-api does not have IDs for its diagnostics, at least for the moment, so a magic diagnostic ID needs to be used. Compare with the TaskListDiagnosticSource.
Possibly an ID like FileBasedPrograms may be at least marginally more clear here.
severity: DiagnosticSeverity.Error, | ||
defaultSeverity: DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
warningLevel: 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.
can you doc the reasons for some of htese values?
return document.Project; | ||
} | ||
public bool IsLiveSource() => 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.
is this true?
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.
Currently these are errors which are essentially only updated when a design time build is performed. That is something which only happens on save. Per the interface methoddoc comment:
Non 'live' errors (aka "build errors") are recognized to potentially represent stale results from a point in the past when the computation occurred.
using (await _gate.DisposableWaitAsync(cancellationToken)) | ||
{ | ||
_diagnosticsByFilePath[documentFilePath] = project.Diagnostics; | ||
} | ||
return (project.Content, project.Diagnostics); | ||
} |
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.
the staleness issue semes real here. it looks like we could return incorrect diagnostics for a solution snapshot. and it's unclear what would snap the ide out of that state.
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.
This is updated each time a design time build occurs, which for the moment occurs whenever the file-based program is saved to disk.
The cached diagnostics would probably need to be updated in the other code paths also. e.g. if run-api completely fails, the stale diagnostics we already had in the cache are likely not useful and should be cleared out.
So, when the "Save" happens, what ensures that the entire pipeline even knows to run again to then get these cached diagnostics? For context, saving a file doesn't not change our content checksums, and we often (but not always) drive these experiences off of content checksums. So we'd need something else to mix into the system to even know to get diagnostics agian.And importantly, that would either have to runafter the diagnostics were definitely computed and cached,or it would have to wait on those being computed when it did its work in order for the user to get the correct diagnostics results. All this is possible. We just have to be careful and clear (and the code should be evident as to how it is ccomplishing this). Thanks! :) |
A file watcher will kick off a design time build when the file-based program is saved. For the pipeline to run again, the TODO from PR description needs to be addressed.
|
Answered offline. |
Added use of IDiagnosticRefresher. Seems decently responsive for the moment. We should still track down making this work with in-memory-only changes in the near-to-medium term so that long design time builds, autosave being turned off, etc. will not mess up the user experience. fbp-directive-diagnostics.mp4Also, if we break things so badly that run-api doesn't even give back a virtual project, we should probably clear out any stale diagnostics from the last run. |
foreach (var simpleDiagnostic in simpleDiagnostics) | ||
{ | ||
var location = new FileLinePositionSpan(simpleDiagnostic.Location.Path, simpleDiagnostic.Location.Span.ToLinePositionSpan()); | ||
var diagnosticData = new DiagnosticData( |
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.
will these ever be warnings? Will people ever be able to suppress these errors, or configure the severity?
If not, then diagnostic source seems like an appropriate way to go, but if not we may want to consider having these come from an analyzer, so all the suppression / configuration infrastructure could come into play.
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.
For the diagnostics we get through here today, it means something is wrong with the directives syntactically, and for exampledotnet run
of the same file will fail.
// check for difference, and signal to host to update if so. | ||
if (previousCachedDiagnostics.IsDefault || !project.Diagnostics.SequenceEqual(previousCachedDiagnostics)) | ||
diagnosticRefresher.RequestWorkspaceRefresh(); |
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.
this looks correct. generally the guidance I have on using the refresher is to use it as sparingly as possible because it refreshesall diagnostics. It looks like you're already checking to make sure there are changes which is good.
But the less that goes through this the better. Ideally we can get most diagnostics from the actual Roslyn snapshots, and then the normal on-type updates handle most changes.
_diagnosticsByFilePath[documentFilePath] = project.Diagnostics; | ||
// check for difference, and signal to host to update if so. | ||
if (previousCachedDiagnostics.IsDefault || !project.Diagnostics.SequenceEqual(previousCachedDiagnostics)) |
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.
How do we remove diagnostics? It looks really bad when we have stale diagnostics, so we definitely need to ensure we're clearing them when the file based project is deleted / renamed / otherwise changes contexts?
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 also probably need to remove them from the_diagnosticsByFilePath
map if the file/project is deleted, otherwise we have a small leak
Uh oh!
There was an error while loading.Please reload this page.
#78688
IsLiveSource() => false
.VirtualProjectXmlProvider
gets a virtual project, it caches the diagnostics from the run-api result in a dictionaryIDiagnosticSource
has a reference to theVirtualProjectXmlProvider
and accesses the latest diagnostics for the project from itIDiagnosticSource
should be queried again. I'm not sure how to do this.Edit: resolved by using IDiagnosticRefresher.FileBasedProgramsProjectSystem
and looked up somehow.IDiagnosticSourceProvider
needs to be registered through MEF the current factoring felt fairly straightforward. However we could imagine wanting to also surface the design time build diagnostics through this provider somehow. In which case we would need the project system to cache diagnostics instead, in some location where theIDiagnosticSource
could access them.IDiagnosticSourceProvider
could be a parameter to theFileBasedProgramsProjectSystem
and when the project system has diagnostics it wants cached/potentially surfaced it could just call into the diagnostic source provider.@jasonmalinowski@dibarbet Interested in any high level feedback on the approach and advice on the TODO also.