diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 5b95c66..e5deb73 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -10,7 +10,7 @@ permissions: contents: read env: - version: '10.9.${{ github.run_number }}' + version: '10.10.${{ github.run_number }}' dotnetVersion: '8' repoUrl: ${{ github.server_url }}/${{ github.repository }} vsixPath: src/CodeNav/bin/Release/net472/CodeNav.vsix diff --git a/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj b/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj index b76fd35..94a6565 100644 --- a/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj +++ b/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/CodeNav.OutOfProc/Constants/CodeItemKindEnum.cs b/src/CodeNav.OutOfProc/Constants/CodeItemKindEnum.cs index a4848e2..df1f42e 100644 --- a/src/CodeNav.OutOfProc/Constants/CodeItemKindEnum.cs +++ b/src/CodeNav.OutOfProc/Constants/CodeItemKindEnum.cs @@ -32,6 +32,9 @@ public enum CodeItemKindEnum [EnumOrder(16)] Event, + [EnumOrder(22)] + ExtensionBlock, + [EnumOrder(10)] ImplementedInterface, diff --git a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/BaseMapper.cs b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/BaseMapper.cs index 92ecc68..d5e0d88 100644 --- a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/BaseMapper.cs +++ b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/BaseMapper.cs @@ -104,7 +104,8 @@ private static string MapFullName(SyntaxNode source, string name, SemanticModel /// String display name private static string MapName(SyntaxToken? identifier, NameSyntax? nameSyntax, string name = "") { - if (identifier != null) + if (identifier != null && + !identifier.Value.IsKind(SyntaxKind.None)) { return identifier.Value.Text; } diff --git a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs index 66ac7da..e873f01 100644 --- a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs +++ b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs @@ -133,6 +133,8 @@ SyntaxKind.RecordDeclaration when member is RecordDeclarationSyntax recordSyntax => RecordMapper.MapRecord(recordSyntax, semanticModel, codeDocumentViewModel), SyntaxKind.StructDeclaration when member is StructDeclarationSyntax structSyntax => StructMapper.MapStruct(structSyntax, semanticModel, tree, codeDocumentViewModel), + SyntaxKind.ExtensionBlockDeclaration when member is ExtensionBlockDeclarationSyntax extensionBlockSyntax + => ExtensionBlockMapper.MapExtensionBlock(extensionBlockSyntax, semanticModel, tree, codeDocumentViewModel), _ => null, }; diff --git a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/ExtensionBlockMapper.cs b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/ExtensionBlockMapper.cs new file mode 100644 index 0000000..9a02302 --- /dev/null +++ b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/ExtensionBlockMapper.cs @@ -0,0 +1,51 @@ +using CodeNav.OutOfProc.Constants; +using CodeNav.OutOfProc.Mappers; +using CodeNav.OutOfProc.ViewModels; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.VisualStudio.Extensibility; + +namespace CodeNav.OutOfProc.Languages.CSharp.Mappers; + +public class ExtensionBlockMapper +{ + public static CodeClassItem MapExtensionBlock(ExtensionBlockDeclarationSyntax member, + SemanticModel semanticModel, SyntaxTree tree, CodeDocumentViewModel codeDocumentViewModel) + { + var codeItem = BaseMapper.MapBase(member, semanticModel, codeDocumentViewModel, + member.Identifier, modifiers: member.Modifiers, name: "Extension"); + codeItem.Kind = CodeItemKindEnum.ExtensionBlock; + codeItem.Moniker = IconMapper.MapMoniker(codeItem.Kind, codeItem.Access); + codeItem.OverlayMoniker = ImageMoniker.KnownValues.OverlayLoginDisabled; + codeItem.Parameters = ParameterMapper.MapParameters(member.ParameterList); + codeItem.Tooltip = TooltipMapper.Map(member, codeItem.Access, string.Empty, codeItem.Name, codeItem.Parameters); + + // Map regions + var regions = RegionMapper.MapRegions(tree, member.Span, codeDocumentViewModel); + + // Map block members + foreach (var blockMember in member.Members) + { + var memberItem = DocumentMapper.MapMember(blockMember, tree, semanticModel, codeDocumentViewModel); + + if (memberItem == null) + { + continue; + } + + // Add member to region if it is part of one + if (RegionMapper.AddToRegion(regions, memberItem)) + { + continue; + } + + // Still here? Add the member to the block + codeItem.Members.Add(memberItem); + } + + // Add regions to block + codeItem.Members.AddRange(regions); + + return codeItem; + } +} diff --git a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/MethodMapper.cs b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/MethodMapper.cs index 53bf16e..a6562fd 100644 --- a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/MethodMapper.cs +++ b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/MethodMapper.cs @@ -55,7 +55,9 @@ private static CodeItem MapMethod(SyntaxNode node, SyntaxToken identifier, codeItem.Id = IdMapper.MapId(codeItem.FullName, parameterList); codeItem.Kind = kind; - codeItem.Moniker = IconMapper.MapMoniker(codeItem.Kind, codeItem.Access); + codeItem.Moniker = IsExtensionMethod(node, semanticModel) + ? ImageMoniker.KnownValues.ExtensionMethod + : IconMapper.MapMoniker(codeItem.Kind, codeItem.Access); return codeItem; } @@ -70,8 +72,13 @@ public static CodeItem MapConstructor(ConstructorDeclarationSyntax member, codeItem.Id = IdMapper.MapId(member.Identifier, member.ParameterList); codeItem.Kind = CodeItemKindEnum.Constructor; codeItem.Moniker = IconMapper.MapMoniker(codeItem.Kind, codeItem.Access); - codeItem.OverlayMoniker = ImageMoniker.KnownValues.Add; return codeItem; } + + private static bool IsExtensionMethod(SyntaxNode node, SemanticModel semanticModel) +{ + var symbol = semanticModel.GetDeclaredSymbol(node); + return symbol is IMethodSymbol { IsExtensionMethod: true }; +} } diff --git a/src/CodeNav.OutOfProc/Mappers/FilterRuleMapper.cs b/src/CodeNav.OutOfProc/Mappers/FilterRuleMapper.cs index 41096e2..25ac438 100644 --- a/src/CodeNav.OutOfProc/Mappers/FilterRuleMapper.cs +++ b/src/CodeNav.OutOfProc/Mappers/FilterRuleMapper.cs @@ -15,7 +15,8 @@ or CodeItemKindEnum.Region or CodeItemKindEnum.ImplementedInterface or CodeItemKindEnum.Constructor or CodeItemKindEnum.LocalFunction - or CodeItemKindEnum.BaseClass => [CodeItemAccessEnum.All], + or CodeItemKindEnum.BaseClass + or CodeItemKindEnum.ExtensionBlock => [CodeItemAccessEnum.All], CodeItemKindEnum.Struct => [CodeItemAccessEnum.Public, CodeItemAccessEnum.Internal, CodeItemAccessEnum.Private, CodeItemAccessEnum.All], _ => [.. Enum.GetValues().Except([CodeItemAccessEnum.Unknown])], }; @@ -29,7 +30,8 @@ or CodeItemKindEnum.Region or CodeItemKindEnum.ImplementedInterface or CodeItemKindEnum.Interface or CodeItemKindEnum.Method - or CodeItemKindEnum.BaseClass => true, + or CodeItemKindEnum.BaseClass + or CodeItemKindEnum.ExtensionBlock => true, _ => false, }; diff --git a/src/CodeNav.OutOfProc/Mappers/IconMapper.cs b/src/CodeNav.OutOfProc/Mappers/IconMapper.cs index 240e7d9..f14a304 100644 --- a/src/CodeNav.OutOfProc/Mappers/IconMapper.cs +++ b/src/CodeNav.OutOfProc/Mappers/IconMapper.cs @@ -15,13 +15,15 @@ public static ImageMoniker MapMoniker(CodeItemKindEnum kind, CodeItemAccessEnum CodeItemKindEnum.BaseClass => "ClassShortcut", CodeItemKindEnum.Class => $"Class{accessString}", CodeItemKindEnum.Constant => $"Constant{accessString}", - CodeItemKindEnum.Constructor or CodeItemKindEnum.Method => $"Method{accessString}", + CodeItemKindEnum.Constructor => "NewMethod", CodeItemKindEnum.Delegate => $"Delegate{accessString}", CodeItemKindEnum.Enum => $"Enumeration{accessString}", CodeItemKindEnum.EnumMember => $"EnumerationItem{accessString}", CodeItemKindEnum.Event => $"Event{accessString}", + CodeItemKindEnum.ExtensionBlock => $"Class", CodeItemKindEnum.ImplementedInterface => "ImplementInterface", CodeItemKindEnum.Interface => $"Interface{accessString}", + CodeItemKindEnum.Method => $"Method{accessString}", CodeItemKindEnum.Namespace => $"Namespace{accessString}", CodeItemKindEnum.Property or CodeItemKindEnum.Indexer => $"Property{accessString}", CodeItemKindEnum.Region => $"Numeric", diff --git a/src/CodeNav.OutOfProc/ToolWindows/Templates/CodeItemStyles.xaml b/src/CodeNav.OutOfProc/ToolWindows/Templates/CodeItemStyles.xaml index 25676cf..619dc2e 100644 --- a/src/CodeNav.OutOfProc/ToolWindows/Templates/CodeItemStyles.xaml +++ b/src/CodeNav.OutOfProc/ToolWindows/Templates/CodeItemStyles.xaml @@ -232,12 +232,16 @@ HorizontalAlignment="Stretch"> - + + + + diff --git a/test/CodeNav.Test.csproj b/test/CodeNav.Test.csproj index 13df255..a48f1a2 100644 --- a/test/CodeNav.Test.csproj +++ b/test/CodeNav.Test.csproj @@ -4,6 +4,8 @@ net8.0-windows8.0 enable enable + 14 + true diff --git a/test/Files/ExtensionBlock/TestExtensionBlock.cs b/test/Files/ExtensionBlock/TestExtensionBlock.cs new file mode 100644 index 0000000..bfe26f2 --- /dev/null +++ b/test/Files/ExtensionBlock/TestExtensionBlock.cs @@ -0,0 +1,16 @@ +namespace CodeNav.Test.Files.ExtensionBlock; + +public static class MyExtensions +{ + public static IEnumerable ValuesLessThan(this IEnumerable source, int threshold) + => source.Where(x => x < threshold); + + extension(IEnumerable source) + { + public IEnumerable ValuesGreaterThan(int threshold) + => source.Where(x => x > threshold); + + public IEnumerable ValuesGreaterThanZero + => source.ValuesGreaterThan(0); + } +} diff --git a/test/MapperTests/MapperTestExtensionBlock.cs b/test/MapperTests/MapperTestExtensionBlock.cs new file mode 100644 index 0000000..64119da --- /dev/null +++ b/test/MapperTests/MapperTestExtensionBlock.cs @@ -0,0 +1,21 @@ +namespace CodeNav.Test.MapperTests; + +[TestFixture] +internal class MapperTestExtensionBlock : BaseTest +{ + [Test] + public async Task TestInlineBaseClassShouldBeOk() + { + var codeItems = await MapToCodeItems("ExtensionBlock/TestExtensionBlock.cs"); + + // First item should be a namespace + var namespaceItem = GetNamespace(codeItems); + + var classItem = GetFirstClass(namespaceItem); + + var extensionBlockItem = GetMemberAtIndex(classItem, 1) as CodeClassItem; + + // Extension block item should have 2 members + Assert.That(extensionBlockItem!.Members, Has.Count.EqualTo(2)); + } +}