Skip to content

Commit b62baae

Browse files
d1820claude
andcommitted
Merge vs2026 into main
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2 parents 27656d7 + 5ff5bb4 commit b62baae

72 files changed

Lines changed: 2795 additions & 17 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Build and Test
6+
7+
```
8+
# Restore packages
9+
nuget restore CodeDocumentor.sln
10+
11+
# Build (no VS deployment)
12+
msbuild CodeDocumentor.sln /p:configuration="Release" /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal
13+
14+
# Debug build (deploys to VS Experimental Instance)
15+
msbuild CodeDocumentor.sln /p:configuration="Debug"
16+
17+
# Run tests
18+
vstest.console.exe CodeDocumentor.Test\bin\Release\net48\CodeDocumentor.Test.dll
19+
```
20+
21+
CI runs on `windows-latest` via `.github/workflows/dotnet.yml`.
22+
23+
## Architecture
24+
25+
Visual Studio extension (VSIX) that generates XML doc comments (`///`) for C# code members using Roslyn -- no network calls.
26+
27+
### Projects
28+
29+
| Project | Role |
30+
|---|---|
31+
| `CodeDocumentor2026` | Main VSIX (.NET 4.8, VS2022/VS2026). Entry point: `CodeDocumentor2026Package.cs` |
32+
| `CodeDocumentor.Common` | Core logic: comment generation, Roslyn tree manipulation (netstandard2.0) |
33+
| `CodeDocumentor.Test` | xUnit + MSTest suite (net48) |
34+
| `CodeDocumentor.Analyzers` | Legacy Roslyn DiagnosticAnalyzer approach -- kept for reference, not used by 2026 extension |
35+
| `CodeDocumentor` | Older VS2022-era VSIX -- retained but superseded |
36+
37+
### Data Flow
38+
39+
```
40+
User action (context menu / editor command)
41+
→ Command.Execute()
42+
→ CommentExecutor -- iterates selected DTE project items, recurses folders
43+
→ TextSelectionExecutor -- grabs raw C# from editor, inserts result, calls SmartFormat
44+
→ CommentBuilderService.AddDocumentation(fileContents)
45+
→ CSharpSyntaxTree.ParseText()
46+
→ Build*Comments() per member type (two passes: members first, then containers)
47+
→ root.ReplaceNodes()
48+
→ returns modified source string
49+
```
50+
51+
### Key Files in `CodeDocumentor.Common`
52+
53+
- `Services/CommentBuilderService.cs` -- central service; `AddDocumentation()` is the main entry; processes Properties/Constructors/Enums/Fields/Methods first, then Interfaces/Classes/Records (second pass because they contain the first)
54+
- `Builders/DocumentationBuilder.cs` -- fluent Roslyn `XmlNodeSyntax` builder; methods: `WithSummary`, `WithParameters`, `WithTypeParamters`, `WithReturnType`, `WithExceptionTypes`, `WithPropertyValueTypes`, `WithExisting`
55+
- `Helper/CommentHelper.cs` -- converts PascalCase identifiers to human-readable summary text via WordMaps, pluralization, and article insertion
56+
- `Helper/DocumentationHeaderHelper.cs` -- creates Roslyn XML element syntax nodes
57+
- `Helpers/NameSplitter.cs` -- splits PascalCase/camelCase into word parts
58+
- `Locators/ServiceLocator.cs` -- static singleton locator for `DocumentationHeaderHelper`, `CommentHelper`, `GenericCommentManager`, `ICommentBuilderService`, `IEventLogger`, `ISettingService`
59+
- `Models/Settings2026.cs` -- all user settings: async suffix, value nodes, public-only, preserve existing summary, natural language returns, cref inclusion, word maps, TODO fallback
60+
61+
### Supported Member Types (8 total)
62+
63+
`CommentBuilderService` handles these Roslyn syntax node types. Each has a `Build*Comments()` method and a `BuildNewDeclaration()` overload, and is registered in `IsDocumentableNode()` and `BuildNewDocumentationNode()`:
64+
65+
| C# construct | Roslyn type | Batch |
66+
|---|---|---|
67+
| class | `ClassDeclarationSyntax` | 2 (container) |
68+
| interface | `InterfaceDeclarationSyntax` | 2 (container) |
69+
| record | `RecordDeclarationSyntax` | 2 (container) |
70+
| enum | `EnumDeclarationSyntax` | 1 (member) |
71+
| method | `MethodDeclarationSyntax` | 1 (member) |
72+
| property | `PropertyDeclarationSyntax` | 1 (member) |
73+
| constructor | `ConstructorDeclarationSyntax` | 1 (member) |
74+
| field | `FieldDeclarationSyntax` | 1 (member) |
75+
76+
Two-pass `root.ReplaceNodes()` is intentional: batch 1 (members) runs first so container nodes in batch 2 already contain updated children.
77+
78+
### Adding a New Member Type
79+
80+
To add support for a new C# syntax node type (e.g. `EventFieldDeclarationSyntax`):
81+
82+
1. Add a `Build*Comments()` method in `CommentBuilderService.cs` (follow existing pattern -- collect old/new node pairs, return a replacement dictionary)
83+
2. Add a `BuildNewDeclaration(XxxSyntax node, ...)` overload in `CommentBuilderService.cs`
84+
3. Register in `IsDocumentableNode()` switch (line ~528)
85+
4. Register in `BuildNewDocumentationNode()` switch (line ~566)
86+
5. Add to the appropriate batch in `AddDocumentation()` (batch 1 for members, batch 2 for containers)
87+
6. Add to `ICommentBuilderService` interface
88+
7. Use `DocumentationBuilder` fluent methods -- `WithSummary` always; add `WithParameters` / `WithReturnType` / `WithExceptionTypes` as applicable
89+
8. Use `DocumentationHeaderHelper` to create raw XML nodes; `CommentHelper` to generate human-readable text from identifier names
90+
91+
### Known Missing Types (not yet supported)
92+
93+
- `EventFieldDeclarationSyntax` -- `public event EventHandler Click;` (field-like, most common form)
94+
- `EventDeclarationSyntax` -- explicit `add`/`remove` event accessors
95+
- `DelegateDeclarationSyntax` -- `public delegate void MyHandler(...)`
96+
- `StructDeclarationSyntax` -- `struct` (mirrors class; would go in batch 2)
97+
- `IndexerDeclarationSyntax` -- `this[int index]`
98+
- `DestructorDeclarationSyntax` -- `~MyClass()`
99+
- `OperatorDeclarationSyntax` -- `operator+` etc.
100+
- `ConversionOperatorDeclarationSyntax` -- `implicit`/`explicit` operator
101+
- `EnumMemberDeclarationSyntax` -- individual enum values (whole enum is supported, members are not)
102+
103+
### Commands (`CodeDocumentor2026/Commands/`)
104+
105+
- `CodeDocumentorContextCommand` -- Solution Explorer right-click on file/folder
106+
- `CodeDocumentorEditorCommand` -- Editor right-click "Document This" for single member
107+
- `CodeDocumentorFileMenu` -- Tools menu "Document File"
108+
109+
### Settings
110+
111+
`OptionPageGrid` in the VSIX reads settings and populates `IBaseSettings`. `ServiceLocator.SettingService` is the runtime access point throughout `Common`.
112+
113+
### Test Project Notes
114+
115+
Tests reference both `CodeDocumentor.Analyzers` and old `CodeDocumentor` projects. New behavior tests should target `CodeDocumentor.Common` directly via `CommentBuilderService`.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,5 @@ Settings.StyleCop
217217
# ==============================================================
218218
# Special Folders/Files that have to be in the repository
219219
# ==============================================================
220-
!protobuf/protobuf/*.user
220+
!protobuf/protobuf/*.user
221+
/bash.exe.stackdump

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dotnet.preferCSharpExtension": true
3+
}

CodeDocumentor.Analyzers/Analyzers/BaseAnalyzerSettings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ public DiagnosticSeverity LookupSeverity(string diagnosticId, ISettings settings
4141
return settings.PropertyDiagnosticSeverity ?? settings.DefaultDiagnosticSeverity;
4242
case Constants.DiagnosticIds.RECORD_DIAGNOSTIC_ID:
4343
return settings.RecordDiagnosticSeverity ?? settings.DefaultDiagnosticSeverity;
44+
default:
45+
return settings.DefaultDiagnosticSeverity;
4446
}
45-
return Constants.DefaultDiagnosticSeverityOnError;
4647
}, diagnosticId, EventLogger, (_) => Constants.DefaultDiagnosticSeverityOnError, eventId: Constants.EventIds.ANALYZER);
4748
}
4849
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Collections.Immutable;
2+
using CodeDocumentor.Analyzers.Builders;
3+
using CodeDocumentor.Common.Locators;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
9+
namespace CodeDocumentor.Analyzers.Analyzers.ConversionOperators
10+
{
11+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
12+
public class ConversionOperatorAnalyzer : DiagnosticAnalyzer
13+
{
14+
private readonly ConversionOperatorAnalyzerSettings _analyzerSettings;
15+
16+
public ConversionOperatorAnalyzer()
17+
{
18+
_analyzerSettings = new ConversionOperatorAnalyzerSettings();
19+
}
20+
21+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
22+
ImmutableArray.Create(_analyzerSettings.GetSupportedDiagnosticRule());
23+
24+
public override void Initialize(AnalysisContext context)
25+
{
26+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27+
context.EnableConcurrentExecution();
28+
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ConversionOperatorDeclaration);
29+
}
30+
31+
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
32+
{
33+
if (!(context.Node is ConversionOperatorDeclarationSyntax node))
34+
{
35+
return;
36+
}
37+
var excludeAnalyzer = ServiceLocator.DocumentationHeaderHelper.HasAnalyzerExclusion(node);
38+
if (excludeAnalyzer)
39+
{
40+
return;
41+
}
42+
var settings = ServiceLocator.SettingService.BuildSettings(context);
43+
context.BuildDiagnostic(node, node.ImplicitOrExplicitKeyword, (alreadyHasComment) => _analyzerSettings.GetRule(alreadyHasComment, settings));
44+
}
45+
}
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using CodeDocumentor.Common;
2+
using CodeDocumentor.Common.Interfaces;
3+
using Microsoft.CodeAnalysis;
4+
5+
namespace CodeDocumentor.Analyzers.Analyzers.ConversionOperators
6+
{
7+
public class ConversionOperatorAnalyzerSettings : BaseAnalyzerSettings
8+
{
9+
public const string DiagnosticId = Constants.DiagnosticIds.CONVERSION_OPERATOR_DIAGNOSTIC_ID;
10+
public const string MessageFormat = Title;
11+
public const string Title = "The conversion operator must have a documentation header.";
12+
13+
public DiagnosticDescriptor GetSupportedDiagnosticRule()
14+
{
15+
return new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, true);
16+
}
17+
18+
public DiagnosticDescriptor GetRule(bool hideDiagnosticSeverity, ISettings settings)
19+
{
20+
return new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category,
21+
hideDiagnosticSeverity ? DiagnosticSeverity.Hidden : LookupSeverity(DiagnosticId, settings), true);
22+
}
23+
}
24+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Collections.Immutable;
2+
using CodeDocumentor.Analyzers.Builders;
3+
using CodeDocumentor.Common.Helper;
4+
using CodeDocumentor.Common.Locators;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
10+
namespace CodeDocumentor.Analyzers.Analyzers.Delegates
11+
{
12+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
13+
public class DelegateAnalyzer : DiagnosticAnalyzer
14+
{
15+
private readonly DelegateAnalyzerSettings _analyzerSettings;
16+
17+
public DelegateAnalyzer()
18+
{
19+
_analyzerSettings = new DelegateAnalyzerSettings();
20+
}
21+
22+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
23+
ImmutableArray.Create(_analyzerSettings.GetSupportedDiagnosticRule());
24+
25+
public override void Initialize(AnalysisContext context)
26+
{
27+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
28+
context.EnableConcurrentExecution();
29+
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.DelegateDeclaration);
30+
}
31+
32+
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
33+
{
34+
if (!(context.Node is DelegateDeclarationSyntax node))
35+
{
36+
return;
37+
}
38+
var settings = ServiceLocator.SettingService.BuildSettings(context);
39+
if (settings.IsEnabledForPublicMembersOnly && PrivateMemberVerifier.IsPrivateMember(node))
40+
{
41+
return;
42+
}
43+
var excludeAnalyzer = ServiceLocator.DocumentationHeaderHelper.HasAnalyzerExclusion(node);
44+
if (excludeAnalyzer)
45+
{
46+
return;
47+
}
48+
context.BuildDiagnostic(node, node.Identifier, (alreadyHasComment) => _analyzerSettings.GetRule(alreadyHasComment, settings));
49+
}
50+
}
51+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using CodeDocumentor.Common;
2+
using CodeDocumentor.Common.Interfaces;
3+
using Microsoft.CodeAnalysis;
4+
5+
namespace CodeDocumentor.Analyzers.Analyzers.Delegates
6+
{
7+
public class DelegateAnalyzerSettings : BaseAnalyzerSettings
8+
{
9+
public const string DiagnosticId = Constants.DiagnosticIds.DELEGATE_DIAGNOSTIC_ID;
10+
public const string MessageFormat = Title;
11+
public const string Title = "The delegate must have a documentation header.";
12+
13+
public DiagnosticDescriptor GetSupportedDiagnosticRule()
14+
{
15+
return new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, true);
16+
}
17+
18+
public DiagnosticDescriptor GetRule(bool hideDiagnosticSeverity, ISettings settings)
19+
{
20+
return new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category,
21+
hideDiagnosticSeverity ? DiagnosticSeverity.Hidden : LookupSeverity(DiagnosticId, settings), true);
22+
}
23+
}
24+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Collections.Immutable;
2+
using CodeDocumentor.Analyzers.Builders;
3+
using CodeDocumentor.Common.Locators;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
9+
namespace CodeDocumentor.Analyzers.Analyzers.Destructors
10+
{
11+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
12+
public class DestructorAnalyzer : DiagnosticAnalyzer
13+
{
14+
private readonly DestructorAnalyzerSettings _analyzerSettings;
15+
16+
public DestructorAnalyzer()
17+
{
18+
_analyzerSettings = new DestructorAnalyzerSettings();
19+
}
20+
21+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
22+
ImmutableArray.Create(_analyzerSettings.GetSupportedDiagnosticRule());
23+
24+
public override void Initialize(AnalysisContext context)
25+
{
26+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27+
context.EnableConcurrentExecution();
28+
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.DestructorDeclaration);
29+
}
30+
31+
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
32+
{
33+
if (!(context.Node is DestructorDeclarationSyntax node))
34+
{
35+
return;
36+
}
37+
var excludeAnalyzer = ServiceLocator.DocumentationHeaderHelper.HasAnalyzerExclusion(node);
38+
if (excludeAnalyzer)
39+
{
40+
return;
41+
}
42+
var settings = ServiceLocator.SettingService.BuildSettings(context);
43+
context.BuildDiagnostic(node, node.Identifier, (alreadyHasComment) => _analyzerSettings.GetRule(alreadyHasComment, settings));
44+
}
45+
}
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using CodeDocumentor.Common;
2+
using CodeDocumentor.Common.Interfaces;
3+
using Microsoft.CodeAnalysis;
4+
5+
namespace CodeDocumentor.Analyzers.Analyzers.Destructors
6+
{
7+
public class DestructorAnalyzerSettings : BaseAnalyzerSettings
8+
{
9+
public const string DiagnosticId = Constants.DiagnosticIds.DESTRUCTOR_DIAGNOSTIC_ID;
10+
public const string MessageFormat = Title;
11+
public const string Title = "The destructor must have a documentation header.";
12+
13+
public DiagnosticDescriptor GetSupportedDiagnosticRule()
14+
{
15+
return new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, true);
16+
}
17+
18+
public DiagnosticDescriptor GetRule(bool hideDiagnosticSeverity, ISettings settings)
19+
{
20+
return new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category,
21+
hideDiagnosticSeverity ? DiagnosticSeverity.Hidden : LookupSeverity(DiagnosticId, settings), true);
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)