private static List <string> MergeFixedCode(IReadOnlyList <string> codes, string fixedCode) { var merged = new List <string>(codes.Count); var found = false; var @namespace = CodeReader.Namespace(fixedCode); var fileName = CodeReader.FileName(fixedCode); foreach (var code in codes) { if (CodeReader.FileName(code) == fileName && CodeReader.Namespace(code) == @namespace) { if (found) { throw new AssertException("Expected unique class names."); } merged.Add(fixedCode); found = true; } else { merged.Add(code); } } if (!found) { throw new AssertException("Failed merging expected one class to have same namespace and class name as fixedCode.\r\n" + "Try specifying a list with all fixed code."); } return(merged); }
private static List <string> MergeFixedCode(IReadOnlyList <string> codes, string fixedCode) { var merged = new List <string>(codes.Count); var found = false; var fileName = CodeReader.FileName(fixedCode); foreach (var code in codes) { if (CodeReader.FileName(code) == fileName) { if (found) { throw AssertException.Create("Expected only one with errors indicated."); } merged.Add(fixedCode); found = true; } else { merged.Add(code); } } if (!found) { throw AssertException.Create("Expected one with errors indicated."); } return(merged); }
private static async Task AssertNoCompilerErrorsAsync(CodeFixProvider codeFix, Solution fixedSolution) { var diagnostics = await Analyze.GetDiagnosticsAsync(fixedSolution).ConfigureAwait(false); var introducedDiagnostics = diagnostics .SelectMany(x => x) .Where(IsIncluded) .ToArray(); if (introducedDiagnostics.Select(x => x.Id) .Except(DiagnosticSettings.AllowedErrorIds()) .Any()) { var errorBuilder = StringBuilderPool.Borrow(); errorBuilder.AppendLine($"{codeFix} introduced syntax error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}."); foreach (var introducedDiagnostic in introducedDiagnostics) { var errorInfo = await introducedDiagnostic.ToStringAsync(fixedSolution).ConfigureAwait(false); errorBuilder.AppendLine($"{errorInfo}"); } errorBuilder.AppendLine("First source file with error is:"); var sources = await Task.WhenAll(fixedSolution.Projects.SelectMany(p => p.Documents).Select(d => CodeReader.GetStringFromDocumentAsync(d, Formatter.Annotation, CancellationToken.None))); var lineSpan = introducedDiagnostics.First().Location.GetMappedLineSpan(); var match = sources.SingleOrDefault(x => CodeReader.FileName(x) == lineSpan.Path); errorBuilder.Append(match); errorBuilder.AppendLine(); throw AssertException.Create(errorBuilder.Return()); } }
/// <summary> /// Get the expected diagnostics and cleaned sources. /// </summary> /// <param name="analyzerId">The descriptor diagnosticId that is expected to produce diagnostics.</param> /// <param name="message">The expected message for the diagnostics, can be null.</param> /// <param name="code">The code with errors indicated.</param> /// <returns>An instance of <see cref="DiagnosticsAndSources"/>.</returns> public static DiagnosticsAndSources CreateFromCodeWithErrorsIndicated(string analyzerId, string message, IReadOnlyList <string> code) { if (analyzerId == null) { throw new ArgumentNullException(nameof(analyzerId)); } var diagnostics = new List <ExpectedDiagnostic>(); var cleanedSources = new List <string>(); foreach (var source in code) { var positions = CodeReader.FindDiagnosticsPositions(source).ToArray(); if (positions.Length == 0) { cleanedSources.Add(source); continue; } cleanedSources.Add(source.Replace("↓", string.Empty)); var fileName = CodeReader.FileName(source); diagnostics.AddRange(positions.Select(p => new ExpectedDiagnostic(analyzerId, message, new FileLinePositionSpan(fileName, p, p)))); } return(new DiagnosticsAndSources(diagnostics, cleanedSources)); }
/// <summary> /// Create a <see cref="Solution"/> for <paramref name="code"/> /// Each unique namespace in <paramref name="code"/> is added as a project. /// </summary> /// <param name="code">The code to create the solution from.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/>.</param> /// <param name="metadataReferences">The metadata references.</param> /// <returns>A <see cref="Solution"/>.</returns> public static Solution CreateSolutionWithOneProject(IEnumerable <string> code, CSharpCompilationOptions compilationOptions, IEnumerable <MetadataReference> metadataReferences = null) { var projectInfo = GetProjectInfo(); return(EmptySolution.AddProject(projectInfo)); ProjectInfo GetProjectInfo() { string projectName = null; foreach (var doc in code) { if (projectName == null) { projectName = CodeReader.Namespace(doc); } else { var ns = CodeReader.Namespace(doc); var indexOf = ns.IndexOf('.'); if (indexOf > 0) { ns = ns.Substring(0, indexOf); } if (ns.Length < projectName.Length) { projectName = ns; } } } var projectId = ProjectId.CreateNewId(projectName); return(ProjectInfo.Create( projectId, VersionStamp.Default, projectName, projectName, LanguageNames.CSharp, metadataReferences: metadataReferences, compilationOptions: compilationOptions, documents: code.Select( x => { var documentName = CodeReader.FileName(x); return DocumentInfo.Create( DocumentId.CreateNewId(projectId, documentName), documentName, sourceCodeKind: SourceCodeKind.Regular, loader: TextLoader.From( TextAndVersion.Create( SourceText.From(x, (Encoding)null, SourceHashAlgorithm.Sha1), VersionStamp.Default))); }))); } }
/// <summary> /// Writes the diagnostic and the offending code. /// </summary> /// <returns>A string for use in assert exception.</returns> internal string ToString(IReadOnlyList <string> sources) { if (this.HasPosition) { var path = this.HasPath ? this.Span.Path : CodeReader.FileName(sources.Single()); var match = sources.SingleOrDefault(x => CodeReader.FileName(x) == path); var line = match != null?CodeReader.GetLineWithErrorIndicated(match, this.Span.StartLinePosition) : string.Empty; return($"{this.Id} {this.Message}\r\n" + $" at line {this.Span.StartLinePosition.Line} and character {this.Span.StartLinePosition.Character} in file {path} | {line.TrimStart(' ')}"); } return($"{this.Id} {this.Message}"); }
/// <summary> /// Create a new instance of <see cref="ExpectedDiagnostic"/>. /// </summary> /// <param name="diagnosticId">The expected diagnostic id.</param> /// <param name="message">The expected message.</param> /// <param name="codeWithErrorsIndicated">The code with error position indicated..</param> /// <param name="cleanedSources"><paramref name="codeWithErrorsIndicated"/> without errors indicated.</param> /// <returns>A new instance of <see cref="ExpectedDiagnostic"/>.</returns> public static IReadOnlyList <ExpectedDiagnostic> CreateManyFromCodeWithErrorsIndicated(string diagnosticId, string message, string codeWithErrorsIndicated, out string cleanedSources) { var positions = CodeReader.FindLinePositions(codeWithErrorsIndicated).ToArray(); if (positions.Length == 0) { throw new ArgumentException("Expected one error position indicated, was zero.", nameof(codeWithErrorsIndicated)); } cleanedSources = codeWithErrorsIndicated.Replace("↓", string.Empty); var fileName = CodeReader.FileName(codeWithErrorsIndicated); return(positions.Select(p => new ExpectedDiagnostic(diagnosticId, message, new FileLinePositionSpan(fileName, p, p))) .ToArray()); }
private static async Task VerifyNoCompilerErrorsAsync(CodeFixProvider fix, Solution fixedSolution) { var diagnostics = await Analyze.GetDiagnosticsAsync(fixedSolution).ConfigureAwait(false); var introducedDiagnostics = diagnostics .SelectMany(x => x) .Where(IsIncluded) .ToArray(); if (introducedDiagnostics.Select(x => x.Id) #pragma warning disable CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() .Except(SuppressedDiagnostics) #pragma warning restore CS0618 // Suppress until removed. Will be replaced with Metadatareferences.FromAttributes() .Any()) { var errorBuilder = StringBuilderPool.Borrow(); errorBuilder.AppendLine($"{fix.GetType().Name} introduced syntax error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}."); foreach (var introducedDiagnostic in introducedDiagnostics) { errorBuilder.AppendLine($"{introducedDiagnostic.ToErrorString()}"); } var sources = await Task.WhenAll(fixedSolution.Projects.SelectMany(p => p.Documents).Select(d => CodeReader.GetStringFromDocumentAsync(d, CancellationToken.None))); errorBuilder.AppendLine("First source file with error is:"); var lineSpan = introducedDiagnostics.First().Location.GetMappedLineSpan(); if (sources.TrySingle(x => CodeReader.FileName(x) == lineSpan.Path, out var match)) { errorBuilder.AppendLine(match); } else if (sources.TryFirst(x => CodeReader.FileName(x) == lineSpan.Path, out _)) { errorBuilder.AppendLine($"Found more than one document for {lineSpan.Path}."); foreach (string source in sources.Where(x => CodeReader.FileName(x) == lineSpan.Path)) { errorBuilder.AppendLine(source); } } else { errorBuilder.AppendLine($"Did not find a single document for {lineSpan.Path}."); } throw new AssertException(errorBuilder.Return()); } }
private static async Task AreEqualAsync(IReadOnlyList <string> expected, Solution actual, string?messageHeader) { var actualCount = actual.Projects.SelectMany(x => x.Documents).Count(); if (expected.Count != actualCount) { throw new AssertException($"Expected {expected.Count} documents the fixed solution has {actualCount} documents."); } foreach (var project in actual.Projects) { foreach (var document in project.Documents) { var fixedSource = await CodeReader.GetStringFromDocumentAsync(document, CancellationToken.None).ConfigureAwait(false); CodeAssert.AreEqual(FindExpected(fixedSource), fixedSource, messageHeader); } } string FindExpected(string fixedSource) { var fixedNamespace = CodeReader.Namespace(fixedSource); var fixedFileName = CodeReader.FileName(fixedSource); var match = expected.FirstOrDefault(x => x == fixedSource); if (match != null) { return(match); } foreach (var candidate in expected) { if (CodeReader.Namespace(candidate) == fixedNamespace && CodeReader.FileName(candidate) == fixedFileName) { return(candidate); } } throw new AssertException($"The fixed solution contains a document {fixedFileName} in namespace {fixedNamespace} that is not in the expected documents."); } }
/// <summary> /// Create a new instance of <see cref="ExpectedDiagnostic"/> with position. /// </summary> /// <param name="codeWithErrorsIndicated">The code with error position indicated..</param> /// <param name="cleanedSources"><paramref name="codeWithErrorsIndicated"/> without errors indicated.</param> /// <returns>A new instance of <see cref="ExpectedDiagnostic"/>.</returns> public ExpectedDiagnostic WithPositionFromCodeWithErrorsIndicated(string codeWithErrorsIndicated, out string cleanedSources) { var positions = CodeReader.FindLinePositions(codeWithErrorsIndicated).ToArray(); if (positions.Length == 0) { throw new ArgumentException("Expected one error position indicated, was zero.", nameof(codeWithErrorsIndicated)); } if (positions.Length > 1) { throw new ArgumentException($"Expected one error position indicated, was {positions.Length}.", nameof(codeWithErrorsIndicated)); } cleanedSources = codeWithErrorsIndicated.Replace("↓", string.Empty); var fileName = CodeReader.FileName(codeWithErrorsIndicated); var position = positions[0]; return(new ExpectedDiagnostic(this.Id, this.Message, new FileLinePositionSpan(fileName, position, position))); }
public SourceMetadata(string code) { this.Code = code; this.FileName = CodeReader.FileName(code); this.Namespace = CodeReader.Namespace(code); }
/// <summary> /// Create a <see cref="Solution"/> for <paramref name="code"/> /// Each unique namespace in <paramref name="code"/> is added as a project. /// </summary> /// <param name="code">The code to create the solution from.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/>.</param> /// <param name="metadataReferences">The metadata references.</param> /// <param name="languageVersion">The <see cref="LanguageVersion"/>.</param> /// <returns>A <see cref="Solution"/>.</returns> public static Solution CreateSolution(IEnumerable <string> code, CSharpCompilationOptions compilationOptions, IEnumerable <MetadataReference> metadataReferences = null, LanguageVersion languageVersion = LanguageVersion.Latest) { var solutionInfo = SolutionInfo.Create( SolutionId.CreateNewId("Test.sln"), VersionStamp.Default, projects: GetProjectInfos()); var solution = EmptySolution; foreach (var projectInfo in solutionInfo.Projects) { solution = solution.AddProject(projectInfo.WithProjectReferences(FindReferences(projectInfo))); } return(solution); IEnumerable <ProjectInfo> GetProjectInfos() { var byNamespace = new SortedDictionary <string, List <string> >(); foreach (var document in code) { var ns = CodeReader.Namespace(document); if (byNamespace.TryGetValue(ns, out var doc)) { doc.Add(document); } else { byNamespace[ns] = new List <string> { document }; } } var byProject = new SortedDictionary <string, List <KeyValuePair <string, List <string> > > >(); foreach (var kvp in byNamespace) { var last = byProject.Keys.LastOrDefault(); var ns = kvp.Key; if (last != null && ns.Contains(last)) { byProject[last].Add(kvp); } else { byProject.Add(ns, new List <KeyValuePair <string, List <string> > > { kvp }); } } foreach (var kvp in byProject) { var assemblyName = kvp.Key; var projectId = ProjectId.CreateNewId(assemblyName); yield return(ProjectInfo.Create( projectId, VersionStamp.Default, assemblyName, assemblyName, LanguageNames.CSharp, compilationOptions: compilationOptions, metadataReferences: metadataReferences, documents: kvp.Value.SelectMany(x => x.Value) .Select( x => { var documentName = CodeReader.FileName(x); return DocumentInfo.Create( DocumentId.CreateNewId(projectId, documentName), documentName, sourceCodeKind: SourceCodeKind.Regular, loader: new StringLoader(x)); })) .WithParseOptions(CSharpParseOptions.Default.WithLanguageVersion(languageVersion))); } } IEnumerable <ProjectReference> FindReferences(ProjectInfo projectInfo) { var references = new List <ProjectReference>(); foreach (var other in solutionInfo.Projects.Where(x => x.Id != projectInfo.Id)) { if (projectInfo.Documents.Any(x => x.TextLoader is StringLoader stringLoader && (stringLoader.Code.Contains($"using {other.Name};") || stringLoader.Code.Contains($"{other.Name}.")))) { references.Add(new ProjectReference(other.Id)); } } return(references); } }
/// <summary> /// Verify that two strings of code are equal. Agnostic to end of line characters. /// </summary> /// <param name="expected">The expected code.</param> /// <param name="actual">The actual code.</param> /// <param name="messageHeader">The first line to add to the exception message on error.</param> internal static void AreEqual(string expected, string actual, string messageHeader) { var expectedPos = 0; var actualPos = 0; var line = 1; while (expectedPos < expected.Length && actualPos < actual.Length) { var ec = expected[expectedPos]; var ac = actual[actualPos]; if (ec == '\r' || ac == '\r') { if (ec == '\r') { expectedPos++; } if (ac == '\r') { actualPos++; } continue; } if (ec != ac) { var errorBuilder = StringBuilderPool.Borrow(); if (messageHeader != null) { errorBuilder.AppendLine(messageHeader); } errorBuilder.AppendLine($"Mismatch on line {line} of file {CodeReader.FileName(expected)}"); var expectedLine = expected.Split('\n')[line - 1].Trim('\r'); var actualLine = actual.Split('\n')[line - 1].Trim('\r'); var diffPos = Math.Min(expectedLine.Length, actualLine.Length); for (var i = 0; i < Math.Min(expectedLine.Length, actualLine.Length); i++) { if (expectedLine[i] != actualLine[i]) { diffPos = i; break; } } errorBuilder.AppendLine($"Expected: {expectedLine}") .AppendLine($"Actual: {actualLine}") .AppendLine($" {new string(' ', diffPos)}^") .AppendLine("Expected:") .Append(expected) .AppendLine() .AppendLine("Actual:") .Append(actual) .AppendLine(); throw AssertException.Create(errorBuilder.Return()); } if (ec == '\n') { line++; } expectedPos++; actualPos++; } while (expectedPos < expected.Length && expected[expectedPos] == '\r') { expectedPos++; } while (actualPos < actual.Length && actual[actualPos] == '\r') { actualPos++; } if (expectedPos == expected.Length && actualPos == actual.Length) { return; } if (messageHeader != null) { var message = StringBuilderPool.Borrow() .AppendLine(messageHeader) .AppendLine($"Mismatch at end of file {CodeReader.FileName(expected)}") .AppendLine("Expected:") .Append(expected) .AppendLine() .AppendLine("Actual:") .Append(actual) .AppendLine() .Return(); throw AssertException.Create(message); } else { var message = StringBuilderPool.Borrow() .AppendLine($"Mismatch at end of file {CodeReader.FileName(expected)}") .AppendLine("Expected:") .Append(expected) .AppendLine() .AppendLine("Actual:") .Append(actual) .AppendLine() .Return(); throw AssertException.Create(message); } }