public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { // This provider is exported for all workspaces - so limit it to just our workspace. var(document, span, cancellationToken) = context; if (document.Project.Solution.Workspace.Kind != WorkspaceKind.AnyCodeRoslynWorkspace) { return; } var lspClient = _roslynLspClientServiceFactory.ActiveLanguageServerClient; if (lspClient == null) { return; } var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var diagnostics = await _diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(document, span, cancellationToken : cancellationToken).ConfigureAwait(false); var diagnostic = diagnostics?.FirstOrDefault(); if (diagnostic != null) { span = diagnostic.TextSpan; } var codeActionParams = new LSP.CodeActionParams { TextDocument = ProtocolConversions.DocumentToTextDocumentIdentifier(document), Range = ProtocolConversions.TextSpanToRange(span, text) }; var commands = await lspClient.RequestAsync(LSP.Methods.TextDocumentCodeAction.ToLSRequest(), codeActionParams, cancellationToken).ConfigureAwait(false); if (commands == null) { return; } foreach (var command in commands) { if (LanguageServicesUtils.TryParseJson(command, out LSP.Command lspCommand)) { // The command can either wrap a Command or a CodeAction. // If a Command, leave it unchanged; we want to dispatch it to the host to execute. // If a CodeAction, unwrap the CodeAction so the guest can run it locally. var commandArguments = lspCommand.Arguments.Single(); if (LanguageServicesUtils.TryParseJson(commandArguments, out LSP.CodeAction lspCodeAction)) { context.RegisterRefactoring(new RoslynRemoteCodeAction(document, lspCodeAction.Command, lspCodeAction.Edit, lspCodeAction.Title, lspClient)); } else { context.RegisterRefactoring(new RoslynRemoteCodeAction(document, lspCommand, lspCommand?.Title, lspClient)); } } } }
/// <summary> /// Return up to date diagnostics for the given span for the document /// <para> /// This can be expensive since it is force analyzing diagnostics if it doesn't have up-to-date one yet. If /// <paramref name="diagnosticId"/> is not null, it gets diagnostics only for this given <paramref /// name="diagnosticId"/> value. /// </para> /// </summary> public static Task <ImmutableArray <DiagnosticData> > GetDiagnosticsForSpanAsync(this IDiagnosticAnalyzerService service, Document document, TextSpan?range, string?diagnosticId = null, bool includeSuppressedDiagnostics = false, CodeActionRequestPriority priority = CodeActionRequestPriority.None, Func <string, IDisposable?>?addOperationScope = null, CancellationToken cancellationToken = default) { Func <string, bool>?shouldIncludeDiagnostic = diagnosticId != null ? id => id == diagnosticId : null; return(service.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic, includeSuppressedDiagnostics, priority, addOperationScope, cancellationToken)); }
protected override Task <ImmutableArray <DiagnosticData> > GetDiagnosticsAsync( RequestContext context, Document document, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) { // For open documents, directly use the IDiagnosticAnalyzerService. This will use the actual snapshots // we're passing in. If information is already cached for that snapshot, it will be returned. Otherwise, // it will be computed on demand. Because it is always accurate as per this snapshot, all spans are correct // and do not need to be adjusted. return(_analyzerService.GetDiagnosticsForSpanAsync(document, range: null, cancellationToken: cancellationToken)); }
public async Task <ImmutableArray <CodeFixCollection> > GetFixesAsync(Document document, TextSpan range, bool includeSuppressionFixes, CancellationToken cancellationToken) { // REVIEW: this is the first and simplest design. basically, when ctrl+. is pressed, it asks diagnostic service to give back // current diagnostics for the given span, and it will use that to get fixes. internally diagnostic service will either return cached information // (if it is up-to-date) or synchronously do the work at the spot. // // this design's weakness is that each side don't have enough information to narrow down works to do. it will most likely always do more works than needed. // sometimes way more than it is needed. (compilation) Dictionary <TextSpan, List <DiagnosticData> > aggregatedDiagnostics = null; foreach (var diagnostic in await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, cancellationToken: cancellationToken).ConfigureAwait(false)) { if (diagnostic.IsSuppressed) { continue; } cancellationToken.ThrowIfCancellationRequested(); aggregatedDiagnostics = aggregatedDiagnostics ?? new Dictionary <TextSpan, List <DiagnosticData> >(); aggregatedDiagnostics.GetOrAdd(diagnostic.TextSpan, _ => new List <DiagnosticData>()).Add(diagnostic); } if (aggregatedDiagnostics == null) { return(ImmutableArray <CodeFixCollection> .Empty); } var result = ArrayBuilder <CodeFixCollection> .GetInstance(); foreach (var spanAndDiagnostic in aggregatedDiagnostics) { await AppendFixesAsync( document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, result, cancellationToken).ConfigureAwait(false); } if (result.Count > 0) { // sort the result to the order defined by the fixers var priorityMap = _fixerPriorityMap[document.Project.Language].Value; result.Sort((d1, d2) => priorityMap.ContainsKey((CodeFixProvider)d1.Provider) ? (priorityMap.ContainsKey((CodeFixProvider)d2.Provider) ? priorityMap[(CodeFixProvider)d1.Provider] - priorityMap[(CodeFixProvider)d2.Provider] : -1) : 1); } // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive if (document.Project.Solution.Workspace.Kind != WorkspaceKind.Interactive && includeSuppressionFixes) { foreach (var spanAndDiagnostic in aggregatedDiagnostics) { await AppendSuppressionsAsync( document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, result, cancellationToken).ConfigureAwait(false); } } return(result.ToImmutableAndFree()); }
public override async Task <IEnumerable <Diagnostic> > GetDocumentSpanDiagnosticsAsync(Document document, TextSpan fixAllSpan, CancellationToken cancellationToken) { bool shouldIncludeDiagnostic(string id) => _diagnosticIds == null || _diagnosticIds.Contains(id); var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync(document, fixAllSpan, shouldIncludeDiagnostic, _includeSuppressedDiagnostics, cancellationToken : cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); return(await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false)); }
public async Task <IEnumerable <CodeFixCollection> > GetFixesAsync(Document document, TextSpan range, bool includeSuppressionFixes, CancellationToken cancellationToken) { // REVIEW: this is the first and simplest design. basically, when ctrl+. is pressed, it asks diagnostic service to give back // current diagnostics for the given span, and it will use that to get fixes. internally diagnostic service will either return cached information // (if it is up-to-date) or synchronously do the work at the spot. // // this design's weakness is that each side don't have enough information to narrow down works to do. it will most likely always do more works than needed. // sometimes way more than it is needed. (compilation) Dictionary <TextSpan, List <DiagnosticData> > aggregatedDiagnostics = null; foreach (var diagnostic in await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); aggregatedDiagnostics = aggregatedDiagnostics ?? new Dictionary <TextSpan, List <DiagnosticData> >(); aggregatedDiagnostics.GetOrAdd(diagnostic.TextSpan, _ => new List <DiagnosticData>()).Add(diagnostic); } var result = new List <CodeFixCollection>(); if (aggregatedDiagnostics == null) { return(result); } foreach (var spanAndDiagnostic in aggregatedDiagnostics) { result = await AppendFixesAsync(document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, result, cancellationToken).ConfigureAwait(false); } if (result.Any()) { // sort the result to the order defined by the fixers var priorityMap = _fixerPriorityMap[document.Project.Language].Value; result.Sort((d1, d2) => priorityMap.ContainsKey((CodeFixProvider)d1.Provider) ? (priorityMap.ContainsKey((CodeFixProvider)d2.Provider) ? priorityMap[(CodeFixProvider)d1.Provider] - priorityMap[(CodeFixProvider)d2.Provider] : -1) : 1); } if (includeSuppressionFixes) { foreach (var spanAndDiagnostic in aggregatedDiagnostics) { result = await AppendSuppressionsAsync(document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, result, cancellationToken).ConfigureAwait(false); } } return(result); }
private async Task <ImmutableArray <(string diagnosticId, string?title)> > GetThirdPartyDiagnosticIdsAndTitlesAsync(Document document, CancellationToken cancellationToken) { var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var range = new TextSpan(0, tree.Length); // Compute diagnostics for everything that is not an IDE analyzer var diagnostics = (await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), includeCompilerDiagnostics: true, includeSuppressedDiagnostics: false, cancellationToken: cancellationToken).ConfigureAwait(false)); // ensure more than just known diagnostics were returned if (!diagnostics.Any()) { return(ImmutableArray <(string diagnosticId, string?title)> .Empty); } return(diagnostics.SelectAsArray(static d => (d.Id, d.Title)).Distinct());
public static Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForSpanAsync(this IDiagnosticAnalyzerService service, Document document, TextSpan range, string? diagnosticId = null, bool includeSuppressedDiagnostics = false, Func<string, IDisposable?>? addOperationScope = null, CancellationToken cancellationToken = default) => service.GetDiagnosticsForSpanAsync(document, range, diagnosticId, includeSuppressedDiagnostics, CodeActionRequestPriority.None, addOperationScope, cancellationToken);
public async Task<ImmutableArray<CodeFixCollection>> GetFixesAsync(Document document, TextSpan range, bool includeConfigurationFixes, bool isBlocking, Func<string, IDisposable?> addOperationScope, CancellationToken cancellationToken) { // REVIEW: this is the first and simplest design. basically, when ctrl+. is pressed, it asks diagnostic service to give back // current diagnostics for the given span, and it will use that to get fixes. internally diagnostic service will either return cached information // (if it is up-to-date) or synchronously do the work at the spot. // // this design's weakness is that each side don't have enough information to narrow down works to do. it will most likely always do more works than needed. // sometimes way more than it is needed. (compilation) // group diagnostics by their diagnostics span // invariant: later code gathers & runs CodeFixProviders for diagnostics with one identical diagnostics span (that gets set later as CodeFixCollection's TextSpan) // order diagnostics by span. SortedDictionary<TextSpan, List<DiagnosticData>>? aggregatedDiagnostics = null; foreach (var diagnostic in await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, diagnosticIdOpt: null, includeConfigurationFixes, addOperationScope, cancellationToken).ConfigureAwait(false)) { if (diagnostic.IsSuppressed) { continue; } cancellationToken.ThrowIfCancellationRequested(); aggregatedDiagnostics ??= new SortedDictionary<TextSpan, List<DiagnosticData>>(); aggregatedDiagnostics.GetOrAdd(diagnostic.GetTextSpan(), _ => new List<DiagnosticData>()).Add(diagnostic); } if (aggregatedDiagnostics == null) { return ImmutableArray<CodeFixCollection>.Empty; } // Order diagnostics by DiagnosticId so the fixes are in a deterministic order. foreach (var diagnosticsWithSpan in aggregatedDiagnostics.Values) { diagnosticsWithSpan.Sort(s_diagnosticDataComparisonById); } // append fixes for all diagnostics with the same diagnostics span using var resultDisposer = ArrayBuilder<CodeFixCollection>.GetInstance(out var result); foreach (var spanAndDiagnostic in aggregatedDiagnostics) { await AppendFixesAsync( document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, fixAllForInSpan: false, isBlocking, result, addOperationScope, cancellationToken).ConfigureAwait(false); } if (result.Count > 0) { // sort the result to the order defined by the fixers ImmutableDictionary<CodeFixProvider, int> priorityMap = _fixerPriorityMap[document.Project.Language].Value; result.Sort((d1, d2) => GetValue(d1).CompareTo(GetValue(d2))); int GetValue(CodeFixCollection c) => priorityMap.TryGetValue((CodeFixProvider)c.Provider, out var value) ? value : int.MaxValue; } // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive if (document.Project.Solution.Workspace.Kind != WorkspaceKind.Interactive && includeConfigurationFixes) { foreach (var spanAndDiagnostic in aggregatedDiagnostics) { await AppendConfigurationsAsync( document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, result, cancellationToken).ConfigureAwait(false); } } return result.ToImmutable(); }