RefactoringGenerator emits the new method (promoted signature, parameter
names = source symbol names) plus the rewritten call site for every
em 02/03 bucket shape (return slot declared inside/outside, tuple
deconstruction, ref write-back, return-call when the selection ends a
non-void method) and refuses loudly where v1 cannot be sound (composite
trailing return, return not ending the method).
The transformed tree must compile with zero diagnostics (the yak's
round-trip check, enforced in both the CLI and the tests).
Findings that shaped the implementation, all enforced by tests:
- NormalizeWhitespace on the WHOLE root corrupts doc-comment trivia so
Roslyn's XML-doc writer fails with CS1569 ('count (-3) must be
non-negative') — and it would reformat the entire file, killing the
diff. Only generated nodes are normalized; original trivia is kept and
indentation/end-of-line is grafted onto the inserted statements/method.
- ParseTypeName("void") yields a node rejected as a method return type
(CS1547) — a void signature must be PredefinedType(Token(VoidKeyword)).
- NormalizeWhitespace drops the space after the contextual keyword 'var'
before '(' — the parsed 'var (x, y) = ...' deconstruction skeleton is
used verbatim; builder-made statements are normalized.
- The new method is normalized inside a throwaway class wrapper: the
normalizer computes indentation from nesting depth, and a bare method
has none.
v1 decision: print a unified-diff PREVIEW to stdout (hand-rolled LCS
unified diff), never an in-place rewrite. Also fixes the hunk header
off-by-one from the phantom trailing empty line of Split('\n').
- 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.