private static bool EntrySupportsSuppressionState(ITableEntryHandle entryHandle, out bool isRoslynEntry, out bool isSuppressedEntry, out bool isCompilerDiagnosticEntry, out bool isNoLocationDiagnosticEntry) { string filePath; isNoLocationDiagnosticEntry = !entryHandle.TryGetValue(StandardTableColumnDefinitions.DocumentName, out filePath) || string.IsNullOrEmpty(filePath); int index; var roslynSnapshot = GetEntriesSnapshot(entryHandle, out index); if (roslynSnapshot == null) { isRoslynEntry = false; isCompilerDiagnosticEntry = false; return(IsNonRoslynEntrySupportingSuppressionState(entryHandle, out isSuppressedEntry)); } var diagnosticData = roslynSnapshot?.GetItem(index)?.Primary; if (!IsEntryWithConfigurableSuppressionState(diagnosticData)) { isRoslynEntry = false; isSuppressedEntry = false; isCompilerDiagnosticEntry = false; return(false); } isRoslynEntry = true; isSuppressedEntry = diagnosticData.IsSuppressed; isCompilerDiagnosticEntry = SuppressionHelpers.IsCompilerDiagnostic(diagnosticData); return(true); }
private static IEnumerable <DiagnosticData> FilterDiagnostics(IEnumerable <DiagnosticData> diagnostics, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics) { foreach (var diagnostic in diagnostics) { var isCompilerDiagnostic = SuppressionHelpers.IsCompilerDiagnostic(diagnostic); if (onlyCompilerDiagnostics && !isCompilerDiagnostic) { continue; } if (isAddSuppression) { // Compiler diagnostics can only be suppressed in source. if (!diagnostic.IsSuppressed && (isSuppressionInSource || !isCompilerDiagnostic)) { yield return(diagnostic); } } else if (diagnostic.IsSuppressed) { yield return(diagnostic); } } }
public bool IsFixableDiagnostic(Diagnostic diagnostic) { // We only offer fix for configurable code style diagnostics which have one of more editorconfig based storage locations. // Also skip suppressed diagnostics defensively, though the code fix engine should ideally never call us for suppressed diagnostics. if ( diagnostic.IsSuppressed || SuppressionHelpers.IsNotConfigurableDiagnostic(diagnostic) || diagnostic.Location.SourceTree == null ) { return(false); } var language = diagnostic.Location.SourceTree.Options.Language; return(IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions( diagnostic.Id, language, out var options ) && !options.IsEmpty && options.All( o => o.StorageLocations.Any(l => l is IEditorConfigStorageLocation2) )); }
/// <summary> /// Returns true if an entry's suppression state can be modified. /// </summary> /// <returns></returns> private static bool IsEntryWithConfigurableSuppressionState(DiagnosticData entry) { // Compiler diagnostics with severity 'Error' are not configurable. // Additionally, diagnostics coming from build are from a snapshot (as opposed to live diagnostics) and cannot be configured. return(entry != null && !SuppressionHelpers.IsNotConfigurableDiagnostic(entry) && !entry.IsBuildDiagnostic()); }
private void OnErrorListSetSeveritySubMenuStatus(object sender, EventArgs e) { // For now, we only enable the Set severity menu when a single configurable diagnostic is selected in the error list // and we can update/create an editorconfig file for the configuration entry. // In future, we can enable support for configuring in presence of multi-selection. var command = (MenuCommand)sender; var selectedEntry = TryGetSingleSelectedEntry(); command.Visible = selectedEntry != null && !SuppressionHelpers.IsNotConfigurableDiagnostic(selectedEntry) && TryGetPathToAnalyzerConfigDoc(selectedEntry, out _, out _); command.Enabled = command.Visible && !KnownUIContexts.SolutionBuildingContext.IsActive; }
/// <summary> /// Returns true if an entry's suppression state can be modified. /// </summary> /// <returns></returns> private static bool IsEntryWithConfigurableSuppressionState(DiagnosticData entry) { return(entry != null && !SuppressionHelpers.IsNotConfigurableDiagnostic(entry)); }
private async Task <ImmutableDictionary <Document, ImmutableArray <Diagnostic> > > GetDocumentDiagnosticsToFixAsync(IEnumerable <DiagnosticData> diagnosticsToFix, Func <Project, bool> shouldFixInProject, bool filterStaleDiagnostics, CancellationToken cancellationToken) { var builder = ImmutableDictionary.CreateBuilder <DocumentId, List <DiagnosticData> >(); foreach (var diagnosticData in diagnosticsToFix.Where(IsDocumentDiagnostic)) { RoslynDebug.AssertNotNull(diagnosticData.DocumentId); if (!builder.TryGetValue(diagnosticData.DocumentId, out var diagnosticsPerDocument)) { diagnosticsPerDocument = new List <DiagnosticData>(); builder[diagnosticData.DocumentId] = diagnosticsPerDocument; } diagnosticsPerDocument.Add(diagnosticData); } if (builder.Count == 0) { return(ImmutableDictionary <Document, ImmutableArray <Diagnostic> > .Empty); } var finalBuilder = ImmutableDictionary.CreateBuilder <Document, ImmutableArray <Diagnostic> >(); var latestDocumentDiagnosticsMap = filterStaleDiagnostics ? new Dictionary <DocumentId, ImmutableHashSet <DiagnosticData> >() : null; foreach (var group in builder.GroupBy(kvp => kvp.Key.ProjectId)) { var projectId = group.Key; var project = _workspace.CurrentSolution.GetProject(projectId); if (project == null || !shouldFixInProject(project)) { continue; } if (filterStaleDiagnostics) { RoslynDebug.AssertNotNull(latestDocumentDiagnosticsMap); var uniqueDiagnosticIds = group.SelectMany(kvp => kvp.Value.Select(d => d.Id)).ToImmutableHashSet(); var latestProjectDiagnostics = (await _diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id, diagnosticIds: uniqueDiagnosticIds, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken) .ConfigureAwait(false)).Where(IsDocumentDiagnostic); latestDocumentDiagnosticsMap.Clear(); foreach (var kvp in latestProjectDiagnostics.Where(d => d.DocumentId != null).GroupBy(d => d.DocumentId !)) { latestDocumentDiagnosticsMap.Add(kvp.Key, kvp.ToImmutableHashSet()); } } foreach (var documentDiagnostics in group) { var document = project.GetDocument(documentDiagnostics.Key); if (document == null) { continue; } IEnumerable <DiagnosticData> documentDiagnosticsToFix; if (filterStaleDiagnostics) { RoslynDebug.AssertNotNull(latestDocumentDiagnosticsMap); if (!latestDocumentDiagnosticsMap.TryGetValue(document.Id, out var latestDocumentDiagnostics)) { // Ignore stale diagnostics in error list. latestDocumentDiagnostics = ImmutableHashSet <DiagnosticData> .Empty; } // Filter out stale diagnostics in error list. documentDiagnosticsToFix = documentDiagnostics.Value.Where(d => latestDocumentDiagnostics.Contains(d) || SuppressionHelpers.IsSynthesizedExternalSourceDiagnostic(d)); } else { documentDiagnosticsToFix = documentDiagnostics.Value; } if (documentDiagnosticsToFix.Any()) { var diagnostics = await documentDiagnosticsToFix.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); finalBuilder.Add(document, diagnostics); } } } return(finalBuilder.ToImmutableDictionary());
private async Task <ImmutableDictionary <Project, ImmutableArray <Diagnostic> > > GetProjectDiagnosticsToFixAsync(IEnumerable <DiagnosticData> diagnosticsToFix, Func <Project, bool> shouldFixInProject, bool filterStaleDiagnostics, CancellationToken cancellationToken) { bool isProjectDiagnostic(DiagnosticData d) => d.DataLocation == null && d.ProjectId != null; var builder = ImmutableDictionary.CreateBuilder <ProjectId, List <DiagnosticData> >(); foreach (var diagnosticData in diagnosticsToFix.Where(isProjectDiagnostic)) { if (!builder.TryGetValue(diagnosticData.ProjectId, out var diagnosticsPerProject)) { diagnosticsPerProject = new List <DiagnosticData>(); builder[diagnosticData.ProjectId] = diagnosticsPerProject; } diagnosticsPerProject.Add(diagnosticData); } if (builder.Count == 0) { return(ImmutableDictionary <Project, ImmutableArray <Diagnostic> > .Empty); } var finalBuilder = ImmutableDictionary.CreateBuilder <Project, ImmutableArray <Diagnostic> >(); var latestDiagnosticsToFixOpt = filterStaleDiagnostics ? new HashSet <DiagnosticData>() : null; foreach (var kvp in builder) { var projectId = kvp.Key; var project = _workspace.CurrentSolution.GetProject(projectId); if (project == null || !shouldFixInProject(project)) { continue; } var diagnostics = kvp.Value; IEnumerable <DiagnosticData> projectDiagnosticsToFix; if (filterStaleDiagnostics) { var uniqueDiagnosticIds = diagnostics.Select(d => d.Id).ToImmutableHashSet(); var latestDiagnosticsFromDiagnosticService = (await _diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id, diagnosticIds: uniqueDiagnosticIds, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken) .ConfigureAwait(false)); latestDiagnosticsToFixOpt.Clear(); latestDiagnosticsToFixOpt.AddRange(latestDiagnosticsFromDiagnosticService.Where(isProjectDiagnostic)); // Filter out stale diagnostics in error list. projectDiagnosticsToFix = diagnostics.Where(d => latestDiagnosticsFromDiagnosticService.Contains(d) || SuppressionHelpers.IsSynthesizedExternalSourceDiagnostic(d)); } else { projectDiagnosticsToFix = diagnostics; } if (projectDiagnosticsToFix.Any()) { var projectDiagnostics = await projectDiagnosticsToFix.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); finalBuilder.Add(project, projectDiagnostics); } } return(finalBuilder.ToImmutableDictionary()); }
/// <summary> /// Gets <see cref="DiagnosticData"/> objects for error list entries, filtered based on the given parameters. /// </summary> public async Task <ImmutableArray <DiagnosticData> > GetItemsAsync(bool selectedEntriesOnly, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, CancellationToken cancellationToken) { var builder = ImmutableArray.CreateBuilder <DiagnosticData>(); Dictionary <string, Project> projectNameToProjectMapOpt = null; Dictionary <Project, ImmutableDictionary <string, Document> > filePathToDocumentMapOpt = null; var entries = selectedEntriesOnly ? _tableControl.SelectedEntries : _tableControl.Entries; foreach (var entryHandle in entries) { cancellationToken.ThrowIfCancellationRequested(); DiagnosticData diagnosticData = null; int index; var roslynSnapshot = GetEntriesSnapshot(entryHandle, out index); if (roslynSnapshot != null) { diagnosticData = roslynSnapshot.GetItem(index)?.Primary; } else if (!isAddSuppression) { // For suppression removal, we also need to handle FxCop entries. bool isSuppressedEntry; if (!IsNonRoslynEntrySupportingSuppressionState(entryHandle, out isSuppressedEntry) || !isSuppressedEntry) { continue; } string errorCode = null, category = null, message = null, filePath = null, projectName = null; int line = -1; // FxCop only supports line, not column. var location = Location.None; if (entryHandle.TryGetValue(StandardTableColumnDefinitions.ErrorCode, out errorCode) && !string.IsNullOrEmpty(errorCode) && entryHandle.TryGetValue(StandardTableColumnDefinitions.ErrorCategory, out category) && !string.IsNullOrEmpty(category) && entryHandle.TryGetValue(StandardTableColumnDefinitions.Text, out message) && !string.IsNullOrEmpty(message) && entryHandle.TryGetValue(StandardTableColumnDefinitions.ProjectName, out projectName) && !string.IsNullOrEmpty(projectName)) { if (projectNameToProjectMapOpt == null) { projectNameToProjectMapOpt = new Dictionary <string, Project>(); foreach (var p in _workspace.CurrentSolution.Projects) { projectNameToProjectMapOpt[p.Name] = p; } } cancellationToken.ThrowIfCancellationRequested(); Project project; if (!projectNameToProjectMapOpt.TryGetValue(projectName, out project)) { // bail out continue; } Document document = null; var hasLocation = (entryHandle.TryGetValue(StandardTableColumnDefinitions.DocumentName, out filePath) && !string.IsNullOrEmpty(filePath)) && (entryHandle.TryGetValue(StandardTableColumnDefinitions.Line, out line) && line >= 0); if (hasLocation) { if (string.IsNullOrEmpty(filePath) || line < 0) { // bail out continue; } ImmutableDictionary <string, Document> filePathMap; filePathToDocumentMapOpt = filePathToDocumentMapOpt ?? new Dictionary <Project, ImmutableDictionary <string, Document> >(); if (!filePathToDocumentMapOpt.TryGetValue(project, out filePathMap)) { filePathMap = await GetFilePathToDocumentMapAsync(project, cancellationToken).ConfigureAwait(false); filePathToDocumentMapOpt[project] = filePathMap; } if (!filePathMap.TryGetValue(filePath, out document)) { // bail out continue; } var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var linePosition = new LinePosition(line, 0); var linePositionSpan = new LinePositionSpan(start: linePosition, end: linePosition); var textSpan = (await tree.GetTextAsync(cancellationToken).ConfigureAwait(false)).Lines.GetTextSpan(linePositionSpan); location = tree.GetLocation(textSpan); } Contract.ThrowIfNull(project); Contract.ThrowIfFalse((document != null) == location.IsInSource); // Create a diagnostic with correct values for fields we care about: id, category, message, isSuppressed, location // and default values for the rest of the fields (not used by suppression fixer). var diagnostic = Diagnostic.Create( id: errorCode, category: category, message: message, severity: DiagnosticSeverity.Warning, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, warningLevel: 1, isSuppressed: isSuppressedEntry, title: message, location: location, customTags: SuppressionHelpers.SynthesizedExternalSourceDiagnosticCustomTags); diagnosticData = document != null? DiagnosticData.Create(document, diagnostic) : DiagnosticData.Create(project, diagnostic); } } if (IsEntryWithConfigurableSuppressionState(diagnosticData)) { var isCompilerDiagnostic = SuppressionHelpers.IsCompilerDiagnostic(diagnosticData); if (onlyCompilerDiagnostics && !isCompilerDiagnostic) { continue; } if (isAddSuppression) { // Compiler diagnostics can only be suppressed in source. if (!diagnosticData.IsSuppressed && (isSuppressionInSource || !isCompilerDiagnostic)) { builder.Add(diagnosticData); } } else if (diagnosticData.IsSuppressed) { builder.Add(diagnosticData); } } } return(builder.ToImmutable()); }