diff --git a/.vscode/launch.json b/.vscode/launch.json index a6f8c927bd..3f190d2e4b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,23 @@ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "version": "0.2.0", "configurations": [ + { + "name": "format @validate.rsp", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/artifacts/bin/dotnet-format/Debug/netcoreapp2.1/dotnet-format.dll", + "args": [ + "@validate.rsp", + "--check" + ], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false, + "justMyCode": false + }, { "name": "format -f --check", "type": "coreclr", diff --git a/Directory.Packages.props b/Directory.Packages.props index 44934e3d62..16d948f681 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,8 +35,8 @@ - - + + \ No newline at end of file diff --git a/src/FormatCommand.cs b/src/FormatCommand.cs index 8fe40ab00e..cf01eab100 100644 --- a/src/FormatCommand.cs +++ b/src/FormatCommand.cs @@ -3,6 +3,7 @@ using System; using System.CommandLine; using System.CommandLine.Parsing; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -17,8 +18,8 @@ internal static class FormatCommand bool noRestore, bool folder, bool fixWhitespace, - string? fixStyle, - string? fixAnalyzers, + string fixStyle, + string fixAnalyzers, string[] diagnostics, string? verbosity, bool check, @@ -37,51 +38,35 @@ internal static RootCommand CreateCommandLineOptions() // Sync changes to option and argument names with the FormatCommant.Handler above. var rootCommand = new RootCommand { - new Argument("workspace") + new Argument("workspace", () => null, Resources.A_path_to_a_solution_file_a_project_file_or_a_folder_containing_a_solution_or_project_file_If_a_path_is_not_specified_then_the_current_directory_is_used).LegalFilePathsOnly(), + new Option(new[] { "--no-restore" }, Resources.Doesnt_execute_an_implicit_restore_before_formatting), + new Option(new[] { "--folder", "-f" }, Resources.Whether_to_treat_the_workspace_argument_as_a_simple_folder_of_files), + new Option(new[] { "--fix-whitespace", "-w" }, Resources.Run_whitespace_formatting_Run_by_default_when_not_applying_fixes), + new Option(new[] { "--fix-style", "-s" }, Resources.Run_code_style_analyzers_and_apply_fixes, argumentType: typeof(string), arity: ArgumentArity.ZeroOrOne) { - Arity = ArgumentArity.ZeroOrOne, - Description = Resources.A_path_to_a_solution_file_a_project_file_or_a_folder_containing_a_solution_or_project_file_If_a_path_is_not_specified_then_the_current_directory_is_used - }.LegalFilePathsOnly(), - new Option(new[] { "--no-restore" }, Resources.Doesnt_execute_an_implicit_restore_before_formatting), - new Option(new[] { "--folder", "-f" }, Resources.Whether_to_treat_the_workspace_argument_as_a_simple_folder_of_files), - new Option(new[] { "--fix-whitespace", "-w" }, Resources.Run_whitespace_formatting_Run_by_default_when_not_applying_fixes), - new Option(new[] { "--fix-style", "-s" }, Resources.Run_code_style_analyzers_and_apply_fixes) - { - Argument = new Argument("severity") { Arity = ArgumentArity.ZeroOrOne }.FromAmong(SeverityLevels) - }, - new Option(new[] { "--fix-analyzers", "-a" }, Resources.Run_3rd_party_analyzers_and_apply_fixes) + ArgumentHelpName = "severity" + }.FromAmong(SeverityLevels), + new Option(new[] { "--fix-analyzers", "-a" }, Resources.Run_3rd_party_analyzers_and_apply_fixes, argumentType: typeof(string), arity: ArgumentArity.ZeroOrOne) { - Argument = new Argument("severity") { Arity = ArgumentArity.ZeroOrOne }.FromAmong(SeverityLevels) - }, - new Option(new[] { "--diagnostics" }, Resources.A_space_separated_list_of_diagnostic_ids_to_use_as_a_filter_when_fixing_code_style_or_3rd_party_issues) + ArgumentHelpName = "severity" + }.FromAmong(SeverityLevels), + new Option(new[] { "--diagnostics" }, () => Array.Empty(), Resources.A_space_separated_list_of_diagnostic_ids_to_use_as_a_filter_when_fixing_code_style_or_3rd_party_issues), + new Option(new[] { "--include" }, () => Array.Empty(), Resources.A_list_of_relative_file_or_folder_paths_to_include_in_formatting_All_files_are_formatted_if_empty), + new Option(new[] { "--exclude" }, () => Array.Empty(), Resources.A_list_of_relative_file_or_folder_paths_to_exclude_from_formatting), + new Option(new[] { "--check" }, Resources.Formats_files_without_saving_changes_to_disk_Terminates_with_a_non_zero_exit_code_if_any_files_were_formatted), + new Option(new[] { "--report" }, Resources.Accepts_a_file_path_which_if_provided_will_produce_a_format_report_json_file_in_the_given_directory, argumentType: typeof(string), arity: ArgumentArity.ZeroOrOne) { - Argument = new Argument(() => Array.Empty()) - }, - new Option(new[] { "--include" }, Resources.A_list_of_relative_file_or_folder_paths_to_include_in_formatting_All_files_are_formatted_if_empty) - { - Argument = new Argument(() => Array.Empty()) - }, - new Option(new[] { "--exclude" }, Resources.A_list_of_relative_file_or_folder_paths_to_exclude_from_formatting) - { - Argument = new Argument(() => Array.Empty()) - }, - new Option(new[] { "--check" }, Resources.Formats_files_without_saving_changes_to_disk_Terminates_with_a_non_zero_exit_code_if_any_files_were_formatted), - new Option(new[] { "--report" }, Resources.Accepts_a_file_path_which_if_provided_will_produce_a_format_report_json_file_in_the_given_directory) - { - Argument = new Argument(() => null) { Name = "report-path" }.LegalFilePathsOnly() - }, - new Option(new[] { "--verbosity", "-v" }, Resources.Set_the_verbosity_level_Allowed_values_are_quiet_minimal_normal_detailed_and_diagnostic) - { - Argument = new Argument() { Arity = ArgumentArity.ExactlyOne }.FromAmong(VerbosityLevels) - }, - new Option(new[] { "--include-generated" }, Resources.Include_generated_code_files_in_formatting_operations) + ArgumentHelpName = "report-path" + }.LegalFilePathsOnly(), + new Option(new[] { "--verbosity", "-v" }, Resources.Set_the_verbosity_level_Allowed_values_are_quiet_minimal_normal_detailed_and_diagnostic).FromAmong(VerbosityLevels), + new Option(new[] { "--include-generated" }, Resources.Include_generated_code_files_in_formatting_operations) { IsHidden = true }, - new Option(new[] { "--binarylog" }, Resources.Log_all_project_or_solution_load_information_to_a_binary_log_file) + new Option(new[] { "--binarylog" }, Resources.Log_all_project_or_solution_load_information_to_a_binary_log_file, argumentType: typeof(string), arity: ArgumentArity.ZeroOrOne) { - Argument = new Argument(() => null) { Name = "binary-log-path", Arity = ArgumentArity.ZeroOrOne }.LegalFilePathsOnly() - }, + ArgumentHelpName = "binary-log-path" + }.LegalFilePathsOnly(), }; rootCommand.Description = "dotnet-format"; @@ -95,8 +80,8 @@ internal static RootCommand CreateCommandLineOptions() internal static string? EnsureFolderNotSpecifiedWhenFixingAnalyzers(CommandResult symbolResult) { - var folder = symbolResult.ValueForOption("--folder"); - var fixAnalyzers = symbolResult.OptionResult("--fix-analyzers"); + var folder = symbolResult.GetValueForOption("--folder"); + var fixAnalyzers = symbolResult.GetOptionResult("--fix-analyzers"); return folder && fixAnalyzers != null ? Resources.Cannot_specify_the_folder_option_when_running_analyzers : null; @@ -104,8 +89,8 @@ internal static RootCommand CreateCommandLineOptions() internal static string? EnsureFolderNotSpecifiedWhenFixingStyle(CommandResult symbolResult) { - var folder = symbolResult.ValueForOption("--folder"); - var fixStyle = symbolResult.OptionResult("--fix-style"); + var folder = symbolResult.GetValueForOption("--folder"); + var fixStyle = symbolResult.GetOptionResult("--fix-style"); return folder && fixStyle != null ? Resources.Cannot_specify_the_folder_option_when_fixing_style : null; @@ -113,8 +98,8 @@ internal static RootCommand CreateCommandLineOptions() internal static string? EnsureFolderNotSpecifiedWithNoRestore(CommandResult symbolResult) { - var folder = symbolResult.ValueForOption("--folder"); - var noRestore = symbolResult.OptionResult("--no-restore"); + var folder = symbolResult.GetValueForOption("--folder"); + var noRestore = symbolResult.GetOptionResult("--no-restore"); return folder && noRestore != null ? Resources.Cannot_specify_the_folder_option_with_no_restore : null; @@ -122,13 +107,30 @@ internal static RootCommand CreateCommandLineOptions() internal static string? EnsureFolderNotSpecifiedWhenLoggingBinlog(CommandResult symbolResult) { - var folder = symbolResult.ValueForOption("--folder"); - var binarylog = symbolResult.OptionResult("--binarylog"); + var folder = symbolResult.GetValueForOption("--folder"); + var binarylog = symbolResult.GetOptionResult("--binarylog"); return folder && binarylog is not null && !binarylog.IsImplicit ? Resources.Cannot_specify_the_folder_option_when_writing_a_binary_log : null; } + internal static OptionResult? GetOptionResult(this CommandResult result, string alias) + { + return result.Children.GetByAlias(alias) as OptionResult; + } + + [return: MaybeNull] + internal static T GetValueForOption(this CommandResult result, string alias) + { + if (result.GetOptionResult(alias) is OptionResult option && + option.GetValueOrDefault() is { } t) + { + return t; + }; + + return default; + } + internal static bool WasOptionUsed(this ParseResult result, params string[] aliases) { return result.Tokens diff --git a/src/Program.cs b/src/Program.cs index 87f266a07c..f7c3764f49 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -48,8 +48,8 @@ private static async Task Main(string[] args) bool noRestore, bool folder, bool fixWhitespace, - string? fixStyle, - string? fixAnalyzers, + string fixStyle, + string fixAnalyzers, string[] diagnostics, string? verbosity, bool check, @@ -173,8 +173,8 @@ private static async Task Main(string[] args) noRestore, logLevel, fixType, - codeStyleSeverity: GetSeverity(fixStyle ?? FixSeverity.Error), - analyzerSeverity: GetSeverity(fixAnalyzers ?? FixSeverity.Error), + codeStyleSeverity: GetSeverity(fixStyle), + analyzerSeverity: GetSeverity(fixAnalyzers), diagnostics: diagnostics.ToImmutableHashSet(), saveFormattedFiles: !check, changesAreErrors: check, @@ -312,6 +312,7 @@ internal static DiagnosticSeverity GetSeverity(string? severity) { return severity?.ToLowerInvariant() switch { + "" => DiagnosticSeverity.Error, FixSeverity.Error => DiagnosticSeverity.Error, FixSeverity.Warn => DiagnosticSeverity.Warning, FixSeverity.Info => DiagnosticSeverity.Info, diff --git a/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs b/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs index fd993d4ea8..7565061e61 100644 --- a/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs +++ b/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs @@ -45,6 +45,8 @@ public async Task InitializeAsync() MSBuildRegistrar.RegisterInstance(); var analyzerWorkspace = await MSBuildWorkspaceLoader.LockedLoadAsync(workspacePath, WorkspaceType.Project, binaryLogPath: null, logWorkspaceWarnings: true, logger, CancellationToken.None); + TestOutputHelper.WriteLine(logger.GetLog()); + // From this project we can get valid AnalyzerReferences to add to our test project. _analyzerReferencesProject = analyzerWorkspace.CurrentSolution.Projects.Single(); } diff --git a/tests/ProgramTests.cs b/tests/ProgramTests.cs index b930bc6edc..18a59606e6 100644 --- a/tests/ProgramTests.cs +++ b/tests/ProgramTests.cs @@ -59,17 +59,17 @@ public void CommandLine_OptionsAreParsedCorrectly() Assert.Equal(0, result.Errors.Count); Assert.Equal(0, result.UnmatchedTokens.Count); Assert.Equal(0, result.UnparsedTokens.Count); - Assert.True(result.ValueForOption("folder")); - Assert.Collection(result.ValueForOption>("include"), + Assert.True(result.ValueForOption("--folder")); + Assert.Collection(result.ValueForOption>("--include"), i0 => Assert.Equal("include1", i0), i1 => Assert.Equal("include2", i1)); - Assert.Collection(result.ValueForOption>("exclude"), + Assert.Collection(result.ValueForOption>("--exclude"), i0 => Assert.Equal("exclude1", i0), i1 => Assert.Equal("exclude2", i1)); - Assert.True(result.ValueForOption("check")); - Assert.Equal("report", result.ValueForOption("report")); - Assert.Equal("detailed", result.ValueForOption("verbosity")); - Assert.True(result.ValueForOption("include-generated")); + Assert.True(result.ValueForOption("--check")); + Assert.Equal("report", result.ValueForOption("--report")); + Assert.Equal("detailed", result.ValueForOption("--verbosity")); + Assert.True(result.ValueForOption("--include-generated")); } [Fact] @@ -83,7 +83,7 @@ public void CommandLine_ProjectArgument_Simple() // Assert Assert.Equal(0, result.Errors.Count); - Assert.Equal("workspaceValue", result.CommandResult.GetArgumentValueOrDefault("workspace")); + Assert.Equal("workspaceValue", result.ValueForArgument("workspace")); } [Fact] @@ -97,8 +97,8 @@ public void CommandLine_ProjectArgument_WithOption_AfterArgument() // Assert Assert.Equal(0, result.Errors.Count); - Assert.Equal("workspaceValue", result.CommandResult.GetArgumentValueOrDefault("workspace")); - Assert.Equal("detailed", result.ValueForOption("verbosity")); + Assert.Equal("workspaceValue", result.ValueForArgument("workspace")); + Assert.Equal("detailed", result.ValueForOption("--verbosity")); } [Fact] @@ -112,8 +112,8 @@ public void CommandLine_ProjectArgument_WithOption_BeforeArgument() // Assert Assert.Equal(0, result.Errors.Count); - Assert.Equal("workspaceValue", result.CommandResult.GetArgumentValueOrDefault("workspace")); - Assert.Equal("detailed", result.ValueForOption("verbosity")); + Assert.Equal("workspaceValue", result.ValueForArgument("workspace")); + Assert.Equal("detailed", result.ValueForOption("--verbosity")); } [Fact] @@ -296,5 +296,44 @@ public void CommandLine_BinaryLog_FailsIfFolderIsSpecified() // Assert Assert.Equal(1, result.Errors.Count); } + + [Fact] + public void CommandLine_Diagnostics_FailsIfDiagnosticNoSpecified() + { + // Arrange + var sut = FormatCommand.CreateCommandLineOptions(); + + // Act + var result = sut.Parse(new[] { "--diagnostics" }); + + // Assert + Assert.Equal(1, result.Errors.Count); + } + + [Fact] + public void CommandLine_Diagnostics_DoesNotFailIfDiagnosticIsSpecified() + { + // Arrange + var sut = FormatCommand.CreateCommandLineOptions(); + + // Act + var result = sut.Parse(new[] { "--diagnostics", "RS0016" }); + + // Assert + Assert.Equal(0, result.Errors.Count); + } + + [Fact] + public void CommandLine_Diagnostics_DoesNotFailIfMultipleDiagnosticAreSpecified() + { + // Arrange + var sut = FormatCommand.CreateCommandLineOptions(); + + // Act + var result = sut.Parse(new[] { "--diagnostics", "RS0016", "RS0017", "RS0018" }); + + // Assert + Assert.Equal(0, result.Errors.Count); + } } }