using ExtractMethod.Tooling; using Microsoft.CodeAnalysis; // CLIs are boring on purpose: argument parsing and printing live here, all // Roslyn logic lives in Tooling/ so the tests can drive it directly. string usage = "usage: ExtractMethod (1-based, inclusive)"; if (args.Length != 3) { Console.Error.WriteLine(usage); return 2; } string file = Path.GetFullPath(args[0]); if (!File.Exists(file)) { Console.Error.WriteLine($"error: file not found: {file}"); Console.Error.WriteLine(usage); return 2; } if (!int.TryParse(args[1], out int startLine) || !int.TryParse(args[2], out int endLine)) { Console.Error.WriteLine($"error: line numbers must be integers"); Console.Error.WriteLine(usage); return 2; } // 1. parse the file var tree = CompilationLoader.ParseFile(file); // 2. build the scratch compilation (refs from TRUSTED_PLATFORM_ASSEMBLIES) var compilation = CompilationLoader.CreateCompilation(tree, Path.GetFileNameWithoutExtension(file)); if (compilation.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) { Console.Error.WriteLine("warning: the file does not compile cleanly under a plain Roslyn compilation; reporting syntax-level resolution only"); } // 3. snap the range to whole statements, report cleanly otherwise var resolved = SelectionResolver.Resolve(tree, startLine, endLine); if (!resolved.Succeeded) { Console.Error.WriteLine($"error: {resolved.Error}"); return SelectionResolver.ExitError; } // 4. compose the full suggestion (em 02 buckets + em 03 extract-first and // signature), generate the refactoring (em 04), verify it and print the // diff. Any failure is a clean error: message on stderr, same exit code // as the resolver, never a stack trace. var model = compilation.GetSemanticModel(tree); try { var extraction = ExtractionReporter.Compose(model, resolved); Console.Write(ReportFormatter.Format(extraction)); // 5. generate the refactoring from the suggestion (em 04 codegen): new // method + rewritten call site + transformed tree. var refactoring = RefactoringGenerator.Generate(model, resolved, extraction.Signature); // 6. round-trip check: the generated code must compile with zero // diagnostics (same scratch-compilation path as the input file). var transformedCompilation = CompilationLoader.CreateCompilation( refactoring.TransformedTree, Path.GetFileNameWithoutExtension(file)); var diagnostics = transformedCompilation.GetDiagnostics().ToList(); if (diagnostics.Count > 0) { Console.Error.WriteLine( $"error: round-trip check failed — the generated code has {diagnostics.Count} diagnostic(s):"); foreach (var diagnostic in diagnostics) { Console.Error.WriteLine($" {diagnostic}"); } Console.Error.WriteLine("---- generated code ----"); Console.Error.Write(refactoring.TransformedText); return SelectionResolver.ExitError; } // 7. v1 decision (em 04): print a unified diff to stdout — PREVIEW, never // an in-place file rewrite. Applying the diff (or a future --write // flag with backup semantics) is a deliberate next step, because // mutating the caller's file is a different safety policy than // generating code. Console.Write(UnifiedDiff.Diff( tree.ToString(), refactoring.TransformedText, Path.GetFileName(file), Path.GetFileName(file) + " (generated)")); } catch (Exception e) when (e is InvalidOperationException or ArgumentException) { Console.Error.WriteLine($"error: {e.Message}"); return SelectionResolver.ExitError; } return 0;