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);
        }
Example #2
0
        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);
        }
Example #3
0
        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());
            }
        }
Example #4
0
        /// <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());
        }
Example #8
0
        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());
            }
        }
Example #9
0
        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)));
        }
Example #11
0
 public SourceMetadata(string code)
 {
     this.Code      = code;
     this.FileName  = CodeReader.FileName(code);
     this.Namespace = CodeReader.Namespace(code);
 }
Example #12
0
        /// <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);
            }
        }
Example #13
0
        /// <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);
            }
        }