예제 #1
0
        /// <summary>
        /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it.
        /// The returned diagnostics are then ordered by location in the source document.
        /// </summary>
        /// <param name="analyzer">The analyzer to run on the documents.</param>
        /// <param name="documents">The <see cref="Document">Documents</see> that the analyzer will be run on.</param>
        /// <param name="validationModes">Flags that specify which compilation diagnostics can cause a failure.</param>
        /// <param name="additionalFiles">Enumerable of <see cref="AdditionalText" /> that will appear to the analyzer as additional files.</param>
        /// <returns>An <see cref="IEnumerable{Diagnostic}" /> that surfaced in the source code, sorted by Location.</returns>
        private static async Task <IEnumerable <Diagnostic> > GetSortedDiagnosticsFromDocumentsAsync(
            DiagnosticAnalyzer analyzer,
            IEnumerable <Document> documents,
            TestValidationModes validationModes,
            IEnumerable <AdditionalText> additionalFiles,
            Dictionary <string, IEnumerable <AdditionalText> > projectAdditionalFiles)
        {
            var projects = new HashSet <Project>();

            foreach (var document in documents)
            {
                projects.Add(document.Project);
            }

            var relevantDiagnostics = new List <Diagnostic>();

            foreach (var project in projects)
            {
                var compilation = await project.GetCompilationAsync().ConfigureAwait(false);

                compilation = EnableDiagnostics(analyzer, compilation);

                if (validationModes != TestValidationModes.None)
                {
                    ValidateCompilation(compilation, validationModes);
                }

                var allProjectsFiles   = additionalFiles ?? Enumerable.Empty <AdditionalText>();
                var allAdditionalFiles = projectAdditionalFiles != null && projectAdditionalFiles.TryGetValue(project.Name, out var thisProjectsFiles)
                    ? allProjectsFiles.Union(thisProjectsFiles)
                    : allProjectsFiles;
                var analyzerOptions          = new AnalyzerOptions(ImmutableArray.Create(allAdditionalFiles.ToArray()));
                var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer), analyzerOptions);

                var diagnostics = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false);

                foreach (var diagnostic in diagnostics)
                {
                    if (diagnostic.Location == Location.None || diagnostic.Location.IsInMetadata)
                    {
                        relevantDiagnostics.Add(diagnostic);
                    }
                    else
                    {
                        foreach (var document in documents)
                        {
                            var tree = await document.GetSyntaxTreeAsync().ConfigureAwait(false);

                            if (tree == diagnostic.Location.SourceTree)
                            {
                                relevantDiagnostics.Add(diagnostic);
                            }
                        }
                    }
                }
            }

            return(SortDiagnostics(relevantDiagnostics));
        }
예제 #2
0
 /// <summary>
 /// Returns the diagnostics found in the given <paramref name="testFiles"/> for the given <paramref name="analyzer"/>.
 /// </summary>
 /// <param name="analyzer">The analyzer to be run on the sources.</param>
 /// <param name="documents">The <see cref="Document">Documents</see> that the analyzer will be run on.</param>
 /// <param name="validationModes">Flags that specify which compilation diagnostics can cause a failure.</param>
 /// <param name="additionalFiles">Enumerable of <see cref="AdditionalText"/> that will appear to the analyzer as additional files.</param>
 /// <param name="projectAdditionalFiles">Mapping of project name to the array of <see cref="AdditionalText"/> it includes.</param>
 /// <returns>An <see cref="IEnumerable{Diagnostic}" /> that surfaced in the source code, sorted by Location.</returns>
 protected static async Task <IEnumerable <Diagnostic> > GetSortedDiagnosticsAsync(
     DiagnosticAnalyzer analyzer,
     IEnumerable <Document> documents,
     TestValidationModes validationModes          = DefaultTestValidationMode,
     IEnumerable <AdditionalText> additionalFiles = null,
     Dictionary <string, IEnumerable <AdditionalText> > projectAdditionalFiles = null)
 {
     return(await GetSortedDiagnosticsFromDocumentsAsync(
                analyzer,
                documents,
                validationModes,
                additionalFiles,
                projectAdditionalFiles).ConfigureAwait(false));
 }
예제 #3
0
        /// <summary>
        /// Ensures no compilation diagnostics exist with severities that match what's specified in
        /// <paramref name="validationModes"/>
        /// </summary>
        /// <remarks>
        /// The idea is taken from the Roslyn Analyzers' project, though their implementation differs greatly (different APIs):
        /// https://github.com/dotnet/roslyn-analyzers/blob/569bd373a4831d3035597197e02980b57602a7f2/src/Test/Utilities/DiagnosticExtensions.cs
        /// </remarks>
        /// <param name="compilation">An object that allows us to obtain the warnings, errors, ... from a compilation</param>
        /// <param name="validationModes">Flags that specify which compilation diagnostics can cause a failure</param>
        private static void ValidateCompilation(Compilation compilation, TestValidationModes validationModes)
        {
            // Ignore the diagnostics about missing a main method (CS5001, BC30420) or not being able to find a type/namespace (CS0246)
            // Ids taken from: https://github.com/Vannevelj/RoslynTester/blob/master/RoslynTester/RoslynTester/Helpers/DiagnosticVerifier.cs
            var ignoredIds = new HashSet <string>(new[] { "CS5001", "BC30420", "CS0246" });

            var severityMapping = new Dictionary <DiagnosticSeverity, TestValidationModes>
            {
                { DiagnosticSeverity.Error, TestValidationModes.ValidateErrors },
                { DiagnosticSeverity.Warning, TestValidationModes.ValidateWarnings },
            };

            // Create a mapping from TestValidationMode to a list of compilation diagnostics. If no
            // compilation diagnostics exist for a given TestValidationMode, no entry is created
            var compileDiagnostics = compilation.GetDiagnostics()
                                     .Where(diagnostic =>
                                            !ignoredIds.Contains(diagnostic.Id) &&
                                            severityMapping.ContainsKey(diagnostic.Severity) &&
                                            validationModes.HasFlag(severityMapping[diagnostic.Severity]))
                                     .GroupBy(diagnostic => diagnostic.Severity)
                                     .Select(g => new { Key = severityMapping[g.Key], Values = g.ToList() })
                                     .ToDictionary(g => g.Key, g => g.Values);

            if (compileDiagnostics.Keys.Any())
            {
                // Helper function that returns two different strings. The first looks like "1 error(s)" and the
                // second like "TestValidationModes.ValidateErrors". Both will be used to construct a helpful message
                var getMessageParts = new Func <TestValidationModes, string, Tuple <string, string> >((mode, whats) =>
                {
                    return(validationModes.HasFlag(mode) && compileDiagnostics.ContainsKey(mode)
                        ? new Tuple <string, string>(
                               string.Format(CultureInfo.CurrentCulture, "{0} {1}", compileDiagnostics[mode].Count, whats),
                               string.Format(CultureInfo.CurrentCulture, "{0}.{1}", nameof(TestValidationModes), Enum.GetName(typeof(TestValidationModes), mode)))
                        : null);
                });

                var messages = new[]
                {
                    getMessageParts(TestValidationModes.ValidateErrors, "error(s)"),
                    getMessageParts(TestValidationModes.ValidateWarnings, "warning(s)"),
                }.Where(x => x != null).ToArray();

                // Create the helpful message
                var message = string.Format(
                    CultureInfo.CurrentCulture,
                    "\nTest compilation contains {0}. Disable {1} if these are expected:\n",
                    string.Join(", ", messages.Select(x => x.Item1)),
                    string.Join(" and/or ", messages.Select(x => x.Item2)));

                var builder = new StringBuilder(message);

                foreach (TestValidationModes mode in Enum.GetValues(typeof(TestValidationModes)))
                {
                    if (compileDiagnostics.ContainsKey(mode))
                    {
                        builder.Append(string.Format(CultureInfo.CurrentCulture, "\n {0}", string.Join("\n", compileDiagnostics[mode])));
                    }
                }

                Xunit.Assert.True(false, builder.ToString());
            }
        }