private async Task VerifyFixAsync( string language, ImmutableArray <DiagnosticAnalyzer> analyzers, ImmutableArray <CodeFixProvider> codeFixProviders, SolutionState oldState, SolutionState newState, int numberOfIterations, Func <ImmutableArray <DiagnosticAnalyzer>, ImmutableArray <CodeFixProvider>, int?, string?, Project, int, IVerifier, CancellationToken, Task <Project> > getFixedProject, IVerifier verifier, CancellationToken cancellationToken) { var project = await CreateProjectAsync(oldState.Sources.ToArray(), oldState.AdditionalFiles.ToArray(), oldState.AdditionalProjects.ToArray(), oldState.AdditionalReferences.ToArray(), language, cancellationToken); var compilerDiagnostics = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); project = await getFixedProject(analyzers, codeFixProviders, CodeFixIndex, CodeFixEquivalenceKey, project, numberOfIterations, verifier, cancellationToken).ConfigureAwait(false); // After applying all of the code fixes, compare the resulting string to the inputted one var updatedDocuments = project.Documents.ToArray(); verifier.Equal(newState.Sources.Count, updatedDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.Sources)}' and '{nameof(updatedDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.Sources)}' contains '{newState.Sources.Count}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); for (var i = 0; i < updatedDocuments.Length; i++) { var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); verifier.EqualOrDiff(newState.Sources[i].content.ToString(), actual.ToString(), $"content of '{newState.Sources[i].filename}' did not match. Diff shown with expected as baseline:"); verifier.Equal(newState.Sources[i].content.Encoding, actual.Encoding, $"encoding of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.Encoding}' but was '{actual.Encoding}'"); verifier.Equal(newState.Sources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); verifier.Equal(newState.Sources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{newState.Sources[i].filename}' but was '{updatedDocuments[i].Name}'"); } var updatedAdditionalDocuments = project.AdditionalDocuments.ToArray(); verifier.Equal(newState.AdditionalFiles.Count, updatedAdditionalDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' and '{nameof(updatedAdditionalDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' contains '{newState.AdditionalFiles.Count}' documents and '{nameof(updatedAdditionalDocuments)}' contains '{updatedAdditionalDocuments.Length}' documents"); for (var i = 0; i < updatedAdditionalDocuments.Length; i++) { var actual = await updatedAdditionalDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); verifier.EqualOrDiff(newState.AdditionalFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AdditionalFiles[i].filename}' did not match. Diff shown with expected as baseline:"); verifier.Equal(newState.AdditionalFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.Encoding}' but was '{actual.Encoding}'"); verifier.Equal(newState.AdditionalFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); verifier.Equal(newState.AdditionalFiles[i].filename, updatedAdditionalDocuments[i].Name, $"file name was expected to be '{newState.AdditionalFiles[i].filename}' but was '{updatedAdditionalDocuments[i].Name}'"); } }
/// <summary> /// Implements a workaround for issue #936, force re-parsing to get the same sort of syntax tree as the original document. /// </summary> /// <param name="project">The project to update.</param> /// <param name="verifier">The verifier to use for test assertions.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> /// <returns>The updated <see cref="Project"/>.</returns> private async Task <Project> RecreateProjectDocumentsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) { foreach (var documentId in project.DocumentIds) { var document = project.GetDocument(documentId); var initialTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false); var recreatedTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (CodeFixValidationMode != CodeFixValidationMode.None) { try { // We expect the tree produced by the code fix (initialTree) to match the form of the tree produced // by the compiler for the same text (recreatedTree). TreeEqualityVisitor.AssertNodesEqual( verifier, SyntaxKindType, await recreatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false), await initialTree.GetRootAsync(cancellationToken).ConfigureAwait(false), checkTrivia: CodeFixValidationMode == CodeFixValidationMode.Full); } catch { // Try to revalidate the tree with a better message var renderedInitialTree = TreeToString(await initialTree.GetRootAsync(cancellationToken).ConfigureAwait(false), CodeFixValidationMode); var renderedRecreatedTree = TreeToString(await recreatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false), CodeFixValidationMode); verifier.EqualOrDiff(renderedRecreatedTree, renderedInitialTree); // This is not expected to be hit, but it will be hit if the validation failure occurred in a // portion of the tree not captured by the rendered form from TreeToString. throw; } } project = document.Project; } return(project); }