Skip to content

Commit 6eed0ea

Browse files
randleeclaude
andcommitted
feat(cli): Add impact filtering CLI options (Phase 4)
Add CLI integration for non-impactful change detection: - --include-non-impactful flag to include non-impactful changes in JSON - --include-formatting flag to include formatting-only changes - --impact-level option (breaking-public|breaking-internal|non-breaking|all) Default behavior: JSON excludes non-impactful, HTML shows all. Includes 25 new E2E tests and comprehensive test fixtures covering all impact levels (BreakingPublicApi, BreakingInternalApi, NonBreaking, FormattingOnly). Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent f6bf1b5 commit 6eed0ea

File tree

13 files changed

+1068
-2
lines changed

13 files changed

+1068
-2
lines changed

src/RoslynDiff.Cli/Commands/DiffCommand.cs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,40 @@ public sealed class Settings : CommandSettings
160160
[DefaultValue(false)]
161161
public bool NoColor { get; init; }
162162

163+
/// <summary>
164+
/// Gets or sets a value indicating whether to include non-impactful changes in output.
165+
/// </summary>
166+
/// <remarks>
167+
/// Non-impactful changes include formatting-only and non-breaking changes.
168+
/// By default, JSON output excludes these for cleaner API consumption.
169+
/// </remarks>
170+
[CommandOption("--include-non-impactful")]
171+
[Description("Include non-impactful changes in JSON output")]
172+
[DefaultValue(false)]
173+
public bool IncludeNonImpactful { get; init; }
174+
175+
/// <summary>
176+
/// Gets or sets a value indicating whether to include formatting-only changes.
177+
/// </summary>
178+
/// <remarks>
179+
/// Formatting-only changes are whitespace and comment changes that don't affect code behavior.
180+
/// </remarks>
181+
[CommandOption("--include-formatting")]
182+
[Description("Include formatting-only changes (whitespace, comments)")]
183+
[DefaultValue(false)]
184+
public bool IncludeFormatting { get; init; }
185+
186+
/// <summary>
187+
/// Gets or sets the minimum impact level to include in output.
188+
/// </summary>
189+
/// <remarks>
190+
/// Valid values: breaking-public, breaking-internal, non-breaking, all.
191+
/// Default is 'all' for HTML output and 'breaking-internal' for JSON output.
192+
/// </remarks>
193+
[CommandOption("--impact-level <level>")]
194+
[Description("Minimum impact level: breaking-public, breaking-internal, non-breaking, all [[default: all for HTML, breaking-internal for JSON]]")]
195+
public string? ImpactLevel { get; init; }
196+
163197
/// <inheritdoc/>
164198
public override ValidationResult Validate()
165199
{
@@ -210,6 +244,30 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
210244
return OutputOrchestrator.ExitCodeError;
211245
}
212246

247+
// Parse and validate impact level
248+
var (impactLevel, impactError) = ParseImpactLevel(settings.ImpactLevel);
249+
if (impactError is not null)
250+
{
251+
AnsiConsole.MarkupLine($"[red]Error: {impactError}[/]");
252+
return OutputOrchestrator.ExitCodeError;
253+
}
254+
255+
// Determine effective minimum impact level based on output type
256+
// Default: 'all' (FormattingOnly) for HTML, 'breaking-internal' for JSON
257+
var effectiveImpactLevel = impactLevel ?? (settings.JsonOutput?.IsSet == true && settings.HtmlOutput is null
258+
? ChangeImpact.BreakingInternalApi
259+
: ChangeImpact.FormattingOnly);
260+
261+
// Adjust for include flags which can override to include more
262+
if (settings.IncludeNonImpactful)
263+
{
264+
effectiveImpactLevel = ChangeImpact.NonBreaking;
265+
}
266+
if (settings.IncludeFormatting)
267+
{
268+
effectiveImpactLevel = ChangeImpact.FormattingOnly;
269+
}
270+
213271
try
214272
{
215273
// Read file contents
@@ -225,7 +283,9 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
225283
IgnoreWhitespace = settings.IgnoreWhitespace,
226284
ContextLines = settings.ContextLines,
227285
OldPath = Path.GetFullPath(settings.OldPath),
228-
NewPath = Path.GetFullPath(settings.NewPath)
286+
NewPath = Path.GetFullPath(settings.NewPath),
287+
IncludeNonImpactful = settings.IncludeNonImpactful || settings.IncludeFormatting,
288+
MinimumImpactLevel = effectiveImpactLevel
229289
};
230290

231291
// Get the appropriate differ
@@ -243,7 +303,10 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
243303
GitOutput = settings.GitOutput?.IsSet == true ? (settings.GitOutput.Value ?? "") : null,
244304
OpenInBrowser = settings.OpenInBrowser,
245305
Quiet = settings.Quiet,
246-
NoColor = settings.NoColor
306+
NoColor = settings.NoColor,
307+
IncludeNonImpactful = settings.IncludeNonImpactful || settings.IncludeFormatting,
308+
IncludeFormatting = settings.IncludeFormatting,
309+
MinimumImpactLevel = effectiveImpactLevel
247310
};
248311

249312
// Use OutputOrchestrator to handle all output logic
@@ -255,4 +318,26 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
255318
return OutputOrchestrator.ExitCodeError;
256319
}
257320
}
321+
322+
/// <summary>
323+
/// Parses the impact level string to a ChangeImpact enum value.
324+
/// </summary>
325+
/// <param name="impactLevel">The impact level string from CLI.</param>
326+
/// <returns>A tuple containing the parsed impact level (or null for default) and any error message.</returns>
327+
private static (ChangeImpact? Level, string? Error) ParseImpactLevel(string? impactLevel)
328+
{
329+
if (string.IsNullOrEmpty(impactLevel))
330+
{
331+
return (null, null); // Use default behavior
332+
}
333+
334+
return impactLevel.ToLowerInvariant() switch
335+
{
336+
"breaking-public" => (ChangeImpact.BreakingPublicApi, null),
337+
"breaking-internal" => (ChangeImpact.BreakingInternalApi, null),
338+
"non-breaking" => (ChangeImpact.NonBreaking, null),
339+
"all" => (ChangeImpact.FormattingOnly, null),
340+
_ => (null, $"Invalid impact level: '{impactLevel}'. Valid values: breaking-public, breaking-internal, non-breaking, all")
341+
};
342+
}
258343
}

src/RoslynDiff.Cli/OutputOrchestrator.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,25 @@ public class OutputSettings
390390
/// When true, plain text output is used even in interactive terminals.
391391
/// </summary>
392392
public bool NoColor { get; init; }
393+
394+
/// <summary>
395+
/// Gets or sets a value indicating whether to include non-impactful changes in output.
396+
/// </summary>
397+
/// <remarks>
398+
/// Non-impactful changes include formatting-only and non-breaking changes.
399+
/// </remarks>
400+
public bool IncludeNonImpactful { get; init; }
401+
402+
/// <summary>
403+
/// Gets or sets a value indicating whether to include formatting-only changes.
404+
/// </summary>
405+
public bool IncludeFormatting { get; init; }
406+
407+
/// <summary>
408+
/// Gets or sets the minimum impact level to include in output.
409+
/// </summary>
410+
/// <remarks>
411+
/// Only changes at or above this impact level are included.
412+
/// </remarks>
413+
public ChangeImpact MinimumImpactLevel { get; init; } = ChangeImpact.FormattingOnly;
393414
}

0 commit comments

Comments
 (0)