/// <summary> /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. /// </summary> /// <param name="analyzer">The analyzer that was being run on the sources</param> /// <param name="diagnostic">The diagnostic that was found in the code</param> /// <param name="actual">The Location of the Diagnostic found in the code</param> /// <param name="expected">The DiagnosticResultLocation that should have been found</param> private static void VerifyDiagnosticLocation(DiagnosticAnalyzer?analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) { var actualSpan = actual.GetLineSpan(); Assert.True(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), $"Expected diagnostic to be in file \"{expected.Path}\" was actually in file \"{actualSpan.Path}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); var actualLinePosition = actualSpan.StartLinePosition; // Only check line position if there is an actual line in the real diagnostic if (actualLinePosition.Line > 0) { if (actualLinePosition.Line + 1 != expected.Line) { Assert.True(false, $"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualLinePosition.Line + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); } } // Only check column position if there is an actual column position in the real diagnostic if (actualLinePosition.Character > 0) { if (actualLinePosition.Character + 1 != expected.Column) { Assert.True(false, $"Expected diagnostic to start at column \"{expected.Column}\" was actually at column \"{actualLinePosition.Character + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); } } }
/// <summary> /// General verifier for codefixes. /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. /// Then gets the string after the codefix is applied and compares it with the expected result. /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. /// </summary> /// <param name="language">The language the source code is in</param> /// <param name="analyzer">The analyzer to be applied to the source code</param> /// <param name="codeFixProvider">The codefix to be applied to the code wherever the relevant Diagnostic is found</param> /// <param name="oldSource">A class in the form of a string before the CodeFix was applied to it</param> /// <param name="newSource">A class in the form of a string after the CodeFix was applied to it</param> /// <param name="codeFixIndex">Index determining which codefix to apply if there are multiple</param> /// <param name="allowNewCompilerDiagnostics">A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied</param> private void VerifyFix(string language, DiagnosticAnalyzer?analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, language); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); analyzerDiagnostics = analyzerDiagnostics .Where(d => codeFixProvider.FixableDiagnosticIds.Contains(d.Id)) .ToArray(); var compilerDiagnostics = GetCompilerDiagnostics(document); var attempts = analyzerDiagnostics.Length; for (int i = 0; i < attempts; ++i) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); if (!actions.Any()) { break; } if (codeFixIndex != null) { document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); break; } document = ApplyFix(document, actions.ElementAt(0)); analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); //check if applying the code fix introduced any new compiler diagnostics if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); Assert.IsTrue(false, string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), document.GetSyntaxRootAsync().Result.ToFullString())); } //check if there are analyzer diagnostics left after the code fix if (!analyzerDiagnostics.Any()) { break; } } //after applying all of the code fixes, compare the resulting string to the inputted one var actual = GetStringFromDocument(document); Assert.AreEqual(newSource, actual); }
/// <summary> /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, /// then verifies each of them. /// </summary> /// <param name="sources">An array of strings to create source documents from to run the analyzers on</param> /// <param name="language">The language of the classes represented by the source strings</param> /// <param name="analyzer">The analyzer to be run on the source code</param> /// <param name="expected">DiagnosticResults that should appear after the analyzer is run on the sources</param> private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer?analyzer, params DiagnosticResult[] expected) { if (analyzer == null) { throw new ArgumentNullException(nameof(analyzer)); } var diagnostics = GetSortedDiagnostics(sources, language, analyzer); VerifyDiagnosticResults(diagnostics, analyzer, expected); }
/// <summary> /// Helper method to format a Diagnostic into an easily readable string /// </summary> /// <param name="analyzer">The analyzer that this verifier tests</param> /// <param name="diagnostics">The Diagnostics to be formatted</param> /// <returns>The Diagnostics formatted as a string</returns> private static string FormatDiagnostics(DiagnosticAnalyzer?analyzer, params Diagnostic[] diagnostics) { var builder = new StringBuilder(); for (var i = 0; i < diagnostics.Length; ++i) { builder.AppendLine($"// {diagnostics[i]}"); var analyzerType = analyzer?.GetType(); if (analyzerType == null) { continue; } var rules = analyzer !.SupportedDiagnostics; foreach (var rule in rules) { if (rule == null || rule.Id != diagnostics[i].Id) { continue; } var location = diagnostics[i].Location; if (location == Location.None) { builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); } else { Assert.True(location.IsInSource, $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); var fileIsCSharp = diagnostics[i].Location.SourceTree?.FilePath.EndsWith(".cs") ?? false; var resultMethodName = fileIsCSharp ? "GetCSharpResultAt" : "GetBasicResultAt"; var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; builder.AppendFormat("{0}({1}, {2}, {3}.{4})", resultMethodName, linePosition.Line + 1, linePosition.Character + 1, analyzerType.Name, rule.Id); } if (i != diagnostics.Length - 1) { builder.Append(','); } builder.AppendLine(); break; } } return(builder.ToString()); }
public HostAnalyzerStateSets(DiagnosticAnalyzerInfoCache analyzerInfoCache, string language, ImmutableDictionary <DiagnosticAnalyzer, StateSet> analyzerMap) { StateSetMap = analyzerMap; _compilerAnalyzer = analyzerInfoCache.GetCompilerDiagnosticAnalyzer(language); // order statesets // order will be in this order // BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers OrderedStateSets = StateSetMap.Values.OrderBy(PriorityComparison).ToImmutableArray(); }
public static void LogAnalyzerCrashCount(DiagnosticAnalyzer?analyzer, Exception?ex, LogAggregator?logAggregator) { if (logAggregator == null || analyzer == null || ex == null || ex is OperationCanceledException) { return; } // TODO: once we create description manager, pass that into here. var telemetry = AllowsTelemetry(analyzer, null); logAggregator.IncreaseCount((telemetry, analyzer.GetType(), ex.GetType())); }
public bool Equals(DiagnosticAnalyzer?x, DiagnosticAnalyzer?y) { if (x is null && y is null) { return(true); } if (x is null || y is null) { return(false); } return(x.GetAnalyzerIdAndVersion().GetHashCode() == y.GetAnalyzerIdAndVersion().GetHashCode()); }
/// <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 Documents that the analyzer will be run on</param> /// <returns>An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location</returns> protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer?analyzer, Document[] documents) { var projects = new HashSet <Project>(); foreach (var document in documents) { projects.Add(document.Project); } var diagnostics = new List <Diagnostic>(); foreach (var project in projects) { ImmutableArray <Diagnostic> diags; if (analyzer != null) { var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; } else { var compilation = project.GetCompilationAsync().Result; diags = compilation.GetDiagnostics(); } foreach (var diag in diags) { if (diag.Location == Location.None || diag.Location.IsInMetadata) { diagnostics.Add(diag); } else { for (int i = 0; i < documents.Length; i++) { var document = documents[i]; var tree = document.GetSyntaxTreeAsync().Result; if (tree == diag.Location.SourceTree) { diagnostics.Add(diag); } } } } } var results = SortDiagnostics(diagnostics); diagnostics.Clear(); return(results); }
private LatestDiagnosticsForSpanGetter( DiagnosticIncrementalAnalyzer owner, CompilationWithAnalyzers?compilation, Document document, IEnumerable <StateSet> stateSets, string?diagnosticId, TextSpan range, bool blockForData, bool includeSuppressedDiagnostics) { _owner = owner; _project = document.Project; _document = document; _stateSets = stateSets; _diagnosticId = diagnosticId; _compilation = compilation; _compilerAnalyzer = _owner.DiagnosticAnalyzerInfoCache.GetCompilerDiagnosticAnalyzer(_document.Project.Language); _range = range; _blockForData = blockForData; _includeSuppressedDiagnostics = includeSuppressedDiagnostics; }
/// <summary> /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. /// </summary> /// <param name="actualResults">The Diagnostics found by the compiler after running the analyzer on the source code</param> /// <param name="analyzer">The analyzer that was being run on the sources</param> /// <param name="expectedResults">Diagnostic Results that should have appeared in the code</param> private static void VerifyDiagnosticResults(Diagnostic[] actualResults, DiagnosticAnalyzer?analyzer, params DiagnosticResult[] expectedResults) { var expectedCount = expectedResults.Length; var actualCount = actualResults.Length; if (expectedCount != actualCount) { string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; Assert.True(false, $"Mismatch between number of diagnostics returned, expected \"{expectedCount}\" actual \"{actualCount}\"\r\n\r\nDiagnostics:\r\n{diagnosticsOutput}\r\n"); } for (var i = 0; i < expectedResults.Length; i++) { var actual = actualResults.ElementAt(i); var expected = expectedResults[i]; if (expected.Line == -1 && expected.Column == -1) { if (actual.Location != Location.None) { Assert.True(false, $"Expected:\nA project diagnostic with No location\nActual:\n{FormatDiagnostics(analyzer, actual)}"); } } else { VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); var additionalLocations = actual.AdditionalLocations.ToArray(); if (additionalLocations.Length != expected.Locations.Count - 1) { Assert.True(false, $"Expected {expected.Locations.Count - 1} additional locations but got {additionalLocations.Length} for Diagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); } for (int j = 0; j < additionalLocations.Length; ++j) { VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); } } if (actual.Id != expected.Id) { Assert.True(false, $"Expected diagnostic id to be \"{expected.Id}\" was \"{actual.Id}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); } if (actual.Severity != expected.Severity) { Assert.True(false, $"Expected diagnostic severity to be \"{expected.Severity}\" was \"{actual.Severity}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); } if (actual.GetMessage() != expected.Message) { Assert.True(false, $"Expected diagnostic message to be \"{expected.Message}\" was \"{actual.GetMessage()}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); } } }
/// <summary> /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, /// then verifies each of them. /// </summary> /// <param name="sources">An array of strings to create source documents from to run the analyzers on</param> /// <param name="language">The language of the classes represented by the source strings</param> /// <param name="analyzer">The analyzer to be run on the source code</param> /// <param name="expected">DiagnosticResults that should appear after the analyzer is run on the sources</param> private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer?analyzer, params DiagnosticResult[] expected) { var diagnostics = GetSortedDiagnostics(sources, language, analyzer); VerifyDiagnosticResults(diagnostics, analyzer, expected); }
/// <summary> /// Given classes in the form of strings, their language, and an IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string after converting it to a document. /// </summary> /// <param name="sources">Classes in the form of strings</param> /// <param name="language">The language the source classes are in</param> /// <param name="analyzer">The analyzer to be run on the sources</param> /// <returns>An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location</returns> private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer?analyzer) { return(GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language))); }