Files
mostalive 49a5633a7d em 03: extract-first scan + full suggestion report
- ExtractFirstScanner: reads-only scan of non-local/param read expressions
  (member/field/property access, element access, invocation, cast), deduped
  by symbol+text with occurrence counts; invocations flagged (hoisting
  changes eval count); own-class field/property reads marked optional.
  Assignment LHS skipped whole (decision 5) — surfaced as a report note.
- SignatureBuilder: promotes bucket overlaps into one coherent signature
  (declared-inside vars drop from params; declared-inside return candidates
  become the return; outside candidates already covered by their ref
  write-back stay ref params). Trailing composite returns stay void with a
  note suggesting an extract-variable first.
- ExtractionReporter + ReportFormatter: single composition shared by CLI and
  tests; report = header, params (ref), returns, locals, extract-first
  (count + flags), notes, suggested-signature line last.
- DataFlowClassifier: Returns now carry types (ReturnSuggestion) — the
  signature line needs them.
- Displays: compact symbol/type formatting for the report.
- Fixture: RepeatReads (dedupe ×2 + skipped field write), Casts (cast
  candidate) appended at the end so all pinned line numbers stay put.
2026-09-12 18:26:20 +01:00

54 lines
2.6 KiB
C#

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
namespace ExtractMethod.Tooling;
/// <summary>
/// Compact symbol display for the suggestion report. The defaults are wrong
/// for a report a human reads next to their own source:
/// - <c>ToDisplayString()</c> fully qualifies every namespace
/// (<c>System.Collections.Generic.List&lt;Widget&gt;</c>),
/// - <c>ToMinimalDisplayString()</c> keeps containing types AND prefixes
/// members with their own type (<c>int Demo._seed</c>).
/// The report wants the shortest form that stays readable in context:
/// types as bare names (<c>Widget</c>, <c>List&lt;Widget&gt;</c>), members as
/// <c>ContainingType.Member</c> with parameter types.
/// </summary>
internal static class Displays
{
/// <summary>
/// Type display for suggestion lines: shortest unambiguous-enough form
/// (no namespaces, no containing types, keyword spellings for special
/// types, nullable annotations kept). Built explicitly because
/// MinimallyQualifiedFormat keeps containing types for nested types
/// (<c>Demo.Widget</c>) and has no With-style override here.
/// </summary>
internal static readonly SymbolDisplayFormat TypeFormat = new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters
| SymbolDisplayGenericsOptions.IncludeVariance,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes
| SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
/// <summary>Compact member display for extract-first entries:
/// <c>Demo._seed</c>, <c>Demo.Scale</c>, <c>Widget.Bigger(Widget)</c>,
/// <c>List&lt;Widget&gt;.this[int]</c>.</summary>
internal static string Member(ISymbol symbol) => symbol switch
{
IMethodSymbol method =>
$"{Type(method.ContainingType)}.{method.Name}({Parameters(method.Parameters)})",
IPropertySymbol { IsIndexer: true } indexer =>
$"{Type(indexer.ContainingType)}.this[{Parameters(indexer.Parameters)}]",
IFieldSymbol or IPropertySymbol or IEventSymbol =>
$"{Type(symbol.ContainingType)}.{symbol.Name}",
_ => symbol.ToDisplayString(TypeFormat),
};
private static string Parameters(ImmutableArray<IParameterSymbol> parameters)
=> string.Join(", ", parameters.Select(p => Type(p.Type)));
private static string Type(ITypeSymbol? type) => type?.ToDisplayString(TypeFormat) ?? "?";
}