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);
        }
示例#2
0
        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));
 }
示例#7
0
        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());
        }