/// <summary> /// Creates a new instance of the <see cref="Benchmark"/> class. /// </summary> public static async Task <Benchmark> CreateAsync(Project project, DiagnosticAnalyzer analyzer) { var benchmarkAnalyzer = new BenchmarkAnalyzer(analyzer); await Analyze.GetDiagnosticsAsync(project, benchmarkAnalyzer).ConfigureAwait(false); return(new Benchmark( analyzer, benchmarkAnalyzer.SyntaxNodeActions, benchmarkAnalyzer.CompilationStartActions, benchmarkAnalyzer.CompilationActions, benchmarkAnalyzer.SemanticModelActions, benchmarkAnalyzer.SymbolActions, benchmarkAnalyzer.CodeBlockStartActions, benchmarkAnalyzer.CodeBlockActions, benchmarkAnalyzer.SyntaxTreeActions, benchmarkAnalyzer.OperationActions, benchmarkAnalyzer.OperationBlockActions, benchmarkAnalyzer.OperationBlockStartActions)); }
/// <summary> /// Verifies that /// 1. <paramref name="solution"/> produces the expected diagnostics /// 2. The code fix fixes the code. /// </summary> /// <param name="analyzer">The <see cref="DiagnosticAnalyzer"/> to check <paramref name="solution"/> with.</param> /// <param name="fix">The <see cref="CodeFixProvider"/> to apply on the <see cref="Diagnostic"/> reported.</param> /// <param name="expectedDiagnostic">The <see cref="ExpectedDiagnostic"/> with information about the expected <see cref="Diagnostic"/>. If <paramref name="analyzer"/> supports more than one <see cref="DiagnosticDescriptor.Id"/> this must be provided.</param> /// <param name="solution">The code to analyze with <paramref name="analyzer"/>. Indicate error position with ↓ (alt + 25).</param> /// <param name="after">The expected code produced by applying <paramref name="fix"/>.</param> /// <param name="fixTitle">The expected title of the fix. Must be provided if more than one code action is registered.</param> /// <param name="allowCompilationErrors">Specify if compilation errors are accepted in the fixed code. This can be for example syntax errors. Default value is <see cref="AllowCompilationErrors.No"/>.</param> public static void CodeFix( DiagnosticAnalyzer analyzer, CodeFixProvider fix, ExpectedDiagnostic expectedDiagnostic, Solution solution, string after, string?fixTitle = null, AllowCompilationErrors allowCompilationErrors = AllowCompilationErrors.No) { VerifyAnalyzerSupportsDiagnostic(analyzer, expectedDiagnostic); VerifyCodeFixSupportsAnalyzer(analyzer, fix); var diagnostics = Analyze.GetDiagnostics(analyzer, solution); var diagnosticsAndSources = DiagnosticsAndSources.Create( expectedDiagnostic, solution.Projects.SelectMany(x => x.Documents).Select(x => x.GetCode()).ToArray()); VerifyDiagnostics(diagnosticsAndSources, diagnostics, solution); VerifyFix(solution, diagnostics, analyzer, fix, MergeFixedCode(diagnosticsAndSources.Code, after), fixTitle, allowCompilationErrors); }
/// <summary> /// Verifies that /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics /// 2. The code fix fixes the code. /// </summary> /// <param name="analyzer">The analyzer to run on the code..</param> /// <param name="codeFix">The code fix to apply.</param> /// <param name="diagnosticsAndSources">The code and expected diagnostics.</param> /// <param name="fixedCode">The expected code produced by the code fix.</param> /// <param name="suppressedDiagnostics">The diagnostics to suppress when compiling.</param> /// <param name="metadataReferences">The meta data metadataReferences to add to the compilation.</param> /// <param name="fixTitle">The title of the fix to apply if more than one.</param> /// <param name="allowCompilationErrors">If compilation errors are accepted in the fixed code.</param> public static void FixAll(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList <string> fixedCode, IEnumerable <string> suppressedDiagnostics, IEnumerable <MetadataReference> metadataReferences, string fixTitle, AllowCompilationErrors allowCompilationErrors) { VerifyAnalyzerSupportsDiagnostics(analyzer, diagnosticsAndSources.ExpectedDiagnostics); VerifyCodeFixSupportsAnalyzer(analyzer, codeFix); var sln = CodeFactory.CreateSolution(diagnosticsAndSources, analyzer, suppressedDiagnostics, metadataReferences); var diagnostics = Analyze.GetDiagnostics(analyzer, sln); VerifyDiagnostics(diagnosticsAndSources, diagnostics); FixAllOneByOne(analyzer, codeFix, sln, fixedCode, fixTitle, allowCompilationErrors); var fixAllProvider = codeFix.GetFixAllProvider(); if (fixAllProvider != null) { foreach (var scope in fixAllProvider.GetSupportedFixAllScopes()) { FixAllByScope(analyzer, codeFix, sln, fixedCode, fixTitle, allowCompilationErrors, scope); } } }
/// <summary> /// Verifies that <paramref name="code"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>. /// </summary> /// <param name="analyzer">The analyzer.</param> /// <param name="code">The code to analyze.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param> /// <param name="metadataReferences">The metadata references to use when compiling.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static async Task ValidAsync(DiagnosticAnalyzer analyzer, IReadOnlyList <string> code, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences) { var diagnostics = await Analyze.GetDiagnosticsAsync( analyzer, code, compilationOptions, metadataReferences) .ConfigureAwait(false); if (diagnostics.SelectMany(x => x).Any()) { var builder = StringBuilderPool.Borrow().AppendLine("Expected no diagnostics, found:"); foreach (var diagnostic in diagnostics.SelectMany(x => x)) { builder.AppendLine(diagnostic.ToString(code)); } throw AssertException.Create(builder.Return()); } }
/// <summary> /// Fix the solution by applying the code fix one fix at the time until it stops fixing the code. /// </summary> /// <returns>The fixed solution or the same instance if no fix.</returns> internal static async Task <Solution> ApplyAllFixableScopeByScopeAsync(Solution solution, DiagnosticAnalyzer analyzer, CodeFixProvider fix, FixAllScope scope, string?fixTitle = null, CancellationToken cancellationToken = default) { var fixable = await Analyze.GetFixableDiagnosticsAsync(solution, analyzer, fix).ConfigureAwait(false); var fixedSolution = solution; int count; do { count = fixable.Count; if (count == 0) { return(fixedSolution); } var diagnosticProvider = await TestDiagnosticProvider.CreateAsync(fixedSolution, fix, fixTitle, fixable).ConfigureAwait(false); fixedSolution = await ApplyAsync(fix, scope, diagnosticProvider, cancellationToken).ConfigureAwait(false); fixable = await Analyze.GetFixableDiagnosticsAsync(fixedSolution, analyzer, fix).ConfigureAwait(false); }while (fixable.Count < count); return(fixedSolution); }
/// <summary> /// Fix the solution by applying the code fix one fix at the time until it stops fixing the code. /// </summary> /// <returns>The fixed solution or the same instance if no fix.</returns> internal static async Task <Solution> ApplyAllFixableOneByOneAsync(Solution solution, DiagnosticAnalyzer analyzer, CodeFixProvider fix, string?fixTitle = null, CancellationToken cancellationToken = default) { var fixable = await Analyze.GetFixableDiagnosticsAsync(solution, analyzer, fix).ConfigureAwait(false); fixable = fixable.OrderBy(x => x.Location, LocationComparer.BySourceSpan).ToArray(); var fixedSolution = solution; int count; do { count = fixable.Count; if (count == 0) { return(fixedSolution); } fixedSolution = await ApplyAsync(fixedSolution, fix, fixable[0], fixTitle, cancellationToken).ConfigureAwait(false); fixable = await Analyze.GetFixableDiagnosticsAsync(fixedSolution, analyzer, fix).ConfigureAwait(false); fixable = fixable.OrderBy(x => x.Location, LocationComparer.BySourceSpan).ToArray(); }while (fixable.Count < count); return(fixedSolution); }
/// <summary> /// Verifies that <paramref name="solution"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>. /// </summary> /// <param name="analyzer">The <see cref="DiagnosticAnalyzer"/> to check <paramref name="solution"/> with.</param> /// <param name="solution">The <see cref="Solution"/> for which no errors or warnings are expected.</param> public static void Valid(DiagnosticAnalyzer analyzer, Solution solution) { var diagnosticsAndErrors = Analyze.GetDiagnosticsAndErrors(analyzer, solution); NoDiagnosticsOrErrors(diagnosticsAndErrors); }
/// <summary> /// Check the solution for compiler errors and warnings. /// </summary> public static void NoCompilerErrors(Solution solution, IReadOnlyList <string> allowedIds, AllowedDiagnostics allowedDiagnostics) { var diagnostics = Analyze.GetDiagnostics(solution); NoCompilerErrors(diagnostics, allowedIds, allowedDiagnostics); }
private static void VerifyDiagnostics(DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList <ImmutableArray <Diagnostic> > diagnostics, Solution solution, string?expectedMessage = null) { if (diagnosticsAndSources.ExpectedDiagnostics.Count == 0) { throw new AssertException("Expected code to have at least one error position indicated with '↓'"); } var allDiagnostics = diagnostics.SelectMany(x => x).ToArray(); if (allDiagnostics.Length == 0) { allDiagnostics = Analyze.GetDiagnostics(solution).SelectMany(x => x).ToArray(); } if (Equals()) { if (expectedMessage != null) { foreach (var actual in allDiagnostics) { var actualMessage = actual.GetMessage(CultureInfo.InvariantCulture); TextAssert.AreEqual(expectedMessage, actualMessage, $"Expected and actual diagnostic message for the diagnostic {actual} does not match"); } } return; } var error = StringBuilderPool.Borrow(); if (allDiagnostics.Length == 1 && diagnosticsAndSources.ExpectedDiagnostics.Count == 1 && diagnosticsAndSources.ExpectedDiagnostics[0].Id == allDiagnostics[0].Id) { if (diagnosticsAndSources.ExpectedDiagnostics[0].PositionMatches(allDiagnostics[0]) && !diagnosticsAndSources.ExpectedDiagnostics[0].MessageMatches(allDiagnostics[0])) { CodeAssert.AreEqual(diagnosticsAndSources.ExpectedDiagnostics[0].Message !, allDiagnostics[0].GetMessage(CultureInfo.InvariantCulture), "Expected and actual messages do not match."); } } error.AppendLine("Expected and actual diagnostics do not match.") .AppendLine("Expected:"); foreach (var expected in diagnosticsAndSources.ExpectedDiagnostics.OrderBy(x => x.Span.StartLinePosition)) { error.AppendLine(expected.ToString(diagnosticsAndSources.Code, " ")); } if (allDiagnostics.Length == 0) { error.AppendLine("Actual: <no diagnostics>"); } else { error.AppendLine("Actual:"); foreach (var diagnostic in allDiagnostics.OrderBy(x => x.Location.SourceSpan.Start)) { error.AppendLine(diagnostic.ToErrorString(" ")); } } throw new AssertException(error.Return()); bool Equals() { foreach (var diagnostic in allDiagnostics) { if (diagnosticsAndSources.ExpectedDiagnostics.Any(e => e.Matches(diagnostic))) { continue; } return(false); } foreach (var expected in diagnosticsAndSources.ExpectedDiagnostics) { if (allDiagnostics.Any(a => expected.Matches(a))) { continue; } return(false); } return(true); } }
/// <summary> /// Verifies that <paramref name="sources"/> produces the expected diagnostics. /// </summary> /// <param name="analyzer">The analyzer to apply.</param> /// <param name="sources">The code with error positions indicated.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param> /// <param name="metadataReferences">The meta data metadataReferences to use when compiling.</param> /// <param name="expectedMessage">The expected message in the diagnostic produced by the analyzer.</param> /// <returns>The meta data from the run..</returns> public static async Task <DiagnosticsMetadata> DiagnosticsWithMetadataAsync( DiagnosticAnalyzer analyzer, DiagnosticsAndSources sources, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences, string expectedMessage = null) { if (sources.ExpectedDiagnostics.Count == 0) { throw AssertException.Create("Expected code to have at least one error position indicated with '↓'"); } var data = await Analyze.GetDiagnosticsWithMetadataAsync( analyzer, sources.Code, compilationOptions, metadataReferences) .ConfigureAwait(false); var expecteds = sources.ExpectedDiagnostics; var actuals = data.Diagnostics .SelectMany(x => x) .ToArray(); if (expecteds.SetEquals(actuals)) { if (expectedMessage != null) { foreach (var actual in data.Diagnostics.SelectMany(x => x)) { var actualMessage = actual.GetMessage(CultureInfo.InvariantCulture); TextAssert.AreEqual(expectedMessage, actualMessage, $"Expected and actual diagnostic message for the diagnostic {actual} does not match"); } } return(new DiagnosticsMetadata( sources.Code, sources.ExpectedDiagnostics, data.Diagnostics, data.Solution)); } var error = StringBuilderPool.Borrow(); error.AppendLine("Expected and actual diagnostics do not match."); var missingExpected = expecteds.Except(actuals); for (var i = 0; i < missingExpected.Count; i++) { if (i == 0) { error.Append("Expected:\r\n"); } var expected = missingExpected[i]; error.AppendLine(expected.ToString(sources.Code)); } if (actuals.Length == 0) { error.AppendLine("Actual: <no errors>"); } var missingActual = actuals.Except(expecteds); if (actuals.Length > 0 && missingActual.Count == 0) { error.AppendLine("Actual: <missing>"); } for (var i = 0; i < missingActual.Count; i++) { if (i == 0) { error.Append("Actual:\r\n"); } var actual = missingActual[i]; error.AppendLine(actual.ToString(sources.Code)); } throw AssertException.Create(StringBuilderPool.Return(error)); }
/// <summary> /// Verifies that <paramref name="solution"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>. /// </summary> /// <param name="analyzer">The analyzer.</param> /// <param name="solution">The <see cref="Solution"/> for which no errors or warnings are expected.</param> public static void NoAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, Solution solution) { var diagnostics = Analyze.GetDiagnostics(analyzer, solution); NoDiagnostics(diagnostics); }