private async Task VerifyFixInternalAsync(string language, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics, int maxNumberOfIterations, Func <ImmutableArray <DiagnosticAnalyzer>, CodeFixProvider, int?, Document, int, CancellationToken, Task <Document> > getFixedDocument, CancellationToken cancellationToken) { var document = this.CreateDocument(oldSource, language); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); document = await getFixedDocument(analyzers, codeFixProvider, codeFixIndex, document, maxNumberOfIterations, cancellationToken).ConfigureAwait(false); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false)); // 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 = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken : cancellationToken).ConfigureAwait(false); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false)); string message = 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())), (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)).ToFullString()); Assert.True(false, message); } // after applying all of the code fixes, compare the resulting string to the inputted one var actual = await GetStringFromDocumentAsync(document, cancellationToken).ConfigureAwait(false); Assert.Equal(newSource, actual); }
public static async Task <ImmutableArray <CodeFixEquivalenceGroup> > CreateAsync(CodeFixProvider codeFixProvider, ImmutableArray <Diagnostic> allDiagnostics, Project project, CancellationToken cancellationToken) { var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(ImmutableArray.Create <CodeFixEquivalenceGroup>()); } var groupLookup = new Dictionary <string, Builder>(); var equivalenceKeys = new HashSet <string>(); foreach (var diagnostic in allDiagnostics) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { continue; } var codeActions = await GetFixesAsync(project, codeFixProvider, diagnostic, cancellationToken).ConfigureAwait(false); equivalenceKeys.Clear(); foreach (var action in codeActions) { if (action.EquivalenceKey != null) { equivalenceKeys.Add(action.EquivalenceKey); } } foreach (var key in equivalenceKeys) { if (!groupLookup.TryGetValue(key, out var group)) { groupLookup.Add(key, group = new Builder(key, project, fixAllProvider, codeFixProvider)); } group.AddDiagnostic(diagnostic); } } return(groupLookup.Select(x => x.Value.ToEquivalenceGroup()).ToImmutableArray()); }
private static Task <Document> GetFixAllAnalyzerSolutionAsync(ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Document document, int numberOfIterations, CancellationToken cancellationToken) { return(GetFixAllAnalyzerAsync(FixAllScope.Solution, analyzers, codeFixProvider, codeFixIndex, document, numberOfIterations, cancellationToken)); }
public MockAnalyzerReference(CodeFixProvider codeFix) { Fixer = codeFix; }
public static void CodeFixProvider_uses_simplified_name_([ValueSource(nameof(AllCodeFixProviders))] CodeFixProvider provider) { var id = provider.FixableDiagnosticIds.First(); Assert.That(provider.GetType().Name, Does.StartWith(id).And.EndsWith("_CodeFixProvider")); }
private async Task VerifyFixInternalAsync( string language, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, string[] oldSources, string[] newSources, int?codeFixIndex, bool allowNewCompilerDiagnostics, int numberOfIterations, Func <ImmutableArray <DiagnosticAnalyzer>, CodeFixProvider, int?, Project, int, CancellationToken, Task <Project> > getFixedProject, CancellationToken cancellationToken) { var project = this.CreateProject(oldSources, language); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); project = await getFixedProject(analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken).ConfigureAwait(false); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false)); // 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 project = await ReformatProjectDocumentsAsync(project, cancellationToken).ConfigureAwait(false); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false)); var message = new StringBuilder(); message.Append("Fix introduced new compiler diagnostics:\r\n"); newCompilerDiagnostics.Aggregate(message, (sb, d) => sb.Append(d.ToString()).Append("\r\n")); foreach (var document in project.Documents) { message.Append("\r\n").Append(document.Name).Append(":\r\n"); message.Append((await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)).ToFullString()); message.Append("\r\n"); } Assert.Fail(message.ToString()); } // After applying all of the code fixes, compare the resulting string to the inputted one var updatedDocuments = project.Documents.ToArray(); Assert.AreEqual($"{newSources.Length} documents", $"{updatedDocuments.Length} documents"); for (var i = 0; i < updatedDocuments.Length; i++) { var actual = await GetStringFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); var expected = newSources[i].NormalizeNewLine(); actual = actual.NormalizeNewLine(); if (expected != actual) { Console.WriteLine("Expected:"); Console.Write(expected); Console.WriteLine(); Console.WriteLine("Actual:"); Console.Write(actual); } Assert.AreEqual(expected, actual); } }
private static Tuple <TestWorkspace, TestDiagnosticAnalyzerService, CodeFixService, IErrorLoggerService> ServiceSetup(CodeFixProvider codefix) { var diagnosticService = new TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); var fixers = SpecializedCollections.SingletonEnumerable( new Lazy <CodeFixProvider, CodeChangeProviderMetadata>( () => codefix, new CodeChangeProviderMetadata("Test", languages: LanguageNames.CSharp))); var code = @"class Program { }"; var workspace = TestWorkspace.CreateCSharp(code); var logger = SpecializedCollections.SingletonEnumerable(new Lazy <IErrorLoggerService>(() => new TestErrorLogger())); var errorLogger = logger.First().Value; var fixService = new CodeFixService( diagnosticService, logger, fixers, SpecializedCollections.EmptyEnumerable <Lazy <ISuppressionFixProvider, CodeChangeProviderMetadata> >()); return(Tuple.Create(workspace, diagnosticService, fixService, errorLogger)); }
private static async Task GetFirstDiagnosticWithFixWithExceptionValidationAsync(CodeFixProvider codefix) { var tuple = ServiceSetup(codefix); using var workspace = tuple.workspace; var errorReportingService = (TestErrorReportingService)workspace.Services.GetRequiredService <IErrorReportingService>(); var errorReported = false; errorReportingService.OnError = message => errorReported = true; GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager); var unused = await tuple.codeFixService.GetMostSevereFixAsync( document, TextSpan.FromBounds(0, 0), CodeActionRequestPriority.None, CodeActionOptions.Default, CancellationToken.None); Assert.True(extensionManager.IsDisabled(codefix)); Assert.False(extensionManager.IsIgnored(codefix)); Assert.True(errorReported); }
private static void RunCodeFixWhileDocumentChanges(DiagnosticAnalyzer diagnosticAnalyzer, CodeFixProvider codeFixProvider, string codeFixTitle, Document document, ParseOptions parseOption, string pathToExpected) { var currentDocument = document; CalculateDiagnosticsAndCode(diagnosticAnalyzer, currentDocument, parseOption, out var diagnostics, out var actualCode); diagnostics.Should().NotBeEmpty(); string codeBeforeFix; var codeFixExecutedAtLeastOnce = false; do { codeBeforeFix = actualCode; var codeFixExecuted = false; for (var diagnosticIndexToFix = 0; !codeFixExecuted && diagnosticIndexToFix < diagnostics.Count; diagnosticIndexToFix++) { var codeActionsForDiagnostic = GetCodeActionsForDiagnostic(codeFixProvider, currentDocument, diagnostics[diagnosticIndexToFix]); if (TryGetCodeActionToApply(codeFixTitle, codeActionsForDiagnostic, out var codeActionToExecute)) { currentDocument = ApplyCodeFix(currentDocument, codeActionToExecute); CalculateDiagnosticsAndCode(diagnosticAnalyzer, currentDocument, parseOption, out diagnostics, out actualCode); codeFixExecutedAtLeastOnce = true; codeFixExecuted = true; } } } while (codeBeforeFix != actualCode); codeFixExecutedAtLeastOnce.Should().BeTrue(); var actualWithUnixLineEnding = actualCode.Replace(WindowsLineEnding, UnixLineEnding); var expectedWithUnixLineEnding = File.ReadAllText(pathToExpected).Replace(WindowsLineEnding, UnixLineEnding); actualWithUnixLineEnding.Should().Be(expectedWithUnixLineEnding); }
private static Task <ImmutableArray <CodeFixCollection> > GetAddedFixesWithExceptionValidationAsync(CodeFixProvider codefix) => GetAddedFixesAsync(codefix, diagnosticAnalyzer: new MockAnalyzerReference.MockDiagnosticAnalyzer(), exception: true);
private static async Task <ImmutableArray <CodeFixCollection> > GetAddedFixesAsync(CodeFixProvider codefix, DiagnosticAnalyzer diagnosticAnalyzer, bool exception = false, bool throwExceptionInFixerCreation = false) { var tuple = ServiceSetup(codefix, throwExceptionInFixerCreation: throwExceptionInFixerCreation); using var workspace = tuple.workspace; var errorReportingService = (TestErrorReportingService)workspace.Services.GetRequiredService <IErrorReportingService>(); var errorReported = false; errorReportingService.OnError = message => errorReported = true; GetDocumentAndExtensionManager(tuple.analyzerService, workspace, out var document, out var extensionManager); var incrementalAnalyzer = (IIncrementalAnalyzerProvider)tuple.analyzerService; var analyzer = incrementalAnalyzer.CreateIncrementalAnalyzer(workspace); var reference = new MockAnalyzerReference(codefix, ImmutableArray.Create(diagnosticAnalyzer)); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); document = project.Documents.Single(); var options = CodeActionOptions.Default; var fixes = await tuple.codeFixService.GetFixesAsync(document, TextSpan.FromBounds(0, 0), options, CancellationToken.None); if (exception) { Assert.True(extensionManager.IsDisabled(codefix)); Assert.False(extensionManager.IsIgnored(codefix)); } Assert.Equal(exception || throwExceptionInFixerCreation, errorReported); return(fixes); }
private static Project ApplyFix(Project project, DiagnosticAnalyzer analyzer, CodeFixProvider fix, int fixIndex) { var diagnostics = GetDiagnostics(project, analyzer); var fixableDiagnostics = diagnostics.Where(d => fix.FixableDiagnosticIds.Contains(d.Id)).ToArray(); var attempts = fixableDiagnostics.Length; for (int i = 0; i < attempts; i++) { var diag = fixableDiagnostics.First(); var doc = project.Documents.FirstOrDefault(d => d.Name == diag.Location.SourceTree.FilePath); if (doc == null) { fixableDiagnostics = fixableDiagnostics.Skip(1).ToArray(); continue; } var actions = new List <CodeAction>(); var fixContex = new CodeFixContext(doc, diag, (a, d) => actions.Add(a), CancellationToken.None); fix.RegisterCodeFixesAsync(fixContex).Wait(); if (!actions.Any()) { break; } var codeAction = actions[fixIndex]; var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result; var solution = operations.OfType <ApplyChangesOperation>().Single().ChangedSolution; project = solution.GetProject(project.Id); fixableDiagnostics = GetDiagnostics(project, analyzer) .Where(d => fix.FixableDiagnosticIds.Contains(d.Id)).ToArray(); if (!fixableDiagnostics.Any()) { break; } } return(project); }
private async Task <CodeAction> GetFixAsync( ImmutableArray <Diagnostic> diagnostics, DiagnosticDescriptor descriptor, CodeFixProvider fixer, Project project, CancellationToken cancellationToken) { if (diagnostics.Length == 1) { return(await GetFixAsync(diagnostics[0], fixer, project, cancellationToken).ConfigureAwait(false)); } FixAllProvider fixAllProvider = fixer.GetFixAllProvider(); if (fixAllProvider == null) { if (Options.DiagnosticIdsFixableOneByOne.Contains(descriptor.Id)) { return(await GetFixAsync(diagnostics[0], fixer, project, cancellationToken).ConfigureAwait(false)); } WriteLine($" '{fixer.GetType().FullName}' does not have FixAllProvider", ConsoleColor.Yellow, Verbosity.Diagnostic); return(null); } if (!fixAllProvider.GetSupportedFixAllDiagnosticIds(fixer).Any(f => f == descriptor.Id)) { WriteLine($" '{fixAllProvider.GetType().FullName}' does not support diagnostic '{descriptor.Id}'", ConsoleColor.Yellow, Verbosity.Diagnostic); return(null); } if (!fixAllProvider.GetSupportedFixAllScopes().Any(f => f == FixAllScope.Project)) { WriteLine($" '{fixAllProvider.GetType().FullName}' does not support scope '{FixAllScope.Project}'", ConsoleColor.Yellow, Verbosity.Diagnostic); return(null); } var multipleFixesInfos = new HashSet <MultipleFixesInfo>(); CodeAction action = null; Options.DiagnosticFixMap.TryGetValue(descriptor.Id, out string equivalenceKey); foreach (Diagnostic diagnostic in diagnostics) { cancellationToken.ThrowIfCancellationRequested(); if (!diagnostic.Location.IsInSource) { continue; } Document document = project.GetDocument(diagnostic.Location.SourceTree); if (document == null) { continue; } CodeAction fixCandidate = await GetFixAsync(diagnostic, fixer, document, multipleFixesInfos, cancellationToken).ConfigureAwait(false); if (fixCandidate == null) { continue; } if (equivalenceKey != null && equivalenceKey != fixCandidate.EquivalenceKey) { break; } action = fixCandidate; var fixAllContext = new FixAllContext( document, fixer, FixAllScope.Project, action.EquivalenceKey, new string[] { descriptor.Id }, new FixAllDiagnosticProvider(diagnostics), cancellationToken); CodeAction fix = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (fix != null) { WriteLine($" CodeFixProvider: '{fixer.GetType().FullName}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); if (!string.IsNullOrEmpty(action.EquivalenceKey)) { WriteLine($" EquivalenceKey: '{action.EquivalenceKey}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); } WriteLine($" FixAllProvider: '{fixAllProvider.GetType().FullName}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); return(fix); } WriteLine($" Fixer '{fixer.GetType().FullName}' registered no action for diagnostic '{descriptor.Id}'", ConsoleColor.DarkGray, Verbosity.Diagnostic); WriteDiagnostics(diagnostics, baseDirectoryPath: Path.GetDirectoryName(project.FilePath), formatProvider: FormatProvider, indentation: " ", maxCount: 10, verbosity: Verbosity.Diagnostic); } return(null); }
private async Task <DiagnosticFixKind> FixDiagnosticsAsync( ImmutableArray <Diagnostic> diagnostics, DiagnosticDescriptor descriptor, ImmutableArray <CodeFixProvider> fixers, Project project, CancellationToken cancellationToken) { string diagnosticId = descriptor.Id; WriteLine($" Fix {diagnostics.Length} {diagnosticId} '{descriptor.Title}'", diagnostics[0].Severity.GetColor(), Verbosity.Normal); WriteDiagnostics(diagnostics, baseDirectoryPath: Path.GetDirectoryName(project.FilePath), formatProvider: FormatProvider, indentation: " ", verbosity: Verbosity.Detailed); CodeFixProvider fixer = null; CodeAction fix = null; for (int i = 0; i < fixers.Length; i++) { cancellationToken.ThrowIfCancellationRequested(); CodeAction fixCandidate = await GetFixAsync( diagnostics, descriptor, fixers[i], project, cancellationToken).ConfigureAwait(false); if (fixCandidate != null) { if (fix == null) { if (Options.DiagnosticFixerMap.IsEmpty || !Options.DiagnosticFixerMap.TryGetValue(diagnosticId, out string fullTypeName) || string.Equals(fixers[i].GetType().FullName, fullTypeName, StringComparison.Ordinal)) { fix = fixCandidate; fixer = fixers[i]; } } else if (Options.DiagnosticFixerMap.IsEmpty || !Options.DiagnosticFixerMap.ContainsKey(diagnosticId)) { WriteMultipleFixersSummary(diagnosticId, fixer, fixers[i]); return(DiagnosticFixKind.MultipleFixers); } } } if (fix != null) { ImmutableArray <CodeActionOperation> operations = await fix.GetOperationsAsync(cancellationToken).ConfigureAwait(false); if (operations.Length == 1) { operations[0].Apply(Workspace, cancellationToken); return((diagnostics.Length != 1 && fixer.GetFixAllProvider() == null) ? DiagnosticFixKind.PartiallyFixed : DiagnosticFixKind.Fixed); } else if (operations.Length > 1) { WriteMultipleOperationsSummary(fix); } } return(DiagnosticFixKind.NotFixed); }
private static Task <Project> FixAllAnalyzerDiagnosticsInSolutionAsync(ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) { return(FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Solution, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken)); }
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) { // to support diagnostics without fixers if (fixer == null) { result.Add(Tuple.Create(diagnostic, (CodeFixCollection)null)); continue; } 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); }
private static async Task <Project> FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope scope, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) { var expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(null); } bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } if (--numberOfIterations < 0) { Assert.Fail("The upper limit for the number of fix all iterations was exceeded"); } Diagnostic firstDiagnostic = null; string equivalenceKey = null; foreach (var diagnostic in analyzerDiagnostics) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } var actions = new List <CodeAction>(); var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (actions.Count > (codeFixIndex ?? 0)) { firstDiagnostic = diagnostic; equivalenceKey = actions[codeFixIndex ?? 0].EquivalenceKey; break; } } if (firstDiagnostic == null) { return(project); } previousDiagnostics = analyzerDiagnostics; done = true; FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); var analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id); var compilerDiagnosticIds = codeFixProvider.FixableDiagnosticIds.Where(x => x.StartsWith("CS", StringComparison.Ordinal)); var disabledDiagnosticIds = project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key); var relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct(); var fixAllContext = new FixAllContext(project.GetDocument(firstDiagnostic.Location.SourceTree), codeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken); var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (action == null) { return(project); } var fixedProject = await ApplyFixAsync(project, action, cancellationToken).ConfigureAwait(false); if (fixedProject != project) { done = false; project = await RecreateProjectDocumentsAsync(fixedProject, cancellationToken).ConfigureAwait(false); } }while (!done); if (expectedNumberOfIterations >= 0) { Assert.AreEqual($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); } return(project); }
protected CSharpCodeFixProviderTest() { _diagnosticAnalyzer = new TDiagnosticAnalyzer(); _codeFixProvider = new TCodeFixProvider(); }
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 ?? 0; Assert.True(index < analyzerDiagnostics.Count()); var actions = new List <CodeAction>(); // do not pass unsupported diagnostics to a code fix provider if (codeFixProvider.FixableDiagnosticIds.Contains(analyzerDiagnostics[index].Id)) { var context = new CodeFixContext(document, analyzerDiagnostics[index], (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); } return(actions.ToImmutableArray()); }
private static async Task <DiagnosticsMetadata> CreateDiagnosticsMetadataAsync(DiagnosticAnalyzer analyzer, CodeFixProvider codeFix, DiagnosticsAndSources diagnosticsAndSources, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReference) { AssertCodeFixCanFixDiagnosticsFromAnalyzer(analyzer, codeFix); var data = await DiagnosticsWithMetadataAsync(analyzer, diagnosticsAndSources, compilationOptions, metadataReference) .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); } return(data); }
public MockAnalyzerReference() { Fixer = new MockFixer(); }
private static async Task <IEnumerable <CodeAction> > GetFixesAsync(Solution solution, CodeFixProvider codeFixProvider, Diagnostic diagnostic, CancellationToken cancellationToken) { List <CodeAction> codeActions = new List <CodeAction>(); await codeFixProvider.RegisterCodeFixesAsync(new CodeFixContext(solution.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => codeActions.Add(a), cancellationToken)).ConfigureAwait(false); return(codeActions); }
public static void CodeFixProvider_are_marked_with_ExportCodeFixProvider_attribute_([ValueSource(nameof(AllCodeFixProviders))] CodeFixProvider provider) { var type = provider.GetType(); Assert.That(type, Has.Attribute <ExportCodeFixProviderAttribute>().With.Property(nameof(ExportCodeFixProviderAttribute.Name)).EqualTo(type.Name)); }
/// <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="env">Information about the code environment.</param> /// <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(TestEnvironment env, string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(env, oldSource, language); var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(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(); 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); }
public Builder(string equivalenceKey, Project project, FixAllProvider fixAllProvider, CodeFixProvider codeFixProvider) { this.equivalenceKey = equivalenceKey; this.project = project; this.fixAllProvider = fixAllProvider; this.codeFixProvider = codeFixProvider; documentDiagnostics = new Dictionary <string, List <Diagnostic> >(); projectDiagnostics = new List <Diagnostic>(); }
private async Task VerifyFixAsync(AnalyzerVerificationContext context, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int?codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(context, oldSource); var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(context, analyzer, new[] { document }); var compilerDiagnostics = (await GetCompilerDiagnosticsAsync(document)).ToList(); var attempts = analyzerDiagnostics.Length; for (var i = 0; i < attempts; ++i) { var actions = new List <CodeAction>(); var codeFixContext = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); await codeFixProvider.RegisterCodeFixesAsync(codeFixContext); if (!actions.Any()) { break; } if (codeFixIndex != null) { document = await ApplyFixAsync(document, actions.ElementAt((int)codeFixIndex)); break; } document = await ApplyFixAsync(document, actions.ElementAt(0)); analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(context, analyzer, new[] { document }); var newCompilerDiagnostics = GetNewDiagnostics(context, compilerDiagnostics, await GetCompilerDiagnosticsAsync(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 var root = await document.GetSyntaxRootAsync(); Assert.NotNull(root); document = document.WithSyntaxRoot(Formatter.Format(root, Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(context, compilerDiagnostics, await GetCompilerDiagnosticsAsync(document)); var diagnostics = string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())); var newDoc = root.ToFullString(); Assert.True(false, $"Fix introduced new compiler diagnostics:\r\n{diagnostics}\r\n\r\nNew document:\r\n{newDoc}\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); Assert.Equal(newSource, actual); }
private static async Task <Document> GetSingleAnalyzerDocumentAsync(ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Document document, int numberOfIterations, CancellationToken cancellationToken) { int expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } 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 (--numberOfIterations < 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++) { if (!codeFixProvider.FixableDiagnosticIds.Contains(analyzerDiagnostics[i].Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } 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); if (expectedNumberOfIterations >= 0) { Assert.Equal($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); } return(document); }
private static async Task <Project> FixEachAnalyzerDiagnosticAsync(ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) { var expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } if (--numberOfIterations < 0) { Assert.Fail("The upper limit for the number of code fix iterations was exceeded"); } previousDiagnostics = analyzerDiagnostics; done = true; foreach (var diagnostic in analyzerDiagnostics) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } var actions = new List <CodeAction>(); var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (actions.Count > 0) { var fixedProject = await ApplyFixAsync(project, actions.ElementAt(codeFixIndex.GetValueOrDefault(0)), cancellationToken).ConfigureAwait(false); if (fixedProject != project) { done = false; project = await RecreateProjectDocumentsAsync(fixedProject, cancellationToken).ConfigureAwait(false); break; } } } }while (!done); if (expectedNumberOfIterations >= 0) { Assert.AreEqual($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); } return(project); }
private static async Task <Document> GetFixAllAnalyzerAsync(FixAllScope scope, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Document document, int numberOfIterations, CancellationToken cancellationToken) { int expectedNumberOfIterations = numberOfIterations; if (numberOfIterations < 0) { numberOfIterations = -numberOfIterations; } var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(null); } bool done; do { var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false); if (analyzerDiagnostics.Length == 0) { break; } if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) { break; } if (--numberOfIterations < 0) { Assert.True(false, "The upper limit for the number of fix all iterations was exceeded"); } string equivalenceKey = null; foreach (var diagnostic in analyzerDiagnostics) { if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) { // do not pass unsupported diagnostics to a code fix provider continue; } var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (actions.Count > (codeFixIndex ?? 0)) { equivalenceKey = actions[codeFixIndex ?? 0].EquivalenceKey; break; } } previousDiagnostics = analyzerDiagnostics; done = true; FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); IEnumerable <string> analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id); IEnumerable <string> compilerDiagnosticIds = codeFixProvider.FixableDiagnosticIds.Where(x => x.StartsWith("CS", StringComparison.Ordinal)); IEnumerable <string> disabledDiagnosticIds = document.Project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key); IEnumerable <string> relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct(); FixAllContext fixAllContext = new FixAllContext(document, codeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken); CodeAction action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (action == null) { return(document); } var fixedDocument = await ApplyFixAsync(document, action, 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); } }while (!done); if (expectedNumberOfIterations >= 0) { Assert.Equal($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); } return(document); }
private static async Task <Document> GetFixAllAnalyzerAsync(FixAllScope scope, ImmutableArray <DiagnosticAnalyzer> analyzers, CodeFixProvider codeFixProvider, int?codeFixIndex, Document document, int maxNumberOfIterations, CancellationToken cancellationToken) { var previousDiagnostics = ImmutableArray.Create <Diagnostic>(); var fixAllProvider = codeFixProvider.GetFixAllProvider(); if (fixAllProvider == null) { return(null); } 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 fix all iterations was exceeded"); } string equivalenceKey = null; foreach (var diagnostic in analyzerDiagnostics) { var actions = new List <CodeAction>(); var context = new CodeFixContext(document, diagnostic, (a, d) => actions.Add(a), cancellationToken); await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); if (actions.Count > (codeFixIndex ?? 0)) { equivalenceKey = actions[codeFixIndex ?? 0].EquivalenceKey; break; } } previousDiagnostics = analyzerDiagnostics; done = true; FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); FixAllContext fixAllContext = new FixAllContext(document, codeFixProvider, scope, equivalenceKey, codeFixProvider.FixableDiagnosticIds, fixAllDiagnosticProvider, cancellationToken); CodeAction action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); if (action == null) { return(document); } var fixedDocument = await ApplyFixAsync(document, action, 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); } }while (!done); return(document); }