/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs

https://github.com/dotnet/roslyn · C# · 172 lines · 145 code · 22 blank · 5 comment · 31 complexity · 93a8a150918da706585f60ca68976b4c MD5 · raw file

  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. #nullable enable
  5. using System.Collections.Generic;
  6. using System.Collections.Immutable;
  7. using System.Linq;
  8. using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
  9. using Microsoft.CodeAnalysis.LanguageServices;
  10. using Microsoft.CodeAnalysis.Operations;
  11. using Microsoft.CodeAnalysis.Shared.Extensions;
  12. using Microsoft.CodeAnalysis.Text;
  13. using Roslyn.Utilities;
  14. namespace Microsoft.CodeAnalysis.SimplifyInterpolation
  15. {
  16. internal static class Helpers
  17. {
  18. public static void UnwrapInterpolation<TInterpolationSyntax, TExpressionSyntax>(
  19. IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IInterpolationOperation interpolation,
  20. out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, out bool negate,
  21. out string? formatString, out ImmutableArray<Location> unnecessaryLocations)
  22. where TInterpolationSyntax : SyntaxNode
  23. where TExpressionSyntax : SyntaxNode
  24. {
  25. alignment = null;
  26. negate = false;
  27. formatString = null;
  28. var unnecessarySpans = new List<TextSpan>();
  29. var expression = Unwrap(interpolation.Expression);
  30. if (interpolation.Alignment == null)
  31. {
  32. UnwrapAlignmentPadding(expression, out expression, out alignment, out negate, unnecessarySpans);
  33. }
  34. if (interpolation.FormatString == null)
  35. {
  36. UnwrapFormatString(virtualCharService, syntaxFacts, expression, out expression, out formatString, unnecessarySpans);
  37. }
  38. unwrapped = expression.Syntax as TExpressionSyntax;
  39. unnecessaryLocations =
  40. unnecessarySpans.OrderBy(t => t.Start)
  41. .SelectAsArray(interpolation.Syntax.SyntaxTree.GetLocation);
  42. }
  43. private static IOperation Unwrap(IOperation expression)
  44. {
  45. while (true)
  46. {
  47. switch (expression)
  48. {
  49. case IParenthesizedOperation parenthesized:
  50. expression = parenthesized.Operand;
  51. continue;
  52. case IConversionOperation { IsImplicit: true } conversion:
  53. expression = conversion.Operand;
  54. continue;
  55. default:
  56. return expression;
  57. }
  58. }
  59. }
  60. private static void UnwrapFormatString(
  61. IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IOperation expression, out IOperation unwrapped,
  62. out string? formatString, List<TextSpan> unnecessarySpans)
  63. {
  64. if (expression is IInvocationOperation { TargetMethod: { Name: nameof(ToString) } } invocation &&
  65. HasNonImplicitInstance(invocation) &&
  66. !syntaxFacts.IsBaseExpression(invocation.Instance.Syntax) &&
  67. !invocation.Instance.Type.IsRefLikeType)
  68. {
  69. if (invocation.Arguments.Length == 1 &&
  70. invocation.Arguments[0].Value is ILiteralOperation { ConstantValue: { HasValue: true, Value: string value } } literal &&
  71. invocation.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.IFormattable).FullName!) is { } systemIFormattable &&
  72. invocation.Instance.Type.Implements(systemIFormattable))
  73. {
  74. unwrapped = invocation.Instance;
  75. formatString = value;
  76. unnecessarySpans.AddRange(invocation.Syntax.Span
  77. .Subtract(invocation.Instance.Syntax.FullSpan)
  78. .Subtract(GetSpanWithinLiteralQuotes(virtualCharService, literal.Syntax.GetFirstToken())));
  79. return;
  80. }
  81. var method = invocation.TargetMethod;
  82. while (method.OverriddenMethod != null)
  83. {
  84. method = method.OverriddenMethod;
  85. }
  86. if (method.ContainingType.SpecialType == SpecialType.System_Object &&
  87. method.Name == nameof(ToString))
  88. {
  89. // A call to `.ToString()` at the end of the interpolation. This is unnecessary.
  90. // Just remove entirely.
  91. unwrapped = invocation.Instance;
  92. formatString = "";
  93. unnecessarySpans.AddRange(invocation.Syntax.Span
  94. .Subtract(invocation.Instance.Syntax.FullSpan));
  95. return;
  96. }
  97. }
  98. unwrapped = expression;
  99. formatString = null;
  100. }
  101. private static TextSpan GetSpanWithinLiteralQuotes(IVirtualCharService virtualCharService, SyntaxToken formatToken)
  102. {
  103. var sequence = virtualCharService.TryConvertToVirtualChars(formatToken);
  104. return sequence.IsDefaultOrEmpty
  105. ? default
  106. : TextSpan.FromBounds(sequence.First().Span.Start, sequence.Last().Span.End);
  107. }
  108. private static void UnwrapAlignmentPadding<TExpressionSyntax>(
  109. IOperation expression, out IOperation unwrapped,
  110. out TExpressionSyntax? alignment, out bool negate, List<TextSpan> unnecessarySpans)
  111. where TExpressionSyntax : SyntaxNode
  112. {
  113. if (expression is IInvocationOperation invocation &&
  114. HasNonImplicitInstance(invocation))
  115. {
  116. var targetName = invocation.TargetMethod.Name;
  117. if (targetName == nameof(string.PadLeft) || targetName == nameof(string.PadRight))
  118. {
  119. var argCount = invocation.Arguments.Length;
  120. if (argCount == 1 || argCount == 2)
  121. {
  122. if (argCount == 1 ||
  123. IsSpaceChar(invocation.Arguments[1]))
  124. {
  125. var alignmentOp = invocation.Arguments[0].Value;
  126. if (alignmentOp != null && alignmentOp.ConstantValue.HasValue)
  127. {
  128. var alignmentSyntax = alignmentOp.Syntax;
  129. unwrapped = invocation.Instance;
  130. alignment = alignmentSyntax as TExpressionSyntax;
  131. negate = targetName == nameof(string.PadRight);
  132. unnecessarySpans.AddRange(invocation.Syntax.Span
  133. .Subtract(invocation.Instance.Syntax.FullSpan)
  134. .Subtract(alignmentSyntax.FullSpan));
  135. return;
  136. }
  137. }
  138. }
  139. }
  140. }
  141. unwrapped = expression;
  142. alignment = null;
  143. negate = false;
  144. }
  145. private static bool HasNonImplicitInstance(IInvocationOperation invocation)
  146. => invocation.Instance != null && !invocation.Instance.IsImplicit;
  147. private static bool IsSpaceChar(IArgumentOperation argument)
  148. => argument.Value.ConstantValue is { HasValue: true, Value: ' ' };
  149. }
  150. }