Skip to content
Open
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
180 changes: 83 additions & 97 deletions src/Transform/TransformBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,139 +468,125 @@ private string GetParameterTypeNameForSyntax(Type type, IEnumerable<Attribute> a
return parameterTypeString;
}

private void AddGenericArguments(StringBuilder sb, Type[] genericArguments)
{
sb.Append($"`{genericArguments.Length}[");
for (int i = 0; i < genericArguments.Length; i++)
{
if (i > 0) { sb.Append(','); }

sb.Append(GetAbbreviatedType(genericArguments[i]));
}

sb.Append(']');
}

private string GetAbbreviatedType(Type type)
/// <summary>
/// Build syntax parameter type or parameter type to string.
/// </summary>
/// <param name="sb">StringBuilder</param>
/// <param name="type">The type to be abbreviated</param>
/// <param name="abbreviate">
/// Try get abbreviated name.
/// e.g.) `System.Int32` -> `int`
/// </param>
/// <param name="dropNamespace">
/// Build type descriptor as no namespace.
/// </param>
/// <param name="fromNested">
/// Indicates that the <paramref name="type"/> is the type of the nesting source.
/// </param>
private static void BuildTypeString(StringBuilder sb, Type? type, bool abbreviate, bool dropNamespace, bool fromNested = false)
{
if (type is null)
{
return string.Empty;
return;
}

string result;
if (type.IsGenericType && !type.IsGenericTypeDefinition)
{
string genericDefinition = GetAbbreviatedType(type.GetGenericTypeDefinition());
// For regular generic types, we find the backtick character, for example:
// System.Collections.Generic.List`1[T] ->
// System.Collections.Generic.List[string]
// For nested generic types, we find the left bracket character, for example:
// System.Collections.Generic.Dictionary`2+Enumerator[TKey, TValue] ->
// System.Collections.Generic.Dictionary`2+Enumerator[string,string]
int backtickOrLeftBracketIndex = genericDefinition.LastIndexOf(type.IsNested ? '[' : '`');
var sb = new StringBuilder(genericDefinition, 0, backtickOrLeftBracketIndex, 512);
AddGenericArguments(sb, type.GetGenericArguments());
result = sb.ToString();
}
else if (type.IsArray)
{
string elementDefinition = GetAbbreviatedType(type.GetElementType());
var sb = new StringBuilder(elementDefinition, elementDefinition.Length + 10);
BuildTypeString(sb, type.GetGenericTypeDefinition(), abbreviate, dropNamespace);
var genericArgs = type.GetGenericArguments();
sb.Append('[');
for (int i = 0; i < type.GetArrayRank() - 1; ++i)
for (var i = 0; i < genericArgs.Length; i++)
{
sb.Append(',');
if (i > 0) sb.Append(',');
BuildTypeString(sb, genericArgs[i], abbreviate, dropNamespace);
}

sb.Append(']');
result = sb.ToString();
}
else if (type.IsArray)
{
BuildTypeString(sb, type.GetElementType(), abbreviate, dropNamespace);
sb.Append('[')
.Append(',', type.GetArrayRank() - 1)
.Append(']');
}
else
{
if (TransformUtils.TryGetTypeAbbreviation(type.FullName, out string abbreviation))
if (abbreviate && TransformUtils.TryGetTypeAbbreviation(type.FullName, out string abbreviatedName))
{
return abbreviation;
sb.Append(abbreviatedName);
return;
}

if (type == typeof(PSCustomObject))
if (!dropNamespace && !string.IsNullOrEmpty(type.Namespace))
{
return type.Name;
sb.Append(type.Namespace)
.Append('.');
}

// Indicates whether the "`n" sign at the end of a generic type name can be omitted
// e.g.) System.Collections.Generic.Dictionary`2[TKey, TValue]
// ^^ Can ommit
bool canOmmitGenericTailingSign = true;
if (type.IsNested)
{
// For nested types, we should return OuterType+InnerType. For example,
// System.Environment+SpecialFolder -> Environment+SpecialFolder
string fullName = type.ToString();
result = type.Namespace == null
? fullName
: fullName.Substring(type.Namespace.Length + 1);
var reflectedType = type.ReflectedType;
// `System.Collections.Generic.Dictionary`2+Enumerator[TKey,TValue]`
// ^^^^^^^^^^^^
BuildTypeString(sb, reflectedType, abbreviate, dropNamespace: true, fromNested: true);
sb.Append('+');
// Nested classe cannot omit the mark if the origin class is a generic type
// e.g.)
// Namespace.ClassA+Nested`1[T]
// ^^ Can ommit
// Namespace.ClassB`1+Nested`1[T1,T2]
// ^^ ^^ Cannot ommit
canOmmitGenericTailingSign = !(reflectedType?.IsGenericType ?? false);
}

if (!fromNested && canOmmitGenericTailingSign)
{
var backtickPosition = type.Name.LastIndexOf('`');
sb.Append(backtickPosition > 0 ? type.Name.Remove(backtickPosition) : type.Name);
}
else
{
result = type.Name;
sb.Append(type.Name);
}
}
}

return result;
private static string GetAbbreviatedType(Type type)
{
StringBuilder sb = Constants.StringBuilderPool.Get();
try
{
BuildTypeString(sb,
Nullable.GetUnderlyingType(type) ?? type,
abbreviate: true,
dropNamespace: true);
return sb.ToString();
}
finally
{
Constants.StringBuilderPool.Return(sb);
}
}

private string GetParameterTypeName(Type type)
{
string typeName = string.Empty;

if (type.IsGenericType)
StringBuilder sb = Constants.StringBuilderPool.Get();
try
{
StringBuilder sb = Constants.StringBuilderPool.Get();

try
{
string genericName = Settings.UseFullTypeName.HasValue && Settings.UseFullTypeName.Value ?
type.GetGenericTypeDefinition().FullName ?? string.Empty :
type.GetGenericTypeDefinition().Name;

if (genericName.Contains(Constants.GenericParameterBackTick))
{
// This removes `2 from type name like: System.Collections.Generic.Dictionary`2
sb.Append(genericName.Remove(genericName.IndexOf(Constants.GenericParameterBackTick)));
}
else
{
sb.Append(genericName);
}

sb.Append(string.Format(Constants.GenericParameterTypeNameStart, type.GetGenericArguments().Length));

List<string> genericParameters = new();

foreach (var name in type.GenericTypeArguments)
{
if (name.FullName is not null)
{
genericParameters.Add(name.FullName);
}
}

sb.Append(string.Join(",", genericParameters));

sb.Append(Constants.GenericParameterTypeNameEnd);

typeName = sb.ToString();
}
finally
{
Constants.StringBuilderPool.Return(sb);
}
BuildTypeString(sb,
Nullable.GetUnderlyingType(type) ?? type,
abbreviate: false,
dropNamespace: !Settings.UseFullTypeName);
return sb.ToString();
}
else
finally
{
typeName = Settings.UseFullTypeName.HasValue && Settings.UseFullTypeName.Value ?
type.FullName ?? string.Empty :
type.Name;
Constants.StringBuilderPool.Return(sb);
}

return typeName;
}

protected Parameter GetParameterInfo(CommandInfo? cmdletInfo, dynamic? helpItem, CommandParameterInfo paramInfo)
Expand Down
8 changes: 4 additions & 4 deletions src/Transform/TransformSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public class TransformSettings
public Guid? ModuleGuid { get; set; }
public string? ModuleName { get; set; }
public string? OnlineVersionUrl { get; set; }
public bool? CreateModulePage { get; set; }
public bool? DoubleDashList { get; set; }
public bool? UseFullTypeName { get; set; }
public bool CreateModulePage { get; set; }
public bool DoubleDashList { get; set; }
public bool UseFullTypeName { get; set; }
public PSSession? Session { get; set; }
public bool? ExcludeDontShow { get; set; }
public bool ExcludeDontShow { get; set; }

public TransformSettings()
{
Expand Down
31 changes: 21 additions & 10 deletions test/Pester/NewMarkdownHelp.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -532,33 +532,44 @@ Write-Host 'Hello World!'
$CCC,
[System.Nullable`1[System.Int32]]
[Parameter(Position=3)]
$ddd
$ddd,
[System.Collections.Generic.Dictionary`2+Enumerator[int, byte[]]]
[Parameter(Position=4)]
$eee
)
}

It 'use full type name when specified' {
$expectedParameters = @(
"string"
"int"
"Dictionary``2+Enumerator[int,byte[]]"
"SwitchParameter"
)
$expectedSyntax = 'Get-Alpha [[-CCC] <string>] [[-ddd] <int>] [-WhatIf] [<CommonParameters>]'
$expectedSyntax = 'Get-Alpha [[-CCC] <string>] [[-ddd] <int>] [[-eee] <Dictionary`2+Enumerator[int,byte[]]>] [-WhatIf] [<CommonParameters>]'
$expectedParameterTypes = @(
"System.String"
"System.Int32"
"System.Collections.Generic.Dictionary``2+Enumerator[System.Int32,System.Byte[]]"
"System.Management.Automation.SwitchParameter"
)

$files = New-MarkdownCommandHelp -Command (get-command Get-Alpha) -OutputFolder "$TestDrive/alpha" -Force
$commandHelp = Import-MarkdownCommandHelp $files
$commandHelp.Syntax.SyntaxParameters.ParameterType | Should -Be $expectedParameters
$commandHelp.Syntax[0].ToString() | Should -Be $expectedSyntax
$commandHelp.Syntax.SyntaxParameters.ParameterType | Should -BeExactly $expectedParameters
$commandHelp.Syntax[0].ToString() | Should -BeExactly $expectedSyntax
$commandHelp.Parameters.Type | Should -BeExactly $expectedParameterTypes
}

It 'does not use full type name when specified' {
$expectedParameterNames = "CCC","ddd","WhatIf"
$expectedParameterTypes = "String","Nullable``1[System.Int32]","SwitchParameter"
$expectedSyntax = 'Get-Alpha [[-CCC] <string>] [[-ddd] <int>] [-WhatIf] [<CommonParameters>]'
$expectedParameterNames = "CCC","ddd","eee","WhatIf"
$expectedParameterTypes = "String","Int32","Dictionary``2+Enumerator[Int32,Byte[]]","SwitchParameter"
$expectedSyntax = 'Get-Alpha [[-CCC] <string>] [[-ddd] <int>] [[-eee] <Dictionary`2+Enumerator[int,byte[]]>] [-WhatIf] [<CommonParameters>]'
$file = New-MarkdownCommandHelp -Command (get-command Get-Alpha) -OutputFolder "$TestDrive/alpha" -Force -AbbreviateParameterTypeName
$ch = Import-MarkdownCommandHelp $file
$ch.Parameters.Name | Should -Be $expectedParameterNames
$ch.Parameters.Type | Should -Be $expectedParameterTypes
$ch.Syntax[0].ToString() | Should -Be $expectedSyntax
$ch.Parameters.Name | Should -BeExactly $expectedParameterNames
$ch.Parameters.Type | Should -BeExactly $expectedParameterTypes
$ch.Syntax[0].ToString() | Should -BeExactly $expectedSyntax
}
}

Expand Down