Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/dotnet-inspect.Tests/CommandExecutionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
18 changes: 18 additions & 0 deletions src/dotnet-inspect.Tests/Parsers/MemberOptionsParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
16 changes: 9 additions & 7 deletions src/dotnet-inspect/Commands/ApiCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CliApiSurface>()!.ToDocumentSchema();
var memberSchemaMap = ApiViewContext.Default.GetSchemaInfo<TypeView>()!.ToDocumentSchema();
// Use combined section names for discovery
return (null!, DiscoverOutput.Execute(options.Discover, typeSchemaMap,
var schema = singleTypeMode
? ApiViewContext.Default.GetSchemaInfo<TypeView>()!.ToDocumentSchema()
: ApiViewContext.Default.GetSchemaInfo<CliApiSurface>()!.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)
Expand Down
70 changes: 70 additions & 0 deletions src/dotnet-inspect/Output/ApiOutputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -888,4 +896,66 @@ private static string GetTreeKindLabel(string kind, int count)
};
return $"{plural} ({count})";
}

private static HashSet<string>? GetRequestedMemberKinds(HashSet<string>? includeSections)
{
if (includeSections is not { Count: > 0 })
return null;

HashSet<string> 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<string>? GetRequestedTypeKinds(HashSet<string>? includeSections)
{
if (includeSections is not { Count: > 0 })
return null;

HashSet<string> 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;
}
}
10 changes: 6 additions & 4 deletions src/dotnet-inspect/Services/SharedOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public SharedOptions()

Select = new Option<string?>("-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");
Expand All @@ -85,13 +85,13 @@ public SharedOptions()

Columns = new Option<string?>("--columns")
{
Description = "Filter columns by name (comma-separated)",
Description = "Filter columns by name (comma/semicolon-separated)",
Arity = ArgumentArity.ZeroOrOne
};

Fields = new Option<string?>("--fields")
{
Description = "Filter fields by name (comma-separated)",
Description = "Filter fields by name (comma/semicolon-separated)",
Arity = ArgumentArity.ZeroOrOne
};
}
Expand Down Expand Up @@ -311,10 +311,12 @@ private static bool IsBareFlag(ParseResult parseResult, Option<string?> 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);
}
}
2 changes: 1 addition & 1 deletion src/dotnet-inspect/dotnet-inspect.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<Authors>Richard Lander</Authors>
<PackAsTool>true</PackAsTool>
<ToolCommandName>dotnet-inspect</ToolCommandName>
<VersionPrefix>0.7.8</VersionPrefix>
<VersionPrefix>0.7.9</VersionPrefix>
<PackageId>dotnet-inspect</PackageId>
<Description>A CLI tool for inspecting .NET assemblies and NuGet packages</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
Loading