diff --git a/src/dotnet-inspect.Tests/CommandExecutionTests.cs b/src/dotnet-inspect.Tests/CommandExecutionTests.cs index cc00d731..9f586f99 100644 --- a/src/dotnet-inspect.Tests/CommandExecutionTests.cs +++ b/src/dotnet-inspect.Tests/CommandExecutionTests.cs @@ -114,6 +114,42 @@ public async Task Api_PlatformLibrary_OneLine() Assert.True(lines.Length > 1, "Expected multiple lines of type output"); } + [Fact] + public async Task Type_SingleType_SelectClasses_ShowsSelectError() + { + var options = new TypeOptions + { + PlatformAssembly = "System.Text.Json", + TypeName = "JsonSerializer", + OneLine = true, + Select = ["Classes"] + }; + + var (exit, _, error) = await ConsoleCapture.RunAsync( + () => TypeCommand.ExecuteAsync(options)); + + Assert.Equal(1, exit); + Assert.Contains("Select value 'Classes' not found", error); + } + + [Fact] + public async Task Type_SingleType_DiscoverMethods_Works() + { + var options = new TypeOptions + { + PlatformAssembly = "System.Text.Json", + TypeName = "JsonSerializer", + Discover = ["Methods"] + }; + + var (exit, output, _) = await ConsoleCapture.RunAsync( + () => TypeCommand.ExecuteAsync(options)); + + Assert.Equal(0, exit); + Assert.Contains("Name", output); + Assert.Contains("Signature", output); + } + [Fact] public async Task Api_NonexistentPackage_ShowsError() { diff --git a/src/dotnet-inspect.Tests/Parsers/MemberOptionsParserTests.cs b/src/dotnet-inspect.Tests/Parsers/MemberOptionsParserTests.cs index 0d8b0ec1..6743cfd7 100644 --- a/src/dotnet-inspect.Tests/Parsers/MemberOptionsParserTests.cs +++ b/src/dotnet-inspect.Tests/Parsers/MemberOptionsParserTests.cs @@ -291,4 +291,22 @@ public async Task KindFilter_SetsKindFilter() Assert.Contains("method", options.KindFilter); } + + [Fact] + public async Task Columns_WithSemicolonSeparator_AreParsedAsMultipleColumns() + { + var options = await ParseSuccessAsync("member", "JsonSerializer", "--package", "System.Text.Json", "--columns", "Kind;Type"); + + Assert.NotNull(options.Columns); + Assert.Equal(["Kind", "Type"], options.Columns); + } + + [Fact] + public async Task Select_WithSemicolonSeparator_AreParsedAsMultipleSections() + { + var options = await ParseSuccessAsync("member", "JsonSerializer", "--package", "System.Text.Json", "-S", "Classes;Constructors"); + + Assert.NotNull(options.Select); + Assert.Equal(["Classes", "Constructors"], options.Select); + } } diff --git a/src/dotnet-inspect/Commands/ApiCommand.cs b/src/dotnet-inspect/Commands/ApiCommand.cs index 6469380a..43b50daf 100644 --- a/src/dotnet-inspect/Commands/ApiCommand.cs +++ b/src/dotnet-inspect/Commands/ApiCommand.cs @@ -58,21 +58,23 @@ internal static (PreambleResult Result, int? Error) RunPreamble(ApiOptions optio { var typePipeline = ApiTypeSectionDescriptors.CreatePipeline(); var memberPipeline = ApiMemberSectionDescriptors.CreatePipeline(); - var allApiSections = typePipeline.AllSectionNames.Concat(memberPipeline.AllSectionNames).Distinct().ToArray(); + bool hasTypeName = !string.IsNullOrWhiteSpace(options.TypeName); + bool typeNameIsGlob = hasTypeName && (options.TypeName!.Contains('*') || options.TypeName!.Contains('?')); + bool singleTypeMode = options is MemberOptions || (hasTypeName && !typeNameIsGlob); + var knownSections = singleTypeMode ? memberPipeline.AllSectionNames : typePipeline.AllSectionNames; // Discovery mode: -D/--discover lists schema if (options.Discover != null) { - // Combine type-list and member-detail schema maps - var typeSchemaMap = ApiViewContext.Default.GetSchemaInfo()!.ToDocumentSchema(); - var memberSchemaMap = ApiViewContext.Default.GetSchemaInfo()!.ToDocumentSchema(); - // Use combined section names for discovery - return (null!, DiscoverOutput.Execute(options.Discover, typeSchemaMap, + var schema = singleTypeMode + ? ApiViewContext.Default.GetSchemaInfo()!.ToDocumentSchema() + : ApiViewContext.Default.GetSchemaInfo()!.ToDocumentSchema(); + return (null!, DiscoverOutput.Execute(options.Discover, schema, tree: options.Tree, json: options.JsonOutput, markdown: !options.OneLine && !options.JsonOutput)); } // -S/--select with values: resolve as section filter for backpressure - var selectResult = SelectResolver.ResolveSelectAsSections(options.Select, allApiSections); + var selectResult = SelectResolver.ResolveSelectAsSections(options.Select, knownSections); if (SelectOutput.WriteUnresolved(selectResult)) return (null!, 1); if (selectResult.Sections != null) diff --git a/src/dotnet-inspect/Output/ApiOutputFormatter.cs b/src/dotnet-inspect/Output/ApiOutputFormatter.cs index 0939bc56..7afda2ae 100644 --- a/src/dotnet-inspect/Output/ApiOutputFormatter.cs +++ b/src/dotnet-inspect/Output/ApiOutputFormatter.cs @@ -764,6 +764,11 @@ internal static int GetMemberSortOrder(string kind) internal static (ApiTypeOneLineView view, int truncated) BuildTypeOneLineView(ApiType type, ApiOptions options) { var grouped = GroupMembersByKind(type, options.MemberFilter, options.UnsafeOnly, options.KindFilter); + var requestedKinds = GetRequestedMemberKinds(options.IncludeSections); + if (requestedKinds is { Count: > 0 }) + grouped = grouped + .Where(kvp => requestedKinds.Contains(kvp.Key)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); if (grouped.Count == 0) return (new ApiTypeOneLineView(), 0); var allEntries = grouped @@ -814,6 +819,9 @@ internal static (ApiSurfaceOneLineView view, int truncated) BuildSurfaceOneLineV { int truncated = 0; var types = api.Types; + var requestedKinds = GetRequestedTypeKinds(options.IncludeSections); + if (requestedKinds is { Count: > 0 }) + types = types.Where(t => requestedKinds.Contains(t.Kind)).ToList(); if (options.Limit.HasValue && options.Limit.Value < types.Count) { truncated = types.Count - options.Limit.Value; @@ -888,4 +896,66 @@ private static string GetTreeKindLabel(string kind, int count) }; return $"{plural} ({count})"; } + + private static HashSet? GetRequestedMemberKinds(HashSet? includeSections) + { + if (includeSections is not { Count: > 0 }) + return null; + + HashSet kinds = []; + foreach (var section in includeSections) + { + switch (section) + { + case "Constructors": + kinds.Add("constructor"); + break; + case "Fields": + kinds.Add("field"); + break; + case "Properties": + kinds.Add("property"); + break; + case "Methods": + kinds.Add("method"); + break; + case "Events": + kinds.Add("event"); + break; + } + } + + return kinds; + } + + private static HashSet? GetRequestedTypeKinds(HashSet? includeSections) + { + if (includeSections is not { Count: > 0 }) + return null; + + HashSet kinds = []; + foreach (var section in includeSections) + { + switch (section) + { + case "Classes": + kinds.Add("class"); + break; + case "Structs": + kinds.Add("struct"); + break; + case "Interfaces": + kinds.Add("interface"); + break; + case "Enums": + kinds.Add("enum"); + break; + case "Delegates": + kinds.Add("delegate"); + break; + } + } + + return kinds; + } } diff --git a/src/dotnet-inspect/Services/SharedOptions.cs b/src/dotnet-inspect/Services/SharedOptions.cs index aaecd41f..f39ae919 100644 --- a/src/dotnet-inspect/Services/SharedOptions.cs +++ b/src/dotnet-inspect/Services/SharedOptions.cs @@ -76,7 +76,7 @@ public SharedOptions() Select = new Option("-S") { - Description = "Select sections by name (comma-separated, supports wildcards)", + Description = "Select sections by name (comma/semicolon-separated, supports wildcards)", Arity = ArgumentArity.ZeroOrOne }; Select.Aliases.Add("--select"); @@ -85,13 +85,13 @@ public SharedOptions() Columns = new Option("--columns") { - Description = "Filter columns by name (comma-separated)", + Description = "Filter columns by name (comma/semicolon-separated)", Arity = ArgumentArity.ZeroOrOne }; Fields = new Option("--fields") { - Description = "Filter fields by name (comma-separated)", + Description = "Filter fields by name (comma/semicolon-separated)", Arity = ArgumentArity.ZeroOrOne }; } @@ -311,10 +311,12 @@ private static bool IsBareFlag(ParseResult parseResult, Option option) string.IsNullOrWhiteSpace(parseResult.GetValue(option)); } + private static readonly char[] ListSeparators = [',', ';']; + private static string[]? ParseCommaSeparatedList(string? value) { if (string.IsNullOrWhiteSpace(value)) return null; - return value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return value.Split(ListSeparators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); } } diff --git a/src/dotnet-inspect/dotnet-inspect.csproj b/src/dotnet-inspect/dotnet-inspect.csproj index 81cb2d2c..824e4767 100644 --- a/src/dotnet-inspect/dotnet-inspect.csproj +++ b/src/dotnet-inspect/dotnet-inspect.csproj @@ -21,7 +21,7 @@ Richard Lander true dotnet-inspect - 0.7.8 + 0.7.9 dotnet-inspect A CLI tool for inspecting .NET assemblies and NuGet packages MIT