public string TypeArgsText(SyntaxNodeAnalysisContext context) { var builder = StringBuilderPool.Borrow(); for (var i = 0; i < this.Count; i++) { if (i > 0) { _ = builder.Append(", "); } _ = builder.Append(this[i].ToString(context)); } if (!this.method.ReturnsVoid) { if (builder.Length > 0) { _ = builder.Append(", "); } _ = builder.Append(this.method.ReturnType.ToString(context)); } return(builder.Return()); }
/// <summary> /// Check the solution for compiler errors and warnings, uses: /// </summary> public static async Task NoCompilerErrorsAsync(Solution solution, IReadOnlyList <string> allowedIds, AllowedDiagnostics allowedDiagnostics) { var diagnostics = await Analyze.GetDiagnosticsAsync(solution).ConfigureAwait(false); var introducedDiagnostics = diagnostics .SelectMany(x => x) .Where(x => IsIncluded(x, allowedDiagnostics)) .ToArray(); if (introducedDiagnostics.Select(x => x.Id) .Except(allowedIds ?? Enumerable.Empty <string>()) .Any()) { var error = StringBuilderPool.Borrow(); error.AppendLine($"Found error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}."); foreach (var introducedDiagnostic in introducedDiagnostics) { var errorInfo = await introducedDiagnostic.ToStringAsync(solution).ConfigureAwait(false); error.AppendLine($"{errorInfo}"); } throw AssertException.Create(StringBuilderPool.Return(error)); } }
/// <summary> /// Returns what System.Type.FullName returns. /// </summary> /// <param name="type">The <see cref="INamedTypeSymbol"/>.</param> /// <returns>What System.Type.FullName returns.</returns> public static string FullName(this INamedTypeSymbol type) { if (type is null) { throw new ArgumentNullException(nameof(type)); } var builder = StringBuilderPool.Borrow(); var previous = default(SymbolDisplayPart); foreach (var part in type.ToDisplayParts(Simple)) { switch (part.Kind) { case SymbolDisplayPartKind.ClassName: case SymbolDisplayPartKind.InterfaceName: case SymbolDisplayPartKind.StructName: case SymbolDisplayPartKind.NamespaceName: if (part.Symbol is { } symbol) { builder.Append(symbol.MetadataName); } else { throw new InvalidOperationException($"Part symbol is null {part}."); } break;
internal static bool TryGetTypesArrayText(ImmutableArray <IParameterSymbol> parameters, SemanticModel semanticModel, int position, out string typesArrayText) { if (parameters.Length == 0) { typesArrayText = "Type.EmptyTypes"; return(true); } var builder = StringBuilderPool.Borrow().Append("new[] { "); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; if (!semanticModel.IsAccessible(position, parameter.Type)) { _ = builder.Return(); typesArrayText = null; return(false); } if (i != 0) { _ = builder.Append(", "); } _ = builder.Append("typeof(") .Append(parameter.Type.ToString(semanticModel, position)) .Append(")"); } typesArrayText = builder.Append(" }").Return(); return(true); }
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()); } }
private static void NoCompilerErrors(IReadOnlyList <ImmutableArray <Diagnostic> > diagnostics, IReadOnlyList <string> allowedIds, AllowedDiagnostics allowedDiagnostics) { var introducedDiagnostics = diagnostics .SelectMany(x => x) .Where(x => IsIncluded(x, allowedDiagnostics)) .Where(x => !IsExcluded(x)) .ToArray(); if (introducedDiagnostics.Select(x => x.Id) .Except(allowedIds ?? Enumerable.Empty <string>()) .Any()) { var error = StringBuilderPool.Borrow(); error.AppendLine($"Found error{(introducedDiagnostics.Length > 1 ? "s" : string.Empty)}."); foreach (var introducedDiagnostic in introducedDiagnostics) { error.AppendLine($"{introducedDiagnostic.ToErrorString()}"); } throw new AssertException(StringBuilderPool.Return(error)); } bool IsExcluded(Diagnostic diagnostic) { if (allowedIds.Contains(diagnostic.Id)) { return(true); } return(diagnostic.Id switch { "CS1061" when diagnostic.GetMessage(CultureInfo.InvariantCulture).Contains("does not contain a definition for 'InitializeComponent' and no accessible extension method 'InitializeComponent' accepting a first argument of type") => true, _ => false, }); }
// ReSharper disable once UnusedMethodReturnValue.Local private static DocumentEditor AddDefaultFieldWithDocs(DocumentEditor editor, ClassDeclarationSyntax containingType) { var code = StringBuilderPool.Borrow() .AppendLine(DefaultDocs) .AppendLine(string.Format(DefaultFieldFormat, Modifier(containingType), containingType.Identifier.ValueText)) .Return(); return(editor.AddField(containingType, ParseField(code)) .Seal(containingType)); }
public void Parallel() { using var builder1 = StringBuilderPool.Borrow(); using var builder2 = StringBuilderPool.Borrow(); builder1.Append("a"); builder2.Append("b"); Assert.AreNotSame(GetInner(builder1), GetInner(builder2)); Assert.AreEqual("a", builder1.ToString()); Assert.AreEqual("b", builder2.ToString()); }
// ReSharper disable once UnusedMethodReturnValue.Local private static DocumentEditor AddDefaultPropertyWithDocs(DocumentEditor editor, ClassDeclarationSyntax containingType) { var code = StringBuilderPool.Borrow() .AppendLine(DefaultDocs) .AppendLine(string.Format(CultureInfo.InvariantCulture, DefaultPropertyFormat, Modifier(containingType), containingType.Identifier.ValueText)) .Return(); return(editor.AddProperty(containingType, ParseProperty(code)) .Seal(containingType)); }
public void BorrowAppendLineReturn() { var text = StringBuilderPool.Borrow() .AppendLine("a") .AppendLine() .AppendLine("b") .Return(); Assert.AreEqual("a\r\n\r\nb\r\n", text); }
private static MethodDeclarationSyntax IMultiValueConverterConvertBack(string containingTypeName) { var code = StringBuilderPool.Borrow() .AppendLine(" object[] System.Windows.Data.IMultiValueConverter.ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)") .AppendLine(" {") .AppendLine($" throw new System.NotSupportedException($\"{{nameof({containingTypeName})}} can only be used in OneWay bindings\");") .AppendLine(" }") .Return(); return(ParseMethod(code)); }
/// <summary> /// Assert that <paramref name="diagnostics"/> is empty. Throw an AssertException with details if not. /// </summary> /// <param name="diagnostics">The diagnostics.</param> public static void NoDiagnostics(IReadOnlyList <ImmutableArray <Diagnostic> > diagnostics) { if (diagnostics.Any(x => x.Any())) { var builder = StringBuilderPool.Borrow().AppendLine("Expected no diagnostics, found:"); foreach (var diagnostic in diagnostics.SelectMany(x => x)) { builder.AppendLine(diagnostic.ToErrorString()); } throw new AssertException(builder.Return()); } }
/// <summary> /// Gets the line indicated by <paramref name="position"/> and inserts ↓ before the character. /// </summary> /// <param name="code">The code to parse.</param> /// <param name="position">The error position.</param> /// <returns>A string with the line with error indicated.</returns> public static string GetLineWithErrorIndicated(string code, LinePosition position) { var builder = StringBuilderPool.Borrow(); var line = 0; var character = 0; foreach (var c in code) { if (c == '\r') { continue; } if (c == '\n') { if (!builder.IsEmpty) { return(builder.Return()); } line++; character = 0; continue; } if (line == position.Line) { if (character == position.Character) { builder.Append('↓'); } builder.Append(c); } character++; } if (!builder.IsEmpty) { if (position.Character == builder.Length) { builder.Append('↓'); } return(builder.Return()); } StringBuilderPool.Return(builder); return($"Code dod not have position {position}"); }
internal static string ToDisplayString(this BindingFlags flags, SyntaxNode?location) { var usingStatic = IsUsingStatic(location); var builder = StringBuilderPool.Borrow(); //// below is in specific order. AppendIf(BindingFlags.Public); AppendIf(BindingFlags.NonPublic); AppendIf(BindingFlags.Static); AppendIf(BindingFlags.Instance); AppendIf(BindingFlags.DeclaredOnly); AppendIf(BindingFlags.FlattenHierarchy); // below is in no specific order AppendIf(BindingFlags.CreateInstance); AppendIf(BindingFlags.Default); AppendIf(BindingFlags.ExactBinding); AppendIf(BindingFlags.GetField); AppendIf(BindingFlags.GetProperty); AppendIf(BindingFlags.IgnoreCase); AppendIf(BindingFlags.InvokeMethod); AppendIf(BindingFlags.IgnoreReturn); AppendIf(BindingFlags.OptionalParamBinding); AppendIf(BindingFlags.PutDispProperty); AppendIf(BindingFlags.PutRefDispProperty); AppendIf(BindingFlags.SetField); AppendIf(BindingFlags.SetProperty); AppendIf(BindingFlags.SuppressChangeType); AppendIf(BindingFlags.DoNotWrapExceptions); return(builder.Return()); void AppendIf(BindingFlags flag) { if (flags.HasFlagFast(flag)) { if (builder.Length != 0) { _ = builder.Append(" | "); } if (!usingStatic) { _ = builder.Append("BindingFlags."); } _ = builder.Append(flag.Name()); } } }
private static bool ShouldKebabCase(PathSegment segment, out string kebabCase) { if (segment.Parameter == null && IsHumpOrSnakeCased(segment.Span)) { var builder = StringBuilderPool.Borrow(); for (var i = 0; i < segment.Span.Length; i++) { var c = segment.Span[i]; if (char.IsUpper(c)) { if (i > 0) { _ = builder.Append("-"); } _ = builder.Append(char.ToLower(c)); } else if (c == '_') { _ = builder.Append("-"); } else { _ = builder.Append(c); } } kebabCase = builder.Return(); return(true); } kebabCase = null; return(false); bool IsHumpOrSnakeCased(Span span) { for (var i = 0; i < span.Length; i++) { var c = span[i]; if (char.IsUpper(c) || c == '_') { return(true); } } return(false); } }
private static bool IsCorrectDelegateType(MethodTypes methodTypes, IMethodSymbol delegateMethod, SyntaxNodeAnalysisContext context, out string delegateText) { if (!methodTypes.ReturnType.Equals(delegateMethod.ReturnType)) { delegateText = DelegateText(); return(false); } if (methodTypes.Count == delegateMethod.Parameters.Length) { for (var i = 0; i < delegateMethod.Parameters.Length; i++) { if (!methodTypes[i].Equals(delegateMethod.Parameters[i].Type)) { delegateText = DelegateText(); return(false); } } delegateText = null; return(true); } delegateText = DelegateText(); return(false); string DelegateText() { if (methodTypes.ReturnType.SpecialType == SpecialType.System_Void) { if (methodTypes.Count == 0) { return("System.Action"); } return(StringBuilderPool.Borrow() .Append("System.Action<") .Append(methodTypes.TypeArgsText(context)) .Append(">") .Return()); } return(StringBuilderPool.Borrow() .Append("System.Func<") .Append(methodTypes.TypeArgsText(context)) .Append(">") .Return()); } }
/// <inheritdoc/> protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context) { var document = context.Document; var syntaxRoot = await document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var semanticModel = await document.GetSemanticModelAsync(context.CancellationToken) .ConfigureAwait(false); foreach (var diagnostic in context.Diagnostics) { if (syntaxRoot.TryFindNodeOrAncestor(diagnostic, out MethodDeclarationSyntax methodDeclaration) && semanticModel.TryGetSymbol(methodDeclaration, context.CancellationToken, out var method)) { if (ClrMethod.IsAttachedGet(method, semanticModel, context.CancellationToken, out var fieldOrProperty) && DependencyProperty.TryGetRegisteredName(fieldOrProperty, semanticModel, context.CancellationToken, out var registeredName)) { var parameter = method.Parameters[0]; var text = StringBuilderPool.Borrow() .AppendLine($"/// <summary>Helper for getting <see cref=\"{fieldOrProperty.Name}\"/> from <paramref name=\"{parameter.Name}\"/>.</summary>") .AppendLine($"/// <param name=\"{parameter.Name}\"><see cref=\"{parameter.Type.ToMinimalDisplayString(semanticModel, methodDeclaration.SpanStart, SymbolDisplayFormat.MinimallyQualifiedFormat)}\"/> to read <see cref=\"{fieldOrProperty.Name}\"/> from.</param>") .AppendLine($"/// <returns>{registeredName} property value.</returns>") .Return(); context.RegisterCodeFix( "Add standard documentation.", (editor, _) => editor.ReplaceNode(methodDeclaration, x => x.WithDocumentationText(text)), this.GetType(), diagnostic); } else if (ClrMethod.IsAttachedSet(method, semanticModel, context.CancellationToken, out fieldOrProperty) && DependencyProperty.TryGetRegisteredName(fieldOrProperty, semanticModel, context.CancellationToken, out registeredName)) { var parameter = method.Parameters[0]; var text = StringBuilderPool.Borrow() .AppendLine($"/// <summary>Helper for setting <see cref=\"{fieldOrProperty.Name}\"/> on <paramref name=\"{parameter.Name}\"/>.</summary>") .AppendLine($"/// <param name=\"{parameter.Name}\"><see cref=\"{parameter.Type.ToMinimalDisplayString(semanticModel, methodDeclaration.SpanStart, SymbolDisplayFormat.MinimallyQualifiedFormat)}\"/> to set <see cref=\"{fieldOrProperty.Name}\"/> on.</param>") .AppendLine($"/// <param name=\"{method.Parameters[1].Name}\">{registeredName} property value.</param>") .Return(); context.RegisterCodeFix( "Add standard documentation.", (editor, _) => editor.ReplaceNode(methodDeclaration, x => x.WithDocumentationText(text)), this.GetType(), diagnostic); } } } }
private static CodeAction FindAction(IReadOnlyList <CodeAction> actions, string fixTitle) { if (fixTitle == null) { if (actions.TrySingle(out var action)) { return(action); } if (actions.Count == 0) { throw new AssertException("Expected one code fix, was 0."); } throw new AssertException($"Expected only one code fix, found {actions.Count}:\r\n" + $"{string.Join("\r\n", actions.Select(x => x.Title))}\r\n" + "Use the overload that specifies title."); } else { if (actions.TrySingle(x => x.Title == fixTitle, out var action)) { return(action); } if (actions.All(x => x.Title != fixTitle)) { var errorBuilder = StringBuilderPool.Borrow(); errorBuilder.AppendLine($"Did not find a code fix with title {fixTitle}.").AppendLine("Found:"); foreach (var codeAction in actions) { errorBuilder.AppendLine(codeAction.Title); } throw new AssertException(StringBuilderPool.Return(errorBuilder)); } if (actions.Count(x => x.Title == fixTitle) == 0) { throw new AssertException("Expected one code fix, was 0."); } throw new AssertException($"Expected only one code fix, found {actions.Count}:\r\n" + $"{string.Join("\r\n", actions.Select(x => x.Title))}\r\n" + "Use the overload that specifies title."); } }
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()); } }
public void UseTwice() { Clear(); // testrunner may run in parallel creating more than one builder. StringBuilder inner1; using (var builder = StringBuilderPool.Borrow()) { builder.Append("a"); inner1 = GetInner(builder); Assert.AreEqual("a", builder.ToString()); } using (var builder = StringBuilderPool.Borrow()) { builder.Append("bc"); var inner2 = GetInner(builder); Assert.AreSame(inner1, inner2); Assert.AreEqual("bc", builder.ToString()); } }
/// <summary> /// Verifies that <paramref name="code"/> produces no diagnostics when analyzed with <paramref name="analyzer"/>. /// </summary> /// <param name="analyzer">The analyzer.</param> /// <param name="code">The code to analyze.</param> /// <param name="compilationOptions">The <see cref="CSharpCompilationOptions"/> to use.</param> /// <param name="metadataReferences">The metadata references to use when compiling.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public static async Task ValidAsync(DiagnosticAnalyzer analyzer, IReadOnlyList <string> code, CSharpCompilationOptions compilationOptions, IReadOnlyList <MetadataReference> metadataReferences) { var diagnostics = await Analyze.GetDiagnosticsAsync( analyzer, code, compilationOptions, metadataReferences) .ConfigureAwait(false); if (diagnostics.SelectMany(x => x).Any()) { var builder = StringBuilderPool.Borrow().AppendLine("Expected no diagnostics, found:"); foreach (var diagnostic in diagnostics.SelectMany(x => x)) { builder.AppendLine(diagnostic.ToString(code)); } throw AssertException.Create(builder.Return()); } }
private static string Format(IReadOnlyList <IdentifierNameSyntax> flags) { var builder = StringBuilderPool.Borrow(); for (var i = 0; i < flags.Count; i++) { if (i > 0) { _ = builder.Append(" | "); } ExpressionSyntax flag = flags[i]; while (flag.Parent is MemberAccessExpressionSyntax memberAccess) { flag = memberAccess; } _ = builder.Append(flag); } return(builder.Return()); }
internal static bool TryGetKey(string text, [NotNullWhen(true)] out string?key) { if (string.IsNullOrEmpty(text)) { key = null; return(false); } var builder = StringBuilderPool.Borrow() .Append(Regex.Replace(text, "{(?<n>\\d+)}", x => $"__{x.Groups["n"].Value}__")) .Replace(" ", "_") .Replace("\r\n", "_n_") .Replace("\n", "_n_") .Replace(".", "_") .Replace(",", "_") .Replace(":", "_"); if (char.IsDigit(builder[0])) { _ = builder.Insert(0, '_'); } const int MaxLength = 100; if (builder.Length > MaxLength) { _ = builder.Remove(MaxLength, builder.Length - MaxLength); } key = builder.Return(); if (key == "Resources") { key += "_"; } return(SyntaxFacts.IsValidIdentifier(key)); }
private static void VerifyNoFix(Solution sln, IReadOnlyList <ImmutableArray <Diagnostic> > diagnostics, CodeFixProvider fix) { var fixableDiagnostics = diagnostics.SelectMany(x => x) .Where(x => fix.FixableDiagnosticIds.Contains(x.Id)) .ToArray(); foreach (var fixableDiagnostic in fixableDiagnostics) { var actions = Fix.GetActions(sln, fix, fixableDiagnostic); if (actions.Any()) { var builder = StringBuilderPool.Borrow() .AppendLine("Expected code to have no fixable diagnostics.") .AppendLine("The following actions were registered:"); foreach (var action in actions) { builder.AppendLine(action.Title); } throw new AssertException(builder.Return()); } } }
private static string CreateStub(DescriptorInfo descriptorInfo) { var descriptor = descriptorInfo.Descriptor; var stub = Properties.Resources.DiagnosticDocTemplate .AssertReplace("{ID}", descriptor.Id) .AssertReplace("## ADD TITLE HERE", $"## {descriptor.Title.ToString(CultureInfo.InvariantCulture)}") .AssertReplace("{SEVERITY}", descriptor.DefaultSeverity.ToString()) .AssertReplace("{ENABLED}", descriptor.IsEnabledByDefault ? "true" : "false") .AssertReplace("{CATEGORY}", descriptor.Category) .AssertReplace("ADD DESCRIPTION HERE", descriptor.Description.ToString(CultureInfo.InvariantCulture)) .AssertReplace("{TITLE}", descriptor.Title.ToString(CultureInfo.InvariantCulture)); if (Analyzers.Count(x => x.SupportedDiagnostics.Any(d => d.Id == descriptor.Id)) == 1) { return(stub.AssertReplace("{TYPENAME}", descriptorInfo.Analyzer.GetType().Name) .AssertReplace("{URL}", descriptorInfo.CodeFileUri)); } var builder = StringBuilderPool.Borrow(); var first = true; foreach (var analyzer in Analyzers.Where(x => x.SupportedDiagnostics.Any(d => d.Id == descriptor.Id))) { _ = builder.AppendLine(" <tr>") .AppendLine($" <td>{(first ? "Code" : string.Empty)}</td>") .AppendLine($" <td><a href=\"{DescriptorInfo.GetCodeFileUri(analyzer)}\">{analyzer.GetType().Name}</a></td>") .AppendLine(" </tr>"); first = false; } var text = builder.Return(); return(stub.Replace(" <tr>\r\n <td>Code</td>\r\n <td><a href=\"{URL}\">{TYPENAME}</a></td>\r\n </tr>\r\n", text) .Replace(" <tr>\n <td>Code</td>\n <td><a href=\"{URL}\">{TYPENAME}</a></td>\n </tr>\n", text)); }
static string GetEnd(string text) { bool lastLine = false; var builder = StringBuilderPool.Borrow(); for (var i = text.Length - 1; i >= 0; i--) { switch (text[i]) { case '\r': if (lastLine) { return(builder.Return()); } builder.Insert(0, "\\r"); break; case '\n': if (lastLine) { return(builder.Return()); } builder.Insert(0, "\\n"); break; default: lastLine = true; builder.Insert(0, text[i]); break; } } return(builder.Return()); }
private static CodeAction FindAction(IReadOnlyList <CodeAction> actions, string?fixTitle) { if (fixTitle is null) { if (actions.TrySingle(out var action)) { return(action); } if (actions.Count == 0) { throw new AssertException("Expected one code fix, was 0."); } throw new AssertException(FoundManyMessage()); } else { if (actions.TrySingle(x => x.Title == fixTitle, out var action)) { return(action); } if (actions.All(x => x.Title != fixTitle)) { var errorBuilder = StringBuilderPool.Borrow() .AppendLine($"Did not find a code fix with title {fixTitle}.").AppendLine("Found:"); foreach (var codeAction in actions) { errorBuilder.AppendLine(codeAction.Title); } throw new AssertException(StringBuilderPool.Return(errorBuilder)); } if (actions.Any(x => x.Title == fixTitle)) { throw new AssertException(FoundManyMessage()); } throw new AssertException("Expected one code fix, was 0."); } string FoundManyMessage() { if (actions.Select(x => x.Title).Distinct().Count() > 1) { var errorBuilder = StringBuilderPool .Borrow() .AppendLine($"Expected only one code fix, found {actions.Count}:"); foreach (var a in actions.OrderBy(x => x.Title)) { errorBuilder.AppendLine(" " + a.Title); } return(errorBuilder.AppendLine("Use the overload that specifies title.") .AppendLine("Or maybe you meant to call RoslynAssert.FixAll?") .Return()); } else { var errorBuilder = StringBuilderPool .Borrow() .AppendLine($"Expected only one code fix, found {actions.Count}:"); foreach (var a in actions.OrderBy(x => x.Title)) { errorBuilder.AppendLine(" " + a.Title); } return(errorBuilder.AppendLine("Or maybe you meant to call RoslynAssert.FixAll?") .Return()); } } }
/// <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) { if (IsAt(expected, expectedPos, "\\r") || IsAt(actual, actualPos, "\\r")) { if (IsAt(expected, expectedPos, "\\r")) { expectedPos += 2; } if (IsAt(actual, actualPos, "\\r")) { actualPos += 2; } continue; } var errorBuilder = StringBuilderPool.Borrow(); if (messageHeader != null) { errorBuilder.AppendLine(messageHeader); } if (!IsSingleLine(expected) || !IsSingleLine(actual)) { errorBuilder.AppendLine( CodeReader.TryGetFileName(expected, out var fileName) ? $"Mismatch on line {line} of file {fileName}." : $"Mismatch on line {line}."); } var expectedLine = expected.Split('\n')[line - 1].Trim('\r'); var actualLine = actual.Split('\n')[line - 1].Trim('\r'); var diffPos = DiffPos(expectedLine, actualLine); errorBuilder.AppendLine($"Expected: {expectedLine}") .AppendLine($"Actual: {actualLine}") .AppendLine($" {new string(' ', diffPos)}^"); if (!IsSingleLine(expected) || !IsSingleLine(actual)) { errorBuilder.AppendLine("Expected:") .Append(expected) .AppendLine() .AppendLine("Actual:") .Append(actual) .AppendLine(); } throw new AssertException(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; } var messageBuilder = StringBuilderPool.Borrow(); if (messageHeader != null) { messageBuilder.AppendLine(messageHeader); } messageBuilder.AppendLine(CodeReader.TryGetFileName(expected, out var name) ? $"Mismatch at end of file {name}." : "Mismatch at end.") .Append("Expected: ").AppendLine(GetEnd(expected)) .Append("Actual: ").AppendLine(GetEnd(actual)) .AppendLine($" {new string(' ', DiffPos(GetEnd(expected), GetEnd(actual)))}^"); if (!IsSingleLine(expected) || !IsSingleLine(actual)) { messageBuilder.AppendLine("Expected:") .Append(expected) .AppendLine() .AppendLine("Actual:") .Append(actual) .AppendLine(); } throw new AssertException(messageBuilder.Return()); bool IsSingleLine(string text) { bool foundNewLine = false; foreach (var c in text) { switch (c) { case '\r': case '\n': foundNewLine = true; break; default: if (foundNewLine) { return(false); } break; } } return(true); } int DiffPos(string expectedLine, string actualLine) { 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; } } return(diffPos); } string GetEnd(string text) { bool lastLine = false; var builder = StringBuilderPool.Borrow(); for (var i = text.Length - 1; i >= 0; i--) { switch (text[i]) { case '\r': if (lastLine) { return(builder.Return()); } builder.Insert(0, "\\r"); break; case '\n': if (lastLine) { return(builder.Return()); } builder.Insert(0, "\\n"); break; default: lastLine = true; builder.Insert(0, text[i]); break; } } return(builder.Return()); } }
/// <summary>Adjust each row in <paramref name="text"/> to start with <paramref name="leadingWhitespace"/>.</summary> /// <param name="text">The text.</param> /// <param name="leadingWhitespace">The whitespace to adjust each row to have.</param> /// <returns><paramref name="text"/> with each line adjusted to start with <paramref name="leadingWhitespace"/>.</returns> public static string WithLeadingWhiteSpace(this string text, string?leadingWhitespace) { if (text is null) { throw new ArgumentNullException(nameof(text)); } if (leadingWhitespace is null) { return(text); } var eol = text.IndexOf('\n'); if (eol < 0 || eol == text.Length - 1) { var substring = FindSubstring(0, text.Length); return(leadingWhitespace + text.Substring(substring.Start, substring.Length)); } var builder = StringBuilderPool.Borrow(); var pos = 0; do { var substring = FindSubstring(pos, eol + 1); _ = builder.Append(leadingWhitespace) .Append(text, substring.Start, substring.Length); pos = eol + 1; }while ((eol = text.IndexOf('\n', pos)) > 0); if (pos < text.Length - 1) { var substring = FindSubstring(pos, text.Length); _ = builder.Append(leadingWhitespace) .Append(text, substring.Start, substring.Length); } return(builder.Return()); Substring FindSubstring(int start, int end) { if (text[start] != ' ') { return(new Substring(start, end)); } if (string.IsNullOrEmpty(leadingWhitespace)) { return(new Substring(text.CountWhile(x => x == ' ', start, end), end)); } var indexOf = text.IndexOf(leadingWhitespace, StringComparison.Ordinal); if (indexOf == 0) { var offset = start; while (text.StartsWith(offset + 1, leadingWhitespace !)) { offset++; } return(new Substring(offset + leadingWhitespace !.Length, end)); } if (indexOf > 0) { return(IsWhitespaceTo(text, start, indexOf) ? new Substring(indexOf, end) : new Substring(start, end)); } return(new Substring(text.CountWhile(x => x == ' ', start, end), end)); } }
private static string CreateStub(DescriptorInfo descriptorInfo) { var descriptor = descriptorInfo.Descriptor; var stub = $@"# {descriptor.Id} ## {descriptor.Title.ToString(CultureInfo.InvariantCulture)} <!-- start generated table --> <table> <tr> <td>CheckId</td> <td>{descriptor.Id}</td> </tr> <tr> <td>Severity</td> <td>{descriptor.DefaultSeverity.ToString()}</td> </tr> <tr> <td>Enabled</td> <td>{(descriptor.IsEnabledByDefault ? "True" : "False")}</td> </tr> <tr> <td>Category</td> <td>{descriptor.Category}</td> </tr> <tr> <td>Code</td> <td><a href=""<URL>""><TYPENAME></a></td> </tr> </table> <!-- end generated table --> ## Description {descriptor.Description.ToString(CultureInfo.InvariantCulture)} ## Motivation ADD MOTIVATION HERE ## How to fix violations ADD HOW TO FIX VIOLATIONS HERE <!-- start generated config severity --> ## Configure severity ### Via ruleset file. Configure the severity per project, for more info see [MSDN](https://msdn.microsoft.com/en-us/library/dd264949.aspx). ### Via #pragma directive. ```C# #pragma warning disable {descriptor.Id} // {descriptor.Title.ToString(CultureInfo.InvariantCulture)} Code violating the rule here #pragma warning restore {descriptor.Id} // {descriptor.Title.ToString(CultureInfo.InvariantCulture)} ``` Or put this at the top of the file to disable all instances. ```C# #pragma warning disable {descriptor.Id} // {descriptor.Title.ToString(CultureInfo.InvariantCulture)} ``` ### Via attribute `[SuppressMessage]`. ```C# [System.Diagnostics.CodeAnalysis.SuppressMessage(""{descriptor.Category}"", ""{descriptor.Id}:{descriptor.Title.ToString(CultureInfo.InvariantCulture)}"", Justification = ""Reason..."")] ``` <!-- end generated config severity -->"; if (Analyzers.Count(x => x.SupportedDiagnostics.Any(d => d.Id == descriptor.Id)) == 1) { return(stub.AssertReplace("<TYPENAME>", descriptorInfo.Analyzer.GetType().Name) .AssertReplace("<URL>", descriptorInfo.CodeFileUri)); } var builder = StringBuilderPool.Borrow(); var first = true; foreach (var analyzer in Analyzers.Where(x => x.SupportedDiagnostics.Any(d => d.Id == descriptor.Id))) { _ = builder.AppendLine(" <tr>") .AppendLine($" <td>{(first ? "Code" : string.Empty)}</td>") .AppendLine($" <td><a href=\"{DescriptorInfo.GetCodeFileUri(analyzer)}\">{analyzer.GetType().Name}</a></td>") .AppendLine(" </tr>"); first = false; } var text = builder.Return(); return(stub.Replace(" <tr>\r\n <td>Code</td>\r\n <td><a href=\"{URL}\">{TYPENAME}</a></td>\r\n </tr>\r\n", text, StringComparison.Ordinal) .Replace(" <tr>\n <td>Code</td>\n <td><a href=\"{URL}\">{TYPENAME}</a></td>\n </tr>\n", text, StringComparison.Ordinal)); }