- 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.
54 lines
2.6 KiB
C#
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<Widget></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<Widget></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<Widget>.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) ?? "?";
|
|
}
|