/// <summary> /// For testing a <see cref="CodeRefactoringProvider"/>. /// </summary> /// <param name="refactoring">The <see cref="CodeRefactoringProvider"/>.</param> /// <param name="before">The code to analyze with <paramref name="refactoring"/>. Position is provided by <paramref name="span"/>.</param> /// <param name="span">A <see cref="TextSpan"/> indicating the position.</param> /// <param name="after">The expected code produced by <paramref name="refactoring"/>.</param> /// <param name="title">The title of the refactoring to apply.</param> public static void Refactoring(CodeRefactoringProvider refactoring, string before, TextSpan span, string after, string title) { #pragma warning disable CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() var refactored = Refactor.Apply(refactoring, before, span, title, MetadataReferences); #pragma warning restore CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() CodeAssert.AreEqual(after, refactored); }
/// <summary> /// For testing a <see cref="CodeRefactoringProvider"/>. /// </summary> /// <param name="refactoring">The <see cref="CodeRefactoringProvider"/>.</param> /// <param name="codeWithPositionIndicated">The code to refactor with position indicated with ↓.</param> /// <param name="title">The title of the refactoring to apply.</param> /// <param name="fixedCode">The expected code produced by the refactoring.</param> public static void Refactoring(CodeRefactoringProvider refactoring, string codeWithPositionIndicated, string title, string fixedCode) { var position = GetPosition(codeWithPositionIndicated, out var testCode); var refactored = Refactor.Apply(refactoring, testCode, position, title, MetadataReferences); CodeAssert.AreEqual(refactored, fixedCode); }
/// <summary> /// For testing a <see cref="CodeRefactoringProvider"/>. /// </summary> /// <param name="refactoring">The <see cref="CodeRefactoringProvider"/>.</param> /// <param name="before">The code to analyze with <paramref name="refactoring"/>. Indicate position with ↓ (alt + 25).</param> /// <param name="after">The expected code produced by <paramref name="refactoring"/>.</param> public static void Refactoring(CodeRefactoringProvider refactoring, string before, string after) { var position = GetPosition(before, out var testCode); #pragma warning disable CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() var refactored = Refactor.Apply(refactoring, testCode, position, MetadataReferences); #pragma warning restore CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() CodeAssert.AreEqual(after, refactored); }
private static async Task AreEqualAsync(IReadOnlyList <string> expected, Solution actual, string messageHeader) { var index = 0; foreach (var project in actual.Projects) { for (var i = 0; i < project.DocumentIds.Count; i++) { var fixedSource = await CodeReader.GetStringFromDocumentAsync(project.GetDocument(project.DocumentIds[i]), Formatter.Annotation, CancellationToken.None).ConfigureAwait(false); CodeAssert.AreEqual(expected[index], fixedSource, messageHeader); index++; } } }
/// <summary> /// Verifies that /// 1. <paramref name="diagnosticsAndSources"/> produces the expected diagnostics when analyzed. /// 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="fixTitle">The title of the fix to apply if more than one.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param> /// <param name="metadataReferences">The meta data metadataReferences to add to the compilation.</param> /// <param name="allowCompilationErrors">If compilation errors are accepted in the fixed code.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static async Task CodeFixAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, string fixedCode, string fixTitle, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences, AllowCompilationErrors allowCompilationErrors) { AssertCodeFixCanFixDiagnosticsFromAnalyzer(analyzer, codeFix); var data = await DiagnosticsWithMetadataAsync(analyzer, diagnosticsAndSources, compilationOptions, metadataReferences).ConfigureAwait(false); var fixableDiagnostics = data.ActualDiagnostics.SelectMany(x => x) .Where(x => codeFix.FixableDiagnosticIds.Contains(x.Id)) .ToArray(); if (fixableDiagnostics.Length == 0) { var message = $"Code analyzed with {analyzer} did not generate any diagnostics fixable by {codeFix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}"; throw AssertException.Create(message); } if (fixableDiagnostics.Length > 1) { var message = $"Code analyzed with {analyzer} generated more than one diagnostic fixable by {codeFix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", data.ExpectedDiagnostics.Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", codeFix.FixableDiagnosticIds)}}}{Environment.NewLine}" + $"Maybe you meant to call AnalyzerAssert.FixAll?"; throw AssertException.Create(message); } var diagnostic = fixableDiagnostics.Single(); var fixedSolution = await Fix.ApplyAsync(data.Solution, codeFix, diagnostic, fixTitle, CancellationToken.None).ConfigureAwait(false); if (ReferenceEquals(data.Solution, fixedSolution)) { throw AssertException.Create($"{codeFix} did not change any document."); } var fixedSource = await CodeReader.GetStringFromDocumentAsync( fixedSolution.GetDocument(data.Solution.GetDocument(diagnostic.Location.SourceTree).Id), Formatter.Annotation, CancellationToken.None) .ConfigureAwait(false); CodeAssert.AreEqual(fixedCode, fixedSource); if (allowCompilationErrors == AllowCompilationErrors.No) { await AssertNoCompilerErrorsAsync(codeFix, fixedSolution).ConfigureAwait(false); } }
private static async Task VerifyFixAsync(Solution sln, IReadOnlyList <ImmutableArray <Diagnostic> > diagnostics, DiagnosticAnalyzer analyzer, CodeFixProvider fix, string fixedCode, string fixTitle = null, AllowCompilationErrors allowCompilationErrors = AllowCompilationErrors.No) { var fixableDiagnostics = diagnostics.SelectMany(x => x) .Where(x => fix.FixableDiagnosticIds.Contains(x.Id)) .ToArray(); if (fixableDiagnostics.Length == 0) { var message = $"Code analyzed with {analyzer} did not generate any diagnostics fixable by {fix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", diagnostics.SelectMany(x => x).Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", fix.FixableDiagnosticIds)}}}"; throw new AssertException(message); } if (fixableDiagnostics.Length > 1) { var message = $"Code analyzed with {analyzer} generated more than one diagnostic fixable by {fix}.{Environment.NewLine}" + $"The analyzed code contained the following diagnostics: {{{string.Join(", ", diagnostics.SelectMany(x => x).Select(d => d.Id))}}}{Environment.NewLine}" + $"The code fix supports the following diagnostics: {{{string.Join(", ", fix.FixableDiagnosticIds)}}}{Environment.NewLine}" + $"Maybe you meant to call AnalyzerAssert.FixAll?"; throw new AssertException(message); } var diagnostic = fixableDiagnostics.Single(); var fixedSolution = await Fix.ApplyAsync(sln, fix, diagnostic, fixTitle).ConfigureAwait(false); if (ReferenceEquals(sln, fixedSolution)) { throw new AssertException($"{fix} did not change any document."); } var fixedSource = await CodeReader.GetStringFromDocumentAsync( fixedSolution.GetDocument(sln.GetDocument(diagnostic.Location.SourceTree).Id), Formatter.Annotation, CancellationToken.None) .ConfigureAwait(false); CodeAssert.AreEqual(fixedCode, fixedSource); if (allowCompilationErrors == AllowCompilationErrors.No) { await VerifyNoCompilerErrorsAsync(fix, fixedSolution).ConfigureAwait(false); } }
private static async Task AreEqualAsync(IReadOnlyList <string> expected, Solution actual, string?messageHeader) { var actualCount = actual.Projects.SelectMany(x => x.Documents).Count(); if (expected.Count != actualCount) { throw new AssertException($"Expected {expected.Count} documents the fixed solution has {actualCount} documents."); } foreach (var project in actual.Projects) { foreach (var document in project.Documents) { var fixedSource = await CodeReader.GetStringFromDocumentAsync(document, CancellationToken.None).ConfigureAwait(false); CodeAssert.AreEqual(FindExpected(fixedSource), fixedSource, messageHeader); } } string FindExpected(string fixedSource) { var fixedNamespace = CodeReader.Namespace(fixedSource); var fixedFileName = CodeReader.FileName(fixedSource); var match = expected.FirstOrDefault(x => x == fixedSource); if (match != null) { return(match); } foreach (var candidate in expected) { if (CodeReader.Namespace(candidate) == fixedNamespace && CodeReader.FileName(candidate) == fixedFileName) { return(candidate); } } throw new AssertException($"The fixed solution contains a document {fixedFileName} in namespace {fixedNamespace} that is not in the expected documents."); } }
/// <summary> /// Serializes the syntax tree and compares the strings. /// This can be useful when having trouble getting whitespace right. /// </summary> /// <typeparam name="T">The node type.</typeparam> /// <param name="expected">The expected shape of the AST.</param> /// <param name="actual">The actual node.</param> public static void Ast <T>(T expected, T actual) where T : SyntaxNode { CodeAssert.AreEqual(SyntaxFactoryWriter.Serialize(expected), SyntaxFactoryWriter.Serialize(actual)); }
/// <summary> /// For testing a <see cref="CodeRefactoringProvider"/>. /// </summary> /// <param name="refactoring">The <see cref="CodeRefactoringProvider"/>.</param> /// <param name="code">The code to refactor.</param> /// <param name="span">The position.</param> /// <param name="title">The title of the refactoring to apply.</param> /// <param name="fixedCode">The expected code produced by the refactoring.</param> public static void Refactoring(CodeRefactoringProvider refactoring, string code, TextSpan span, string title, string fixedCode) { var refactored = Refactor.Apply(refactoring, code, span, title, MetadataReferences); CodeAssert.AreEqual(refactored, fixedCode); }
/// <summary> /// Serializes the syntax tree and compares the strings. /// This can be useful when having trouble getting whitespace right. /// </summary> /// <typeparam name="T">The node type.</typeparam> /// <param name="expected">The expected shape of the AST.</param> /// <param name="actual">The actual node.</param> /// <param name="settings"><see cref="AstWriterSettings"/>.</param> public static void Ast <T>(T expected, T actual, AstWriterSettings settings = null) where T : SyntaxNode { CodeAssert.AreEqual(AstWriter.Serialize(expected, settings), AstWriter.Serialize(actual, settings)); }
private static void VerifyDiagnostics(DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList <Diagnostic> actuals, string expectedMessage = null) { if (diagnosticsAndSources.ExpectedDiagnostics.Count == 0) { throw new AssertException("Expected code to have at least one error position indicated with '↓'"); } if (diagnosticsAndSources.ExpectedDiagnostics.SetEquals(actuals)) { if (expectedMessage != null) { foreach (var actual in actuals) { 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 (actuals.Count == 1 && diagnosticsAndSources.ExpectedDiagnostics.Count == 1 && diagnosticsAndSources.ExpectedDiagnostics[0].Id == actuals[0].Id) { if (diagnosticsAndSources.ExpectedDiagnostics[0].PositionMatches(actuals[0]) && !diagnosticsAndSources.ExpectedDiagnostics[0].MessageMatches(actuals[0])) { CodeAssert.AreEqual(diagnosticsAndSources.ExpectedDiagnostics[0].Message, actuals[0].GetMessage(CultureInfo.InvariantCulture), "Expected and actual messages do not match."); } } error.AppendLine("Expected and actual diagnostics do not match."); var missingExpected = diagnosticsAndSources.ExpectedDiagnostics.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(diagnosticsAndSources.Code)); } if (actuals.Count == 0) { error.AppendLine("Actual: <no errors>"); } var missingActual = actuals.Except(diagnosticsAndSources.ExpectedDiagnostics); if (actuals.Count > 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.ToErrorString()); } throw new AssertException(error.Return()); }