private static async Task <IEnumerable <CodeAction> > GetFixesAsync(Solution solution, CodeFixProvider codeFixProvider, Diagnostic diagnostic) { List <CodeAction> codeActions = new List <CodeAction>(); await codeFixProvider.RegisterCodeFixesAsync(new CodeFixContext(solution.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => codeActions.Add(a), CancellationToken.None)); return(codeActions); }
private async Task <IEnumerable <Tuple <Diagnostic, CodeFixCollection> > > GetDiagnosticAndFixesAsync( IEnumerable <Diagnostic> diagnostics, DiagnosticAnalyzer provider, CodeFixProvider fixer, TestDiagnosticAnalyzerDriver testDriver, Document document, TextSpan span, FixAllScope?scope, string fixAllActionId) { Assert.NotEmpty(diagnostics); var result = new List <Tuple <Diagnostic, CodeFixCollection> >(); if (scope == null) { // Simple code fix. foreach (var diagnostic in diagnostics) { var fixes = new List <CodeFix>(); var context = new CodeFixContext(document, diagnostic, (a, d) => fixes.Add(new CodeFix(document.Project, a, d)), CancellationToken.None); await fixer.RegisterCodeFixesAsync(context); if (fixes.Any()) { var codeFix = new CodeFixCollection( fixer, diagnostic.Location.SourceSpan, fixes.ToImmutableArray(), fixAllState: null, supportedScopes: ImmutableArray <FixAllScope> .Empty, firstDiagnostic: null); result.Add(Tuple.Create(diagnostic, codeFix)); } } } else { // Fix all fix. var fixAllProvider = fixer.GetFixAllProvider(); Assert.NotNull(fixAllProvider); var fixAllState = GetFixAllState(fixAllProvider, diagnostics, provider, fixer, testDriver, document, scope.Value, fixAllActionId); var fixAllContext = fixAllState.CreateFixAllContext(new ProgressTracker(), CancellationToken.None); var fixAllFix = await fixAllProvider.GetFixAsync(fixAllContext); if (fixAllFix != null) { // Same fix applies to each diagnostic in scope. foreach (var diagnostic in diagnostics) { var diagnosticSpan = diagnostic.Location.IsInSource ? diagnostic.Location.SourceSpan : default(TextSpan); var codeFix = new CodeFixCollection( fixAllProvider, diagnosticSpan, ImmutableArray.Create(new CodeFix(document.Project, fixAllFix, diagnostic)), fixAllState: null, supportedScopes: ImmutableArray <FixAllScope> .Empty, firstDiagnostic: null); result.Add(Tuple.Create(diagnostic, codeFix)); } } } return(result); }
/// <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 static async Task VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics) { Document document = CreateDocument(oldSource, language); Diagnostic[] analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); IEnumerable <Diagnostic> compilerDiagnostics = GetCompilerDiagnostics(document); int 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 = await ApplyFix(document, actions.ElementAt((int)codeFixIndex)); break; } document = await ApplyFix(document, actions.ElementAt(0)); analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); IEnumerable <Diagnostic> 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, $"Fix introduced new compiler diagnostics:{string.Join(Environment.NewLine, newCompilerDiagnostics.Select(d => d.ToString()))}" + $"{Environment.NewLine}{Environment.NewLine}New document:{Environment.NewLine}{document.GetSyntaxRootAsync().Result.ToFullString()}{Environment.NewLine}"); } //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 string actual = GetStringFromDocument(document); Assert.AreEqual <string>( newSource?.Replace("\r", "", StringComparison.InvariantCulture), actual?.Replace("\r", "", StringComparison.InvariantCulture)); }
/// <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 }); var compilerDiagnostics = GetCompilerDiagnostics(document); var attempts = analyzerDiagnostics.Length; for (var 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); }
protected async Task VerifyFix(string oldSource, string newSource, int?codeFixIndex = null) { using (var workspace = new AdhocWorkspace()) { var project = AddProject(workspace.CurrentSolution, oldSource); var document = project.Documents.Single(); var compilation = await project.GetCompilationAsync(); var compilerDiagnostics = compilation.GetDiagnostics(); VerifyNoCompilerDiagnosticErrors(compilerDiagnostics); var analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( DiagnosticAnalyzer, project.AnalyzerOptions); var previousAnalyzerDiagnostics = analyzerDiagnostics; var attempts = analyzerDiagnostics.Length; for (var i = 0; i < attempts; ++i) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); await CodeFixProvider.RegisterCodeFixesAsync(context); if (!actions.Any()) { break; } document = await document.ApplyCodeAction(actions[codeFixIndex ?? 0]); compilation = await document.Project.GetCompilationAsync(); compilerDiagnostics = compilation.GetDiagnostics(); VerifyNoCompilerDiagnosticErrors(compilerDiagnostics); analyzerDiagnostics = await compilation.GetSortedAnalyzerDiagnostics( DiagnosticAnalyzer, project.AnalyzerOptions); // check if there are analyzer diagnostics left after the code fix var newAnalyzerDiagnostics = analyzerDiagnostics.Except(previousAnalyzerDiagnostics).ToList(); if (analyzerDiagnostics.Length == previousAnalyzerDiagnostics.Length && newAnalyzerDiagnostics.Any()) { Execute.Assertion.Fail( $"Fix didn't fix analyzer diagnostics: {newAnalyzerDiagnostics.ToDebugString()} New document:{Environment.NewLine}{await document.ToFullString()}"); } previousAnalyzerDiagnostics = analyzerDiagnostics; } var actual = await document.ToFullString(); actual.Should().Be(newSource); } }
private async Task <(ImmutableArray <Diagnostic>, ImmutableArray <CodeAction>, CodeAction actionToinvoke)> GetDiagnosticAndFixesAsync( IEnumerable <Diagnostic> diagnostics, CodeFixProvider fixer, TestDiagnosticAnalyzerDriver testDriver, Document document, TextSpan span, FixAllScope?scope, int index) { Assert.NotEmpty(diagnostics); var intersectingDiagnostics = diagnostics.Where(d => d.Location.SourceSpan.IntersectsWith(span)) .ToImmutableArray(); var fixes = new List <CodeFix>(); foreach (var diagnostic in intersectingDiagnostics) { var context = new CodeFixContext( document, diagnostic, (a, d) => fixes.Add(new CodeFix(document.Project, a, d)), CancellationToken.None); await fixer.RegisterCodeFixesAsync(context); } var actions = fixes.SelectAsArray(f => f.Action); actions = actions.SelectMany(a => a is TopLevelSuppressionCodeAction ? a.NestedCodeActions : ImmutableArray.Create(a)).ToImmutableArray(); actions = MassageActions(actions); if (scope == null) { // Simple code fix. return(intersectingDiagnostics, actions, actions.Length == 0 ? null : actions[index]); } else { var equivalenceKey = actions[index].EquivalenceKey; // Fix all fix. var fixAllProvider = fixer.GetFixAllProvider(); Assert.NotNull(fixAllProvider); var fixAllState = GetFixAllState( fixAllProvider, diagnostics, fixer, testDriver, document, scope.Value, equivalenceKey); var fixAllContext = fixAllState.CreateFixAllContext(new ProgressTracker(), CancellationToken.None); var fixAllFix = await fixAllProvider.GetFixAsync(fixAllContext); // We have collapsed the fixes down to the single fix-all fix, so we just let our // caller know they should pull that entry out of the result. return(intersectingDiagnostics, ImmutableArray.Create(fixAllFix), fixAllFix); } }
private static IEnumerable <CodeAction> GetCodeActionsForDiagnostic(CodeFixProvider codeFixProvider, Document document, Diagnostic diagnostic) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); return(actions); }
internal static List <CodeAction> GetFixActions(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string source) { var document = CodeAnalysisHelper.CreateDocument(source, language); var analyzerDiagnostics = CodeAnalysisHelper.GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); return(actions); }
/// <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="oldSources">Code files, each in the form of a string before the CodeFix was applied to it</param> /// <param name="newSources">Code files, each 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 in the same location</param> /// <param name="allowNewCompilerDiagnostics">A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied</param> /// <param name="hasEntrypoint"><c>true</c> to set the compiler in a mode as if it were compiling an exe (as opposed to a dll).</param> private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string[] oldSources, string[] newSources, int?codeFixIndex, bool allowNewCompilerDiagnostics, bool hasEntrypoint) { var project = CreateProject(oldSources, language, hasEntrypoint); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(ImmutableArray.Create(analyzer), project.Documents.ToArray(), hasEntrypoint); var compilerDiagnostics = project.Documents.SelectMany(doc => GetCompilerDiagnostics(doc)); var attempts = analyzerDiagnostics.Length; // We'll go through enough for each diagnostic to be caught once for (int i = 0; i < attempts; ++i) { var diagnostic = analyzerDiagnostics[0]; // just get the first one -- the list gets smaller with each loop. var document = project.GetDocument(diagnostic.Location.SourceTree); var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); if (!actions.Any()) { continue; } document = ApplyFix(document, actions[codeFixIndex ?? 0]); project = document.Project; this.Logger.WriteLine("Code after fix:\n{0}", document.GetSyntaxRootAsync().Result.ToFullString()); analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(ImmutableArray.Create(analyzer), project.Documents.ToArray()); var newCompilerDiagnostics = GetNewDiagnostics( compilerDiagnostics, project.Documents.SelectMany(doc => GetCompilerDiagnostics(doc))); // 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.True( 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())); } } // After applying all of the code fixes, compare the resulting string to the inputted one int j = 0; foreach (var document in project.Documents) { var actual = GetStringFromDocument(document); Assert.Equal(newSources[j++], actual, ignoreLineEndingDifferences: true); } }
/// <summary> /// Create an instance of <see cref="TestDiagnosticProvider"/>. /// </summary> /// <returns>The <see cref="TestDiagnosticProvider"/>.</returns> internal static async Task <TestDiagnosticProvider> CreateAsync(Solution solution, CodeFixProvider fix, string?fixTitle, IReadOnlyList <Diagnostic> diagnostics) { var actions = new List <CodeAction>(); var diagnostic = diagnostics.First(); var context = new CodeFixContext(solution.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), CancellationToken.None); await fix.RegisterCodeFixesAsync(context).ConfigureAwait(false); var action = FindAction(actions, fixTitle); return(new TestDiagnosticProvider(diagnostics, solution.GetDocument(diagnostics.First().Location.SourceTree), action.EquivalenceKey)); }
/// <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> /// <param name="languageVersionCSharp">C# language version used for compiling the test project, required unless you inform the VB language version.</param> /// <param name="languageVersionVB">VB language version used for compiling the test project, required unless you inform the C# language version.</param> private async static Task VerifyFixAsync(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB) { var supportedDiagnostics = analyzer.SupportedDiagnostics.Select(d => d.Id); var codeFixFixableDiagnostics = codeFixProvider.FixableDiagnosticIds; Assert.True(codeFixFixableDiagnostics.Any(d => supportedDiagnostics.Contains(d)), "Code fix provider does not fix the diagnostic provided by the analyzer."); var document = CreateDocument(oldSource, language, languageVersionCSharp, languageVersionVB); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }).ConfigureAwait(true); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true); 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); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(true); if (!actions.Any()) { break; } document = codeFixIndex != null ? await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)).ConfigureAwait(true) : await ApplyFixAsync(document, actions.ElementAt(0)).ConfigureAwait(true); analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }).ConfigureAwait(true); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); //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(await document.GetSyntaxRootAsync().ConfigureAwait(true), Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n\r\nNew document:\r\n{(await document.GetSyntaxRootAsync().ConfigureAwait(true)).ToFullString()}\r\n"); } //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 = await GetStringFromDocumentAsync(document).ConfigureAwait(true); Assert.Equal(newSource, actual); }
// Helpers/Fixing private static async Task GetCodeFixActionsAsync(CodeFixProvider fixer, Project project, Diagnostic diagnostic, List <CodeAction> actions, CancellationToken cancellationToken) { if (!fixer.FixableDiagnosticIds.Contains(diagnostic.Id)) { throw new ArgumentException($"Diagnostic is not supported by CodeFixProvider: Diagnostic={diagnostic.Id}, CodeFixProvider={fixer.GetType().Name}"); } var tree = diagnostic.Location.SourceTree ?? throw new Exception("Syntax tree is null"); var document = project.GetDocument(tree) ?? throw new Exception("Document is not found"); var context = new CodeFixContext(document, diagnostic, (action, _) => actions.Add(action), cancellationToken); await fixer.RegisterCodeFixesAsync(context).ConfigureAwait(false); }
private ImmutableArray <CodeAction> GetCodeFixesForDiagnostic([NotNull] Diagnostic diagnostic, [NotNull] Document document, [NotNull] CodeFixProvider fixProvider) { ImmutableArray <CodeAction> .Builder builder = ImmutableArray.CreateBuilder <CodeAction>(); Action <CodeAction, ImmutableArray <Diagnostic> > registerCodeFix = (codeAction, _) => builder.Add(codeAction); var fixContext = new CodeFixContext(document, diagnostic, registerCodeFix, CancellationToken.None); fixProvider.RegisterCodeFixesAsync(fixContext).Wait(); return(builder.ToImmutable()); }
private static async Task <Document> GetSingleAnalyzerDocumentAsync(ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Document document, int maxNumberOfIterations, CancellationToken cancellationToken) { var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } if (--maxNumberOfIterations < 0) { Assert.True(false, "The upper limit for the number of code fix iterations was exceeded"); } previousDiagnostics = analyzerDiagnostics; done = true; for (var i = 0; i < analyzerDiagnostics.Length; i++) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostics[i], (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (actions.Count > 0) { var fixedDocument = await ApplyFixAsync(document, actions.ElementAt(codeFixIndex.GetValueOrDefault(0)), cancellationToken).ConfigureAwait(false); if (fixedDocument != document) { done = false; var newText = await fixedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); // workaround for issue #936 - force re-parsing to get the same sort of syntax tree as the original document. document = document.WithText(newText); break; } } } }while (!done); return(document); }
private async Task VerifyFixAsync(string language, ImmutableArray <string> diagnosticIds, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, language); var compilerDiagnostics = (await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)).ToList(); var analyzerDiagnostics = compilerDiagnostics.Where(c => diagnosticIds.Contains(c.Id)).ToList(); var attempts = analyzerDiagnostics.Count(); 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); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(true); if (!actions.Any()) { break; } if (codeFixIndex != null) { document = await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)).ConfigureAwait(true); break; } document = await ApplyFixAsync(document, actions.ElementAt(0)).ConfigureAwait(true); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); compilerDiagnostics = (await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)).ToList(); analyzerDiagnostics = compilerDiagnostics.Where(c => diagnosticIds.Contains(c.Id)).ToList(); //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(await document.GetSyntaxRootAsync().ConfigureAwait(true), Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document).ConfigureAwait(true)); Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString()))}\r\n\r\nNew document:\r\n{(await document.GetSyntaxRootAsync().ConfigureAwait(true)).ToFullString()}\r\n"); } //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 = await GetStringFromDocumentAsync(document).ConfigureAwait(true); Assert.Equal(newSource, actual); }
/// <summary> /// Apply codefixes and returns reslt. /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. /// 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="equivalenceKey">CodeAction.EquivalenceKey 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> internal static string GetFixResult(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string equivalenceKey, bool allowNewCompilerDiagnostics) { var document = CodeAnalysisHelper.CreateDocument(oldSource, language); var analyzerDiagnostics = CodeAnalysisHelper.GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); var compilerDiagnostics = CodeAnalysisHelper.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 (equivalenceKey != null) { document = CodeAnalysisHelper.ApplyFix(document, actions.Single(n => n.EquivalenceKey == equivalenceKey)); break; } document = CodeAnalysisHelper.ApplyFix(document, actions.ElementAt(0)); analyzerDiagnostics = CodeAnalysisHelper.GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); var newCompilerDiagnostics = CodeAnalysisHelper.GetNewDiagnostics(compilerDiagnostics, CodeAnalysisHelper.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 = CodeAnalysisHelper.GetNewDiagnostics(compilerDiagnostics, CodeAnalysisHelper.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; } } var newSource = CodeAnalysisHelper.GetStringFromDocument(document); return(newSource); }
/// <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="diagnosticId">Diagnostic id to fix</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, string diagnosticId, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, language); var compilerDiagnostics = GetCompilerDiagnostics(document); if (compilerDiagnostics.Any(x => x.Id == diagnosticId) == false) { var reportedIssues = compilerDiagnostics.Select(x => x.Id).ToList(); Assert.Fail($"There is no issue reported for {diagnosticId}. Reported issues: {string.Join(',', reportedIssues)}"); } foreach (var diagnosticToFix in compilerDiagnostics.Where(x => x.Id == diagnosticId)) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnosticToFix, (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)); 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())); } } //after applying all of the code fixes, compare the resulting string to the inputted one var actual = GetStringFromDocument(document); Assert.AreEqual(newSource, actual); }
/// <summary> /// Get the code actions registered by <paramref name="fix"/> for <paramref name="solution"/>. /// </summary> /// <param name="solution">The solution with the diagnostic.</param> /// <param name="fix">The code fix.</param> /// <param name="diagnostic">The diagnostic.</param> /// <returns>The list of registered actions.</returns> internal static async Task <IReadOnlyList <CodeAction> > GetActionsAsync(Solution solution, CodeFixProvider fix, Diagnostic diagnostic) { var document = solution.GetDocument(diagnostic.Location.SourceTree); var actions = new List <CodeAction>(); var context = new CodeFixContext( document, diagnostic, (a, d) => actions.Add(a), CancellationToken.None); await fix.RegisterCodeFixesAsync(context).ConfigureAwait(false); return(actions); }
/// <summary> /// Get the code actions registered by <paramref name="codeFix"/> for <paramref name="solution"/>. /// </summary> /// <param name="solution">The solution with the diagnostic.</param> /// <param name="codeFix">The code fix.</param> /// <param name="diagnostic">The diagnostic.</param> /// <returns>The list of registered actions.</returns> internal static IReadOnlyList <CodeAction> GetActions(Solution solution, CodeFixProvider codeFix, Diagnostic diagnostic) { var document = solution.GetDocument(diagnostic.Location.SourceTree); var actions = new List <CodeAction>(); var context = new CodeFixContext( document, diagnostic, (a, d) => actions.Add(a), CancellationToken.None); codeFix.RegisterCodeFixesAsync(context).GetAwaiter().GetResult(); return(actions); }
public async Task InvokeAsync() { var actions = ImmutableArray.CreateBuilder <CodeAction>(); void registerCodeFix(CodeAction a, ImmutableArray <Diagnostic> d) => actions.Add(a); var context = new CodeFixContext(document, diagnostic, registerCodeFix, CancellationToken.None); await provider.RegisterCodeFixesAsync(context).ConfigureAwait(false); var codeAction = actions.Single(); operations = await codeAction.GetOperationsAsync(CancellationToken.None).ConfigureAwait(false); }
public async Task <string> GetEquivalenceKeyAsync(CodeFixProvider codeFixProvider, Document document, CancellationToken cancellationToken) { if (codeFixProvider == null) { throw new System.ArgumentException(); } this.codeActions.Clear(); await codeFixProvider .RegisterCodeFixesAsync(new CodeFixContext(document, this.diagnostics[0], (a, _) => codeActions.Add(a), cancellationToken)) .ConfigureAwait(false); return(codeActions.Count > 0 ? codeActions[0].EquivalenceKey ! : string.Empty); }
/// <summary> /// General verifier for a diagnostics that should not have fix registred. /// Creates a Document from the source string, then gets diagnostics on it and verify if it has no fix registred. /// It will fail the test if it has any fix registred to it /// </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="source">A class in the form of a string before the CodeFix was applied to it</param> /// <param name="languageVersionCSharp">C# language version used for compiling the test project, required unless you inform the VB language version.</param> /// <param name="languageVersionVB">VB language version used for compiling the test project, required unless you inform the C# language version.</param> private async static Task VerifyHasNoFixAsync(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string source, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB) { var document = CreateDocument(source, language, languageVersionCSharp, languageVersionVB); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }).ConfigureAwait(true); foreach (var analyzerDiagnostic in analyzerDiagnostics) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostic, (a, d) => actions.Add(a), CancellationToken.None); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(true); Assert.False(actions.Any(), $"Should not have a code fix registered for diagnostic '{analyzerDiagnostic.Id}'"); } }
void VerifyNoFixes(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string source) { var document = CreateDocument(source, language); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document, }); foreach (var analyzerDiagnostic in analyzerDiagnostics) { var context = new CodeFixContext(document, analyzerDiagnostic, (a, d) => { Assert.Fail($"Expected no code actions, got: {a}"); }, default(CancellationToken)); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); } }
public static async Task <Project> ApplyCodeFix(this Project project, Diagnostic diagnostic, CodeFixProvider fix) { var document = project.Solution.GetDocument(diagnostic.Location.SourceTree); var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), CancellationToken.None); await fix.RegisterCodeFixesAsync(context); var operations = await actions.First().GetOperationsAsync(CancellationToken.None); var changeSolution = operations.OfType <ApplyChangesOperation>().First().ChangedSolution; var newProject = changeSolution.Projects.First(); return(newProject); }
private void VerifyNoFixOffered(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string source, bool hasEntrypoint) { var document = CreateDocument(source, language, hasEntrypoint); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(ImmutableArray.Create(analyzer), new[] { document }); 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(); Assert.Empty(actions); } }
/// <summary> /// General verifier for a diagnostics that verifies how many code actions a code fix registered. /// Creates a Document from the source string, then gets diagnostics on it and verify if it has x number of code actions registred. /// It will fail the test if it has a different number of code actions registred to it. /// </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="source">A class in the form of a string before the CodeFix was applied to it</param> /// <param name="languageVersionCSharp">C# language version used for compiling the test project, required unless you inform the VB language version.</param> /// <param name="languageVersionVB">VB language version used for compiling the test project, required unless you inform the C# language version.</param> /// <param name="numberOfCodeActions">The expected number of code actions provided by the code fix.</param> private async static Task VerifyNumberOfCodeActions(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string source, int numberOfCodeActions, LanguageVersion languageVersionCSharp, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersionVB) { var document = CreateDocument(source, language, languageVersionCSharp, languageVersionVB); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzer, new[] { document }).ConfigureAwait(true); foreach (var analyzerDiagnostic in analyzerDiagnostics) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostic, (a, d) => actions.Add(a), CancellationToken.None); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(true); var numberOfCodeActionsFound = actions.Count(); Assert.True(numberOfCodeActions == numberOfCodeActionsFound, $"Should have {numberOfCodeActions} code actions registered for diagnostic '{analyzerDiagnostic.Id}' but got {numberOfCodeActionsFound}."); } }
private async Task <FixedResult> FixCodeSingleDiagnostics(CancellationToken cancellationToken) { var codeActions = new List <CodeAction>(); await codeFixProvider.RegisterCodeFixesAsync(new CodeFixContext(document, this.diagnostics[0], (a, _) => codeActions.Add(a), cancellationToken)).ConfigureAwait(false); if (codeActions.Count == 0) { return(FixedResult.Empty); } var oldSolution = document.Project.Solution; var changedDocument = await ConvertCodeActionToChangedDocument(codeActions[0], cancellationToken).ConfigureAwait(false); return(GetFixedChanges(oldSolution, changedDocument.Project.Solution)); }
private async Task <ImmutableArray <CodeAction> > GetOfferedFixesInternalAsync(string language, string source, int?diagnosticIndex, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, CancellationToken cancellationToken) { var document = this.CreateDocument(source, language); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false); var index = diagnosticIndex.HasValue ? diagnosticIndex.Value : 0; Assert.True(index < analyzerDiagnostics.Count()); var actions = new List <CodeAction>(); var context = new CodeFixContext(document, analyzerDiagnostics[index], (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); return(actions.ToImmutableArray()); }
private async Task <ImmutableArray <CodeAction> > GetCodeFixesAsync(Document document, TextSpan span, DiagnosticDescriptor descriptor) { ImmutableArray <CodeAction> .Builder builder = ImmutableArray.CreateBuilder <CodeAction>(); SyntaxTree tree = await document.GetSyntaxTreeAsync(CancellationToken.None).ConfigureAwait(false); var diagnostic = Diagnostic.Create(descriptor, Location.Create(tree, span)); var context = new CodeFixContext(document, diagnostic, (a, _) => builder.Add(a), CancellationToken.None); CodeFixProvider provider = this.CreateProvider(); provider.RegisterCodeFixesAsync(context).Wait(); return(builder.ToImmutable()); }
/// <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 }); var compilerDiagnostics = GetCompilerDiagnostics(document); var attempts = analyzerDiagnostics.Length; for (var 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.ShouldBeEmpty(() => GetErrorMessage(document, compilerDiagnostics)); } //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); actual.ShouldBe(newSource); }