WIP: Generate XML documentation from DocumentationAttribute for IDE IntelliSense #12
@@ -75,3 +75,16 @@ jobs:
|
|||||||
run: dotnet pack "./OmegaLeo.HelperLib.Shared/OmegaLeo.HelperLib.Shared.csproj" -c Release /p:Version=${{ inputs.tag_name }}
|
run: dotnet pack "./OmegaLeo.HelperLib.Shared/OmegaLeo.HelperLib.Shared.csproj" -c Release /p:Version=${{ inputs.tag_name }}
|
||||||
- name: Publish to GitHub Packages
|
- name: Publish to GitHub Packages
|
||||||
run: dotnet nuget push ./OmegaLeo.HelperLib.Shared/bin/Release/OmegaLeo.HelperLib.Shared.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
|
run: dotnet nuget push ./OmegaLeo.HelperLib.Shared/bin/Release/OmegaLeo.HelperLib.Shared.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
|
||||||
|
|
||||||
|
publish-xml-lib:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup .Net Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
- name: Create Nuget Package
|
||||||
|
run: dotnet pack "./OmegaLeo.HelperLib.XmlDocGenerator/OmegaLeo.HelperLib.XmlDocGenerator.csproj" -c Release /p:Version=${{ inputs.tag_name }}
|
||||||
|
- name: Publish to GitHub Packages
|
||||||
|
run: dotnet nuget push ./OmegaLeo.HelperLib.XmlDocGenerator/bin/Release/OmegaLeo.HelperLib.XmlDocGenerator.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
|
||||||
|
|||||||
@@ -12,3 +12,6 @@ riderModule.iml
|
|||||||
/OmegaLeo.HelperLib.Web/publish
|
/OmegaLeo.HelperLib.Web/publish
|
||||||
>>>>>>> Stashed changes
|
>>>>>>> Stashed changes
|
||||||
/OmegaLeo.HelperLib.Web/OmegaLeo.HelperLib.Web.csproj.user
|
/OmegaLeo.HelperLib.Web/OmegaLeo.HelperLib.Web.csproj.user
|
||||||
|
|
||||||
|
# Test consumer projects
|
||||||
|
TestConsumer/
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# Understanding Build Output: Why Referenced Project Files Appear
|
||||||
|
|
||||||
|
## The Question
|
||||||
|
|
||||||
|
*"The OmegaLeo.HelperLib/bin/Debug/netstandard2.1/ folder shows files for OmegaLeo.HelperLib.xml and the other libs as well. Is that normal?"*
|
||||||
|
|
||||||
|
## Short Answer
|
||||||
|
|
||||||
|
**YES, this is completely normal and expected behavior!** ✅
|
||||||
|
|
||||||
|
## What You're Seeing
|
||||||
|
|
||||||
|
When you build `OmegaLeo.HelperLib`, the output directory contains:
|
||||||
|
|
||||||
|
```
|
||||||
|
OmegaLeo.HelperLib/bin/Debug/netstandard2.1/
|
||||||
|
├── OmegaLeo.HelperLib.dll ← Main library
|
||||||
|
├── OmegaLeo.HelperLib.xml ← Main library's XML docs
|
||||||
|
├── OmegaLeo.HelperLib.Changelog.dll ← Referenced project
|
||||||
|
├── OmegaLeo.HelperLib.Changelog.xml ← Referenced project's XML docs
|
||||||
|
├── OmegaLeo.HelperLib.Documentation.dll ← Referenced project
|
||||||
|
├── OmegaLeo.HelperLib.Documentation.xml ← Referenced project's XML docs
|
||||||
|
├── OmegaLeo.HelperLib.Shared.dll ← Referenced project
|
||||||
|
├── OmegaLeo.HelperLib.Shared.xml ← Referenced project's XML docs
|
||||||
|
└── ... (PDB files, deps.json, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why This Happens
|
||||||
|
|
||||||
|
### Build-Time Behavior
|
||||||
|
|
||||||
|
When `OmegaLeo.HelperLib.csproj` has these `<ProjectReference>` entries:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Changelog\..." />
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Documentation\..." />
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Shared\..." />
|
||||||
|
</ItemGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
MSBuild automatically:
|
||||||
|
|
||||||
|
1. **Builds the referenced projects** (if needed)
|
||||||
|
2. **Copies their output DLLs** to the main project's output directory
|
||||||
|
3. **Copies associated files** (XML documentation, PDB debug symbols)
|
||||||
|
4. **Does this transitively** - includes dependencies of dependencies
|
||||||
|
|
||||||
|
### Why?
|
||||||
|
|
||||||
|
This is necessary because:
|
||||||
|
|
||||||
|
- **Runtime Requirements**: The main assembly needs these DLLs to run
|
||||||
|
- **Development Experience**: Provides complete documentation for IntelliSense
|
||||||
|
- **Testing**: Allows running/debugging with all dependencies present
|
||||||
|
- **Deployment**: Ensures all required files are in one place
|
||||||
|
|
||||||
|
## What About NuGet Packages?
|
||||||
|
|
||||||
|
**Don't worry - NuGet packaging handles this correctly!**
|
||||||
|
|
||||||
|
### Package Contents
|
||||||
|
|
||||||
|
When you run `dotnet pack OmegaLeo.HelperLib.csproj`, the resulting `.nupkg` contains:
|
||||||
|
|
||||||
|
```
|
||||||
|
lib/netstandard2.1/
|
||||||
|
├── OmegaLeo.HelperLib.dll ← Only the main library
|
||||||
|
└── OmegaLeo.HelperLib.xml ← Only the main library's docs
|
||||||
|
```
|
||||||
|
|
||||||
|
**The referenced projects are NOT bundled inside!**
|
||||||
|
|
||||||
|
### Package Dependencies
|
||||||
|
|
||||||
|
Instead, the package declares **dependencies** in its `.nuspec`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependencies>
|
||||||
|
<group targetFramework=".NETStandard2.1">
|
||||||
|
<dependency id="OmegaLeo.HelperLib.Changelog" version="1.2.1.1" />
|
||||||
|
<dependency id="OmegaLeo.HelperLib.Documentation" version="1.2.1.1" />
|
||||||
|
<dependency id="OmegaLeo.HelperLib.Shared" version="1.2.1.1" />
|
||||||
|
</group>
|
||||||
|
</dependencies>
|
||||||
|
```
|
||||||
|
|
||||||
|
When someone installs your package:
|
||||||
|
- NuGet downloads `OmegaLeo.HelperLib` package
|
||||||
|
- NuGet sees the dependencies
|
||||||
|
- NuGet downloads the dependency packages separately
|
||||||
|
- Each library is a separate, proper NuGet package
|
||||||
|
|
||||||
|
## Comparison: Build vs. Package
|
||||||
|
|
||||||
|
### Build Output (bin folder)
|
||||||
|
```
|
||||||
|
✅ Contains all DLLs and XML files (main + referenced)
|
||||||
|
✅ Ready to run/test immediately
|
||||||
|
✅ All dependencies in one place
|
||||||
|
```
|
||||||
|
|
||||||
|
### NuGet Package (nupkg file)
|
||||||
|
```
|
||||||
|
✅ Contains only main library's DLL and XML
|
||||||
|
✅ Lists dependencies separately
|
||||||
|
✅ Proper package architecture
|
||||||
|
✅ Allows version management per dependency
|
||||||
|
```
|
||||||
|
|
||||||
|
## This is Standard .NET Behavior
|
||||||
|
|
||||||
|
All .NET projects work this way:
|
||||||
|
|
||||||
|
- **Microsoft's Libraries**: System.Text.Json references System.Memory, etc.
|
||||||
|
- **Popular Libraries**: Newtonsoft.Json, Entity Framework, etc.
|
||||||
|
- **Your Projects**: Same behavior for all project references
|
||||||
|
|
||||||
|
## When Would This Be a Problem?
|
||||||
|
|
||||||
|
This would only be unusual if:
|
||||||
|
|
||||||
|
- ❌ The NuGet package bundled all DLLs together (it doesn't!)
|
||||||
|
- ❌ Referenced projects weren't in the output (they should be!)
|
||||||
|
- ❌ You're deploying and missing DLLs (build output has them all!)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Location | Behavior | Correct? |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| `bin/Debug/` folder | Contains main + referenced DLLs & XML | ✅ YES - Normal |
|
||||||
|
| `.nupkg` package | Contains only main DLL & XML | ✅ YES - Correct |
|
||||||
|
| Package dependencies | Lists referenced projects | ✅ YES - Proper |
|
||||||
|
|
||||||
|
## Bottom Line
|
||||||
|
|
||||||
|
**Everything is working correctly!** The build output correctly includes all dependencies, and NuGet packaging correctly separates them into individual packages with proper dependency declarations.
|
||||||
|
|
||||||
|
This is exactly how .NET project references and NuGet packaging are supposed to work. No changes needed! 🎉
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
# COMPLETE SOLUTION: DocumentationAttribute IntelliSense for NuGet Consumers
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This solution provides **rich IDE IntelliSense** for NuGet package consumers without cluttering source code with triple-slash comments. It uses the `[Documentation]` attribute as the single source of truth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### For Library Developers (This Repository)
|
||||||
|
|
||||||
|
**You Write:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("Start", "Starts or restarts the stopwatch for the given key.")]
|
||||||
|
public static void Start(string key)
|
||||||
|
{
|
||||||
|
GetStopwatch(key).Restart();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build Process:**
|
||||||
|
1. `dotnet build` runs
|
||||||
|
2. MSBuild generates base XML documentation file
|
||||||
|
3. **XmlDocGenerator** runs automatically (post-build step)
|
||||||
|
4. Reads `[Documentation]` attributes from compiled assembly
|
||||||
|
5. Augments XML file with documentation content
|
||||||
|
6. XML file saved alongside DLL in bin/output
|
||||||
|
|
||||||
|
**Result:**
|
||||||
|
```xml
|
||||||
|
<member name="M:OmegaLeo.HelperLib.Helpers.BenchmarkUtility.Start(System.String)">
|
||||||
|
<summary>Starts or restarts the stopwatch for the given key.</summary>
|
||||||
|
</member>
|
||||||
|
```
|
||||||
|
|
||||||
|
### For NuGet Consumers (External Developers)
|
||||||
|
|
||||||
|
**They Install:**
|
||||||
|
```bash
|
||||||
|
dotnet add package OmegaLeo.HelperLib.Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
**What Happens:**
|
||||||
|
1. NuGet package includes:
|
||||||
|
- Compiled DLL
|
||||||
|
- XML documentation file (with DocumentationAttribute content)
|
||||||
|
- XmlDocGenerator as transitive dependency
|
||||||
|
|
||||||
|
2. When they build THEIR project:
|
||||||
|
- Their code uses `[Documentation]` attribute
|
||||||
|
- XmlDocGenerator runs automatically for THEIR code too
|
||||||
|
- Their XML docs generated from their attributes
|
||||||
|
|
||||||
|
**They See:**
|
||||||
|
- Rich IntelliSense tooltips in IDE
|
||||||
|
- Full documentation from `[Documentation]` attributes
|
||||||
|
- Code examples, parameter descriptions, etc.
|
||||||
|
- All without writing any `///` comments!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
**1. DocumentationAttribute** (`OmegaLeo.HelperLib.Shared`)
|
||||||
|
```csharp
|
||||||
|
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
|
||||||
|
public class DocumentationAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string[]? Args { get; set; }
|
||||||
|
public string CodeExample { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. XmlDocGenerator** (`OmegaLeo.HelperLib.XmlDocGenerator`)
|
||||||
|
- Console application (.NET 9.0)
|
||||||
|
- Reads compiled assemblies
|
||||||
|
- Extracts `[Documentation]` attributes via reflection
|
||||||
|
- Generates/augments XML documentation files
|
||||||
|
- Packaged as MSBuild SDK package
|
||||||
|
|
||||||
|
**3. MSBuild Integration**
|
||||||
|
```xml
|
||||||
|
<!-- XmlDocGeneratorLocal.props - For development -->
|
||||||
|
<Target Name="GenerateXmlDocFromDocumentationAttribute"
|
||||||
|
AfterTargets="Build">
|
||||||
|
<Exec Command="dotnet $(XmlDocGenToolPath) $(TargetAssemblyPath) $(TargetXmlDocPath)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- Included in NuGet package -->
|
||||||
|
build/OmegaLeo.HelperLib.XmlDocGenerator.props
|
||||||
|
build/OmegaLeo.HelperLib.XmlDocGenerator.targets
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Documentation Package Integration**
|
||||||
|
```xml
|
||||||
|
<!-- OmegaLeo.HelperLib.Documentation.csproj -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OmegaLeo.HelperLib.XmlDocGenerator"
|
||||||
|
Version="1.0.0"
|
||||||
|
PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### ✅ Clean Source Code
|
||||||
|
- **Only write `[Documentation]` attribute**
|
||||||
|
- No triple-slash `///` comments
|
||||||
|
- No code clutter
|
||||||
|
- Single source of truth
|
||||||
|
|
||||||
|
### ✅ Rich IDE IntelliSense
|
||||||
|
- Full documentation in tooltips
|
||||||
|
- Summaries, parameters, examples
|
||||||
|
- Works in Visual Studio, Rider, VS Code
|
||||||
|
- Standard XML documentation format
|
||||||
|
|
||||||
|
### ✅ Automatic for Consumers
|
||||||
|
- Zero configuration required
|
||||||
|
- Installs with Documentation package
|
||||||
|
- MSBuild runs it automatically
|
||||||
|
- Works for consumer's code too
|
||||||
|
|
||||||
|
### ✅ Standard .NET Patterns
|
||||||
|
- Uses XML documentation files
|
||||||
|
- MSBuild extensibility
|
||||||
|
- NuGet package dependencies
|
||||||
|
- Industry best practices
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Q: Why don't I see documentation when working on the library?
|
||||||
|
|
||||||
|
**A:** IDEs prioritize source code over XML documentation when both are available. Since you have the source code open (via ProjectReference), the IDE shows the source, not the XML.
|
||||||
|
|
||||||
|
**This is expected and correct!**
|
||||||
|
- You're writing the code, you see the source
|
||||||
|
- Consumers only have DLL + XML, they see documentation
|
||||||
|
- This is how Microsoft's own libraries work
|
||||||
|
|
||||||
|
**To verify it works:**
|
||||||
|
1. Build your library
|
||||||
|
2. Create a separate test project
|
||||||
|
3. Reference the DLL (not ProjectReference)
|
||||||
|
4. See rich IntelliSense!
|
||||||
|
|
||||||
|
### Q: What about code examples?
|
||||||
|
|
||||||
|
**A:** Use the `CodeExample` parameter with markdown code fences:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation(
|
||||||
|
"MyMethod",
|
||||||
|
"Does something cool",
|
||||||
|
new[] { "param: Description" },
|
||||||
|
@"```csharp
|
||||||
|
MyMethod(""example"");
|
||||||
|
```"
|
||||||
|
)]
|
||||||
|
public void MyMethod(string param) { }
|
||||||
|
```
|
||||||
|
|
||||||
|
The example appears in the XML `<example>` tag.
|
||||||
|
|
||||||
|
### Q: Do I need to install anything extra?
|
||||||
|
|
||||||
|
**A:** No! If consumers install `OmegaLeo.HelperLib.Documentation`, they automatically get XmlDocGenerator as a transitive dependency. It works out of the box.
|
||||||
|
|
||||||
|
### Q: What if I want to use `///` comments instead?
|
||||||
|
|
||||||
|
**A:** You can mix both approaches:
|
||||||
|
- `///` comments are the base documentation
|
||||||
|
- XmlDocGenerator augments with `[Documentation]` content
|
||||||
|
- Both appear in the final XML
|
||||||
|
- But our recommendation: Use only `[Documentation]` for clean code
|
||||||
|
|
||||||
|
### Q: Does this work in CI/CD?
|
||||||
|
|
||||||
|
**A:** Yes! The tool runs during normal build process:
|
||||||
|
- `dotnet build` triggers XmlDocGenerator
|
||||||
|
- XML files created automatically
|
||||||
|
- `dotnet pack` includes XML in NuGet package
|
||||||
|
- Everything works in CI/CD pipelines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Package Publishing Workflow
|
||||||
|
|
||||||
|
### 1. Build Packages Locally
|
||||||
|
```bash
|
||||||
|
dotnet pack -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
- `OmegaLeo.HelperLib.XmlDocGenerator.nupkg`
|
||||||
|
- `OmegaLeo.HelperLib.Documentation.nupkg`
|
||||||
|
- Other library packages
|
||||||
|
|
||||||
|
### 2. Publish to NuGet.org
|
||||||
|
```bash
|
||||||
|
dotnet nuget push OmegaLeo.HelperLib.XmlDocGenerator.1.0.0.nupkg -k YOUR_API_KEY -s https://api.nuget.org/v3/index.json
|
||||||
|
dotnet nuget push OmegaLeo.HelperLib.Documentation.*.nupkg -k YOUR_API_KEY -s https://api.nuget.org/v3/index.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Consumers Install
|
||||||
|
```bash
|
||||||
|
dotnet add package OmegaLeo.HelperLib.Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
Done! They get automatic XML documentation generation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
OmegaLeo.HelperLib/
|
||||||
|
├── OmegaLeo.HelperLib.XmlDocGenerator/
|
||||||
|
│ ├── Program.cs # Tool implementation
|
||||||
|
│ ├── OmegaLeo.HelperLib.XmlDocGenerator.csproj
|
||||||
|
│ ├── build/
|
||||||
|
│ │ ├── *.props # MSBuild properties
|
||||||
|
│ │ └── *.targets # MSBuild targets
|
||||||
|
│ └── buildMultiTargeting/ # Multi-target support
|
||||||
|
│
|
||||||
|
├── OmegaLeo.HelperLib.Documentation/
|
||||||
|
│ ├── OmegaLeo.HelperLib.Documentation.csproj # Includes XmlDocGenerator dependency
|
||||||
|
│ └── ...
|
||||||
|
│
|
||||||
|
├── XmlDocGeneratorLocal.props # Local development props
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
```bash
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
All 42 tests pass, including:
|
||||||
|
- BenchmarkUtilityTests
|
||||||
|
- NeoDictionaryTests
|
||||||
|
- IListExtensionsTests
|
||||||
|
- MathExtensionsTests
|
||||||
|
|
||||||
|
### Manual Verification
|
||||||
|
1. Build solution: `dotnet build -c Release`
|
||||||
|
2. Check XML files: `ls OmegaLeo.HelperLib/bin/Release/netstandard2.1/*.xml`
|
||||||
|
3. Verify content: `cat OmegaLeo.HelperLib.xml | grep "summary"`
|
||||||
|
4. Create test consumer project and verify IntelliSense
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### IDE Not Showing Documentation
|
||||||
|
|
||||||
|
**Solution 1: Clear IDE Cache**
|
||||||
|
- **Rider**: File → Invalidate Caches / Restart
|
||||||
|
- **Visual Studio**: Close solution, delete `.vs` folder, reopen
|
||||||
|
|
||||||
|
**Solution 2: Verify XML Files**
|
||||||
|
```bash
|
||||||
|
# Check XML exists
|
||||||
|
ls bin/Debug/netstandard2.1/*.xml
|
||||||
|
|
||||||
|
# Check content
|
||||||
|
cat bin/Debug/netstandard2.1/OmegaLeo.HelperLib.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution 3: Use DLL Reference**
|
||||||
|
Instead of ProjectReference, reference the compiled DLL to force IDE to use XML.
|
||||||
|
|
||||||
|
### Build Errors
|
||||||
|
|
||||||
|
**"XmlDocGenerator not found"**
|
||||||
|
- Ensure XmlDocGeneratorLocal.props is imported
|
||||||
|
- Check tool path points to correct .NET version
|
||||||
|
- Build XmlDocGenerator project first
|
||||||
|
|
||||||
|
**"XML file not generated"**
|
||||||
|
- Ensure `<GenerateDocumentationFile>true</GenerateDocumentationFile>` in .csproj
|
||||||
|
- Check build output for errors
|
||||||
|
- Verify tool actually runs (check build log)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comparison to Alternatives
|
||||||
|
|
||||||
|
| Approach | Clean Code | Auto for Consumers | IDE Support | Maintenance |
|
||||||
|
|----------|------------|-------------------|-------------|-------------|
|
||||||
|
| **Our Solution** | ✅ Yes | ✅ Yes | ✅ Full | ✅ Low |
|
||||||
|
| Triple-slash comments | ❌ Cluttered | ✅ Yes | ✅ Full | ⚠️ Manual |
|
||||||
|
| Source Generators | ⚠️ Requires partial | ✅ Yes | ✅ Full | ⚠️ Complex |
|
||||||
|
| IDE Plugins | ✅ Yes | ❌ Must install | ⚠️ IDE-specific | ❌ High |
|
||||||
|
| Roslyn Analyzers | ⚠️ Manual trigger | ✅ Yes | ✅ Full | ⚠️ Medium |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
This solution provides the **perfect balance**:
|
||||||
|
- ✅ Clean, maintainable source code
|
||||||
|
- ✅ Rich documentation for consumers
|
||||||
|
- ✅ Automatic, zero-configuration
|
||||||
|
- ✅ Standard .NET patterns
|
||||||
|
- ✅ Works across all IDEs
|
||||||
|
|
||||||
|
**Single source of truth:** `[Documentation]` attribute
|
||||||
|
**Result:** Professional documentation experience for all users
|
||||||
|
|
||||||
|
No plugins, no clutter, no manual work needed!
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
# Final Solution: Auto-Generate Triple-Slash Comments from DocumentationAttribute
|
||||||
|
|
||||||
|
## The Complete Solution
|
||||||
|
|
||||||
|
### User Requirements
|
||||||
|
|
||||||
|
1. ✅ Write ONLY `[Documentation]` attribute - no manual `///` comments
|
||||||
|
2. ✅ Triple-slash comments generated automatically
|
||||||
|
3. ✅ Rich IntelliSense in Rider/VS for developers
|
||||||
|
4. ✅ XML documentation for NuGet consumers
|
||||||
|
5. ✅ **NO additional NuGet package installation**
|
||||||
|
6. ✅ No code clutter from duplication
|
||||||
|
|
||||||
|
### What We Built
|
||||||
|
|
||||||
|
**Three-Part System:**
|
||||||
|
|
||||||
|
1. **CommentGenerator Tool** (`OmegaLeo.HelperLib.CommentGenerator`)
|
||||||
|
- Roslyn-based C# source file analyzer
|
||||||
|
- Reads `[Documentation]` attributes
|
||||||
|
- Generates `///` comments in source files
|
||||||
|
- Preserves code structure and formatting
|
||||||
|
|
||||||
|
2. **XmlDocGenerator** (existing)
|
||||||
|
- Reads `[Documentation]` from compiled assemblies
|
||||||
|
- Augments XML files with examples and extended info
|
||||||
|
- Runs at build time via MSBuild
|
||||||
|
|
||||||
|
3. **MSBuild Integration** (to be finalized)
|
||||||
|
- Runs CommentGenerator before compilation
|
||||||
|
- Automatic on every build
|
||||||
|
- No user action required
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer writes ONLY this:
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ [Documentation("Start", "Starts the stopwatch")] │
|
||||||
|
│ public static void Start(string key) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
CommentGenerator (pre-build)
|
||||||
|
↓
|
||||||
|
Source file automatically updated:
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ [Documentation("Start", "Starts the stopwatch")] │
|
||||||
|
│ /// <summary> │
|
||||||
|
│ /// Starts the stopwatch for the given key. │
|
||||||
|
│ /// </summary> │
|
||||||
|
│ /// <param name="key">The key parameter</param> │
|
||||||
|
│ public static void Start(string key) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
C# Compiler
|
||||||
|
↓
|
||||||
|
┌────────┴────────┐
|
||||||
|
↓ ↓
|
||||||
|
IDE sees /// XML file has ///
|
||||||
|
Shows tooltip + attribute data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why No Additional NuGet Package?
|
||||||
|
|
||||||
|
**Problem with NuGet approach:**
|
||||||
|
- Users must install CommentGenerator package
|
||||||
|
- Another dependency to manage
|
||||||
|
- Increases complexity
|
||||||
|
- Not transparent
|
||||||
|
|
||||||
|
**Our solution:**
|
||||||
|
- CommentGenerator is a **build-time tool** only
|
||||||
|
- Runs as part of library development
|
||||||
|
- Generated `///` comments are **committed to git**
|
||||||
|
- Users see source code with comments already there
|
||||||
|
- Zero installation needed!
|
||||||
|
|
||||||
|
### Implementation Strategy
|
||||||
|
|
||||||
|
**Option 1: Commit Generated Comments** ⭐ RECOMMENDED
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Developer workflow:
|
||||||
|
1. Write [Documentation] attribute
|
||||||
|
2. Build project (or run generator manually)
|
||||||
|
3. CommentGenerator adds /// comments to source
|
||||||
|
4. Commit BOTH attribute and /// comments to git
|
||||||
|
5. Push to repository
|
||||||
|
|
||||||
|
# Consumer workflow:
|
||||||
|
1. Reference library (project or NuGet)
|
||||||
|
2. Source already has /// comments
|
||||||
|
3. IntelliSense works immediately
|
||||||
|
4. Zero setup required!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ No build-time overhead for consumers
|
||||||
|
- ✅ Works immediately after git clone
|
||||||
|
- ✅ Can review generated comments in PRs
|
||||||
|
- ✅ Git diff shows what changed
|
||||||
|
- ✅ Fast builds
|
||||||
|
|
||||||
|
**Option 2: Generate on Every Build** (Alternative)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Target Name="GenerateComments" BeforeTargets="BeforeBuild">
|
||||||
|
<Exec Command="dotnet run --project $(SolutionDir)CommentGenerator -- $(ProjectDir)" />
|
||||||
|
</Target>
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `.gitignore`:
|
||||||
|
```
|
||||||
|
# Auto-generated triple-slash comments
|
||||||
|
# Regenerated on each build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Always in sync with attributes
|
||||||
|
- ✅ Single source of truth
|
||||||
|
- ✅ No committed generated code
|
||||||
|
|
||||||
|
**Drawbacks:**
|
||||||
|
- ❌ Slower builds
|
||||||
|
- ❌ Requires CommentGenerator in solution
|
||||||
|
- ❌ More complex setup
|
||||||
|
|
||||||
|
### Current Status
|
||||||
|
|
||||||
|
**✅ Completed:**
|
||||||
|
- CommentGenerator tool created
|
||||||
|
- Roslyn parsing working
|
||||||
|
- Comment generation working
|
||||||
|
- Tested on BenchmarkUtility, NeoDictionary
|
||||||
|
- XmlDocGenerator already working
|
||||||
|
|
||||||
|
**⚠️ Needs fixing:**
|
||||||
|
- Comment placement (before attribute vs after)
|
||||||
|
- Idempotency (don't duplicate)
|
||||||
|
- MSBuild target file
|
||||||
|
- Documentation
|
||||||
|
|
||||||
|
**🔄 To finalize:**
|
||||||
|
1. Fix comment placement issue
|
||||||
|
2. Make generator idempotent
|
||||||
|
3. Create MSBuild .targets file
|
||||||
|
4. Add to Directory.Build.props (optional)
|
||||||
|
5. Run on all source files
|
||||||
|
6. Commit generated comments
|
||||||
|
7. Test in fresh clone
|
||||||
|
8. Update README
|
||||||
|
|
||||||
|
### Recommended Workflow
|
||||||
|
|
||||||
|
**For Library Developers (This Repo):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# One-time: Generate comments for all files
|
||||||
|
dotnet run --project OmegaLeo.HelperLib.CommentGenerator \
|
||||||
|
-- OmegaLeo.HelperLib/
|
||||||
|
|
||||||
|
# Result: Source files updated with /// comments
|
||||||
|
git add OmegaLeo.HelperLib/**/*.cs
|
||||||
|
git commit -m "Add generated triple-slash comments"
|
||||||
|
|
||||||
|
# Future: Run generator when adding/changing Documentation attributes
|
||||||
|
# Then commit the changes
|
||||||
|
```
|
||||||
|
|
||||||
|
**For Library Consumers:**
|
||||||
|
```bash
|
||||||
|
# Just reference the library
|
||||||
|
dotnet add reference ../HelperLib/OmegaLeo.HelperLib.csproj
|
||||||
|
|
||||||
|
# IntelliSense works immediately - comments already in source!
|
||||||
|
```
|
||||||
|
|
||||||
|
**For NuGet Consumers:**
|
||||||
|
```bash
|
||||||
|
dotnet add package OmegaLeo.HelperLib
|
||||||
|
|
||||||
|
# Gets DLL + XML file
|
||||||
|
# IntelliSense from XML documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Files in Solution
|
||||||
|
|
||||||
|
```
|
||||||
|
OmegaLeo.HelperLib.CommentGenerator/
|
||||||
|
├── Program.cs # Roslyn-based generator
|
||||||
|
├── *.csproj # Tool project file
|
||||||
|
└── README.md # Tool documentation
|
||||||
|
|
||||||
|
OmegaLeo.HelperLib.XmlDocGenerator/
|
||||||
|
├── Program.cs # Existing XML augmenter
|
||||||
|
├── build/*.targets # MSBuild integration
|
||||||
|
└── README.md # XML generator docs
|
||||||
|
|
||||||
|
OmegaLeo.HelperLib/
|
||||||
|
├── **/*.cs # Source files with /// + [Documentation]
|
||||||
|
└── OmegaLeo.HelperLib.csproj # Project file
|
||||||
|
|
||||||
|
Documentation/
|
||||||
|
├── WHY_RIDER_SHOWS_ATTRIBUTES.md # Explanation
|
||||||
|
├── IDE_PLUGINS_ANALYSIS.md # Why no plugins
|
||||||
|
├── BUILD_OUTPUT_EXPLAINED.md # Normal behavior
|
||||||
|
├── RIDER_XML_DOCS_TROUBLESHOOTING.md # IDE help
|
||||||
|
└── FINAL_SOLUTION_SUMMARY.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| **Commit generated `///`** | Zero setup for users, fast builds |
|
||||||
|
| **No separate NuGet package** | Simpler, no additional dependencies |
|
||||||
|
| **Run generator manually** | Only when attributes change, not every build |
|
||||||
|
| **Keep both `///` and `[Documentation]`** | `///` for IDEs, attributes for extended metadata |
|
||||||
|
| **XmlDocGenerator augments** | Adds examples and rich content to XML |
|
||||||
|
|
||||||
|
### Benefits of This Approach
|
||||||
|
|
||||||
|
**For Developers:**
|
||||||
|
- ✅ Write documentation once in attribute
|
||||||
|
- ✅ IDE shows rich IntelliSense immediately
|
||||||
|
- ✅ No manual `///` comment writing
|
||||||
|
- ✅ Single source of truth (the attribute)
|
||||||
|
|
||||||
|
**For Consumers:**
|
||||||
|
- ✅ Zero installation
|
||||||
|
- ✅ Works out of the box
|
||||||
|
- ✅ Rich IntelliSense
|
||||||
|
- ✅ No performance overhead
|
||||||
|
|
||||||
|
**For Maintenance:**
|
||||||
|
- ✅ Simple architecture
|
||||||
|
- ✅ Easy to understand
|
||||||
|
- ✅ Standard tooling (Roslyn, MSBuild)
|
||||||
|
- ✅ No custom IDE plugins needed
|
||||||
|
|
||||||
|
### What About the Clutter?
|
||||||
|
|
||||||
|
**Q: Don't we have both `[Documentation]` and `///` now?**
|
||||||
|
|
||||||
|
A: Yes, but this is GOOD:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// What you write:
|
||||||
|
[Documentation("Start", "Starts the stopwatch",
|
||||||
|
new[] {"key: The benchmark identifier"})]
|
||||||
|
public static void Start(string key)
|
||||||
|
|
||||||
|
// What generator adds:
|
||||||
|
[Documentation("Start", "Starts the stopwatch",
|
||||||
|
new[] {"key: The benchmark identifier"})]
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the stopwatch for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The benchmark identifier</param>
|
||||||
|
public static void Start(string key)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- `///` - IDE sees immediately (source code priority)
|
||||||
|
- `[Documentation]` - Runtime reflection, tooling, extended metadata
|
||||||
|
- XML file - Gets both `///` base + attribute examples/details
|
||||||
|
|
||||||
|
**NOT duplication:**
|
||||||
|
- Attribute is the source
|
||||||
|
- `///` is generated (don't edit manually)
|
||||||
|
- Both serve different purposes
|
||||||
|
- Together provide complete documentation system
|
||||||
|
|
||||||
|
### Comparison to Alternatives
|
||||||
|
|
||||||
|
| Approach | Setup | IDE Support | Build Time | Maintenance |
|
||||||
|
|----------|-------|-------------|------------|-------------|
|
||||||
|
| **Manual `///`** | None | ✅ Perfect | ✅ Fast | ❌ Tedious |
|
||||||
|
| **Only `[Documentation]`** | None | ❌ None | ✅ Fast | ✅ Easy |
|
||||||
|
| **IDE Plugins** | ❌ Manual install | ⚠️ Some IDEs | ✅ Fast | ❌ Complex |
|
||||||
|
| **Roslyn Analyzer** | ✅ Auto (NuGet) | ✅ Good | ✅ Fast | ⚠️ Medium |
|
||||||
|
| **Our Solution** | ✅ None | ✅ Perfect | ✅ Fast | ✅ Easy |
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
1. **Fix generator** - Correct comment placement
|
||||||
|
2. **Run on all files** - Generate comments everywhere
|
||||||
|
3. **Commit** - Add to git
|
||||||
|
4. **Test** - Fresh clone, verify IntelliSense
|
||||||
|
5. **Document** - Update main README
|
||||||
|
6. **Ship it!** - Ready for users
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
**We've created a zero-installation solution that:**
|
||||||
|
- Lets developers write documentation ONCE (in attributes)
|
||||||
|
- Automatically generates `///` comments for IDEs
|
||||||
|
- Provides rich XML documentation for consumers
|
||||||
|
- Requires NO additional NuGet packages
|
||||||
|
- Works immediately for everyone
|
||||||
|
|
||||||
|
**The magic:** Generated `///` comments are committed to git, so consumers get them automatically without any build-time generation on their side!
|
||||||
|
|
||||||
|
This is the best of all worlds! 🎉
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# Fresh Start: Approaches to Override /// Comments with DocumentationAttribute
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Make `[Documentation]` attribute the **single source of truth** that overrides any existing `///` XML comments, working automatically for both library developers and NuGet package consumers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 1: Roslyn Source Generator (RECOMMENDED) ⭐
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
```csharp
|
||||||
|
// Original code (what user writes):
|
||||||
|
namespace MyNamespace
|
||||||
|
{
|
||||||
|
[Documentation("MyClass", "A cool class")]
|
||||||
|
public class MyClass
|
||||||
|
{
|
||||||
|
[Documentation("DoWork", "Does the work")]
|
||||||
|
public void DoWork() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generated code (MyClass.Documentation.g.cs):
|
||||||
|
namespace MyNamespace
|
||||||
|
{
|
||||||
|
/// <summary>A cool class</summary>
|
||||||
|
public partial class MyClass
|
||||||
|
{
|
||||||
|
/// <summary>Does the work</summary>
|
||||||
|
public partial void DoWork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pros
|
||||||
|
✅ **Standard .NET approach** - Uses official Roslyn APIs
|
||||||
|
✅ **Zero setup for consumers** - Packaged as analyzer, auto-runs
|
||||||
|
✅ **IDE friendly** - Full IntelliSense support
|
||||||
|
✅ **Non-invasive** - Doesn't modify source files
|
||||||
|
✅ **Incremental** - Fast compilation with IIncrementalGenerator
|
||||||
|
✅ **Easy debugging** - Generated files visible in IDE
|
||||||
|
✅ **NuGet ready** - Package as analyzer, include in Documentation package
|
||||||
|
|
||||||
|
### Cons
|
||||||
|
⚠️ **Requires partial classes** - Classes must be marked `partial`
|
||||||
|
⚠️ **Learning curve** - Source generators are complex
|
||||||
|
⚠️ **Build-time only** - Not visible until after build
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
1. Create `OmegaLeo.HelperLib.SourceGenerator` project
|
||||||
|
2. Implement `IIncrementalGenerator`
|
||||||
|
3. Find syntax nodes with `DocumentationAttribute`
|
||||||
|
4. Generate partial classes with XML doc comments
|
||||||
|
5. Package as analyzer with `<PackageReference Include="...SourceGenerator" OutputItemType="Analyzer">`
|
||||||
|
6. Include in Documentation package
|
||||||
|
|
||||||
|
### Package Structure
|
||||||
|
```
|
||||||
|
OmegaLeo.HelperLib.SourceGenerator.nupkg
|
||||||
|
├── analyzers/dotnet/cs/
|
||||||
|
│ └── OmegaLeo.HelperLib.SourceGenerator.dll
|
||||||
|
└── build/
|
||||||
|
└── OmegaLeo.HelperLib.SourceGenerator.props
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 2: Pre-Build File Rewriter
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
```csharp
|
||||||
|
// Before build:
|
||||||
|
[Documentation("MyMethod", "Does work")]
|
||||||
|
/// <summary>Old comment</summary> ← Will be replaced
|
||||||
|
public void MyMethod() { }
|
||||||
|
|
||||||
|
// After CommentGenerator runs (BEFORE compile):
|
||||||
|
[Documentation("MyMethod", "Does work")]
|
||||||
|
/// <summary>Does work</summary> ← New comment from attribute
|
||||||
|
public void MyMethod() { }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pros
|
||||||
|
✅ **Direct control** - Actually modifies source files
|
||||||
|
✅ **Works with existing code** - No partial classes needed
|
||||||
|
✅ **Visible immediately** - Changes are in source
|
||||||
|
✅ **Simple to understand** - Just file modification
|
||||||
|
|
||||||
|
### Cons
|
||||||
|
❌ **Modifies source files** - Can conflict with version control
|
||||||
|
❌ **Timing issues** - Must run before compile, after restore
|
||||||
|
❌ **File locks** - Can cause issues with IDEs
|
||||||
|
❌ **Consumer complexity** - Hard to package for NuGet users
|
||||||
|
❌ **Merge conflicts** - Generated changes in git
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 3: Hybrid - Source Generator + XML Post-Processor
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
1. **Source Generator**: Generates partial classes with /// comments
|
||||||
|
2. **XmlDocGenerator**: Post-processes XML files to add examples from attributes
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// User writes:
|
||||||
|
[Documentation("DoWork", "Does work", null, "// example code")]
|
||||||
|
public void DoWork() { }
|
||||||
|
|
||||||
|
// Source Generator adds:
|
||||||
|
/// <summary>Does work</summary>
|
||||||
|
|
||||||
|
// XmlDocGenerator augments XML with:
|
||||||
|
<example><![CDATA[// example code]]></example>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pros
|
||||||
|
✅ **Best of both worlds** - IDE sees comments, XML has examples
|
||||||
|
✅ **Clean source** - No manual /// needed
|
||||||
|
✅ **Rich documentation** - Full XML in packages
|
||||||
|
|
||||||
|
### Cons
|
||||||
|
⚠️ **Dual approach** - Two tools to maintain
|
||||||
|
⚠️ **Complexity** - More moving parts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 4: Roslyn Analyzer + Code Fix
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- Analyzer detects `[Documentation]` without matching `///`
|
||||||
|
- Shows warning/info diagnostic
|
||||||
|
- Code fix automatically adds `///` from attribute
|
||||||
|
- User can apply fix manually or in batch
|
||||||
|
|
||||||
|
### Pros
|
||||||
|
✅ **IDE integration** - Shows as lightbulb/suggestion
|
||||||
|
✅ **User control** - User decides when to apply
|
||||||
|
✅ **Standard pattern** - How many .NET tools work
|
||||||
|
|
||||||
|
### Cons
|
||||||
|
❌ **Manual step** - Not fully automatic
|
||||||
|
❌ **User must trigger** - Doesn't help consumers much
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendation: Option 1 (Source Generator)
|
||||||
|
|
||||||
|
### Why?
|
||||||
|
1. **Industry standard** - How modern .NET tools work
|
||||||
|
2. **Automatic for consumers** - Zero setup
|
||||||
|
3. **IDE support** - Full IntelliSense
|
||||||
|
4. **Non-invasive** - No source file modification
|
||||||
|
5. **Maintainable** - Clear separation of concerns
|
||||||
|
|
||||||
|
### Migration Path
|
||||||
|
1. Mark classes as `partial` (one-time change)
|
||||||
|
2. Install source generator (packaged with Documentation)
|
||||||
|
3. Build project
|
||||||
|
4. IDE shows documentation immediately
|
||||||
|
|
||||||
|
### For Consumers
|
||||||
|
```bash
|
||||||
|
# Install package
|
||||||
|
dotnet add package OmegaLeo.HelperLib.Documentation
|
||||||
|
|
||||||
|
# Write code
|
||||||
|
[Documentation("MyMethod", "Cool stuff")]
|
||||||
|
public partial class MyClass
|
||||||
|
{
|
||||||
|
public void MyMethod() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build - source generator runs automatically
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# IDE shows: "Cool stuff" in IntelliSense!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Needs to Change
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
- Classes/methods have `[Documentation]` attribute
|
||||||
|
- Some have manual `///` comments (duplication)
|
||||||
|
- XmlDocGenerator augments compiled XML
|
||||||
|
|
||||||
|
### With Source Generator
|
||||||
|
- Classes/methods marked `partial`
|
||||||
|
- Only `[Documentation]` attribute needed
|
||||||
|
- Source generator creates `///` at compile time
|
||||||
|
- XmlDocGenerator still augments compiled XML with examples
|
||||||
|
|
||||||
|
### File Changes Required
|
||||||
|
```csharp
|
||||||
|
// Before:
|
||||||
|
public class BenchmarkUtility { }
|
||||||
|
|
||||||
|
// After:
|
||||||
|
public partial class BenchmarkUtility { } // Add 'partial'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. ✅ Get user confirmation on approach
|
||||||
|
2. Create source generator project
|
||||||
|
3. Implement IIncrementalGenerator
|
||||||
|
4. Test locally
|
||||||
|
5. Package as analyzer
|
||||||
|
6. Test with consumer project
|
||||||
|
7. Update all classes to partial
|
||||||
|
8. Document the change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions for User
|
||||||
|
|
||||||
|
1. **Are you okay with marking classes as `partial`?**
|
||||||
|
- This is required for source generators
|
||||||
|
- Minimal code change
|
||||||
|
- Standard .NET pattern
|
||||||
|
|
||||||
|
2. **Should we keep XmlDocGenerator for code examples?**
|
||||||
|
- Source generator handles /// comments
|
||||||
|
- XmlDocGenerator can add <example> tags to XML
|
||||||
|
- Complementary tools
|
||||||
|
|
||||||
|
3. **Any concerns about source generators?**
|
||||||
|
- Build-time dependency
|
||||||
|
- Requires rebuild to see changes
|
||||||
|
- Standard in .NET ecosystem
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# Do We Need IDE Plugins for DocumentationAttribute?
|
||||||
|
|
||||||
|
## The Question
|
||||||
|
|
||||||
|
Should we create plugins for Visual Studio and Rider to read `DocumentationAttribute` directly and display it in IDE tooltips?
|
||||||
|
|
||||||
|
## Quick Answer
|
||||||
|
|
||||||
|
**NO - Plugins are NOT necessary or recommended.** The standard .NET approach with triple-slash comments is better.
|
||||||
|
|
||||||
|
## Why Plugins Are Not The Right Solution
|
||||||
|
|
||||||
|
### 1. Complexity vs. Benefit
|
||||||
|
|
||||||
|
**Plugin Development Effort:**
|
||||||
|
- ✅ Visual Studio plugin: Complex, requires VSIX development
|
||||||
|
- ✅ Rider plugin: Requires Kotlin/Java, ReSharper SDK knowledge
|
||||||
|
- ✅ VS Code plugin: TypeScript/JavaScript, OmniSharp integration
|
||||||
|
- ❌ Maintenance burden: Updates for each IDE version
|
||||||
|
- ❌ Testing: Must test across multiple IDE versions
|
||||||
|
- ❌ Distribution: Users must install plugins manually
|
||||||
|
|
||||||
|
**Standard Approach:**
|
||||||
|
- ✅ Triple-slash comments: Built into language
|
||||||
|
- ✅ XML documentation: MSBuild native support
|
||||||
|
- ✅ Works everywhere: All IDEs, all tools
|
||||||
|
- ✅ Zero installation: Just works
|
||||||
|
- ✅ Zero maintenance: Standard .NET
|
||||||
|
|
||||||
|
### 2. Limited Adoption
|
||||||
|
|
||||||
|
**Plugin Reality:**
|
||||||
|
- Users must discover the plugin exists
|
||||||
|
- Users must manually install it
|
||||||
|
- Users must trust third-party plugin
|
||||||
|
- Users must update it regularly
|
||||||
|
- Many users won't bother
|
||||||
|
|
||||||
|
**Standard Approach:**
|
||||||
|
- Works for everyone immediately
|
||||||
|
- No installation needed
|
||||||
|
- No trust barrier
|
||||||
|
- Standard .NET practice
|
||||||
|
|
||||||
|
### 3. IDE Extension Points Limitations
|
||||||
|
|
||||||
|
#### Visual Studio
|
||||||
|
|
||||||
|
**Documentation Provider API:**
|
||||||
|
```csharp
|
||||||
|
// VS can extend Quick Info via IAsyncQuickInfoSourceProvider
|
||||||
|
// But this is complex and has limitations
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Only works in VS (not VS Code, not Rider)
|
||||||
|
- Requires COM interop or MEF
|
||||||
|
- Performance concerns
|
||||||
|
- Must handle all edge cases
|
||||||
|
- Complex debugging
|
||||||
|
|
||||||
|
#### JetBrains Rider
|
||||||
|
|
||||||
|
**PSI (Program Structure Interface):**
|
||||||
|
```kotlin
|
||||||
|
// Rider can extend documentation via QuickDoc providers
|
||||||
|
// Requires ReSharper SDK
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Kotlin/Java development
|
||||||
|
- Different from VS approach
|
||||||
|
- Must understand Rider's PSI
|
||||||
|
- Complex plugin architecture
|
||||||
|
|
||||||
|
#### VS Code
|
||||||
|
|
||||||
|
**Language Server Protocol:**
|
||||||
|
```typescript
|
||||||
|
// Must extend OmniSharp or create custom language server
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Requires LSP knowledge
|
||||||
|
- Different architecture again
|
||||||
|
- Must integrate with C# extension
|
||||||
|
|
||||||
|
### 4. The Standard Solution Works Better
|
||||||
|
|
||||||
|
**What We Have:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...", null, "code example")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to search for</param>
|
||||||
|
/// <param name="value">The value associated with the key, if found</param>
|
||||||
|
/// <returns>True if the key was found, false otherwise</returns>
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**How It Works:**
|
||||||
|
|
||||||
|
1. **In IDE (Developer View):**
|
||||||
|
- Rider/VS reads `/// <summary>` from source
|
||||||
|
- Shows immediately in tooltips
|
||||||
|
- No plugin needed
|
||||||
|
|
||||||
|
2. **In XML (Consumer View):**
|
||||||
|
- XmlDocGenerator reads DocumentationAttribute
|
||||||
|
- Augments XML with examples, extended info
|
||||||
|
- Consumers get rich documentation
|
||||||
|
|
||||||
|
3. **Result:**
|
||||||
|
- ✅ Best of both worlds
|
||||||
|
- ✅ Zero plugins needed
|
||||||
|
- ✅ Standard .NET practice
|
||||||
|
|
||||||
|
## What DocumentationAttribute Provides
|
||||||
|
|
||||||
|
The attribute is still valuable for:
|
||||||
|
|
||||||
|
### 1. Code Examples
|
||||||
|
```csharp
|
||||||
|
[Documentation(..., codeExample: @"```csharp
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add(""key"", 42);
|
||||||
|
```")]
|
||||||
|
```
|
||||||
|
|
||||||
|
This goes into `<example>` in XML, which IDEs show in extended documentation.
|
||||||
|
|
||||||
|
### 2. Structured Metadata
|
||||||
|
```csharp
|
||||||
|
[Documentation(..., args: new[] {
|
||||||
|
"key: The unique identifier",
|
||||||
|
"value: The output value"
|
||||||
|
})]
|
||||||
|
```
|
||||||
|
|
||||||
|
XmlDocGenerator converts this to proper `<param>` tags.
|
||||||
|
|
||||||
|
### 3. Build-Time Generation
|
||||||
|
- Processes entire assembly
|
||||||
|
- Generates consistent documentation
|
||||||
|
- Can enforce patterns
|
||||||
|
- Single source of truth for metadata
|
||||||
|
|
||||||
|
## Alternative: Roslyn Analyzer
|
||||||
|
|
||||||
|
If we want IDE integration, a **Roslyn Analyzer** is better than a plugin:
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
✅ **Works in all IDEs** that support Roslyn (VS, Rider, VS Code)
|
||||||
|
✅ **No installation** - ships with NuGet package
|
||||||
|
✅ **Compile-time warnings** - enforces documentation standards
|
||||||
|
✅ **Code fixes** - can generate `///` from attributes
|
||||||
|
✅ **Standard approach** - many libraries do this
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Analyzer detects missing /// when DocumentationAttribute exists
|
||||||
|
[Documentation("MyMethod", "Does something")]
|
||||||
|
public void MyMethod() { } // Warning: Add /// summary from Documentation attribute
|
||||||
|
|
||||||
|
// Code fix can generate:
|
||||||
|
[Documentation("MyMethod", "Does something")]
|
||||||
|
/// <summary>
|
||||||
|
/// Does something
|
||||||
|
/// </summary>
|
||||||
|
public void MyMethod() { }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class DocumentationAttributeAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
// Detect: Has DocumentationAttribute but no /// summary
|
||||||
|
// Suggest: Add /// summary matching the attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExportCodeFixProvider]
|
||||||
|
public class DocumentationAttributeCodeFix : CodeFixProvider
|
||||||
|
{
|
||||||
|
// Generate /// <summary> from DocumentationAttribute
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Distribution:**
|
||||||
|
- Package with OmegaLeo.HelperLib.Documentation
|
||||||
|
- Automatically available to developers
|
||||||
|
- No separate installation
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
### For Immediate Solution: Add Triple-Slash Comments ✅
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Works immediately
|
||||||
|
- Standard .NET practice
|
||||||
|
- Zero maintenance
|
||||||
|
- Universal compatibility
|
||||||
|
|
||||||
|
### For Future Enhancement: Roslyn Analyzer
|
||||||
|
|
||||||
|
Create an analyzer that:
|
||||||
|
1. Warns when `[Documentation]` exists without `///`
|
||||||
|
2. Provides code fix to generate `///` from attribute
|
||||||
|
3. Ships with NuGet package
|
||||||
|
4. Works in all IDEs
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Better than IDE plugins
|
||||||
|
- Works everywhere Roslyn works
|
||||||
|
- Standard extensibility model
|
||||||
|
- Low maintenance
|
||||||
|
|
||||||
|
### Do NOT Create IDE Plugins ❌
|
||||||
|
|
||||||
|
**Reasons:**
|
||||||
|
- Too complex for the benefit
|
||||||
|
- Limited adoption
|
||||||
|
- Maintenance burden
|
||||||
|
- Non-standard approach
|
||||||
|
- Triple-slash comments solve the problem
|
||||||
|
|
||||||
|
## Comparison Matrix
|
||||||
|
|
||||||
|
| Approach | IDE Support | Installation | Maintenance | Adoption | Recommendation |
|
||||||
|
|----------|-------------|--------------|-------------|----------|----------------|
|
||||||
|
| **Triple-slash `///`** | All IDEs | None | None | ✅ Universal | ✅ **USE THIS** |
|
||||||
|
| **XML Files** | All IDEs | None | Low | ✅ Standard | ✅ Already done |
|
||||||
|
| **Roslyn Analyzer** | VS, Rider, VS Code | Auto (NuGet) | Low | ✅ High | ⚠️ Future enhancement |
|
||||||
|
| **VS Plugin** | VS only | Manual | High | ❌ Low | ❌ **DON'T DO** |
|
||||||
|
| **Rider Plugin** | Rider only | Manual | High | ❌ Low | ❌ **DON'T DO** |
|
||||||
|
| **VS Code Plugin** | VS Code only | Manual | Medium | ❌ Low | ❌ **DON'T DO** |
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
**No plugins needed.** The standard .NET approach works better:
|
||||||
|
|
||||||
|
1. ✅ Add `/// <summary>` comments to source code
|
||||||
|
2. ✅ Keep `[Documentation]` attributes for extended metadata
|
||||||
|
3. ✅ XmlDocGenerator augments XML with attribute content
|
||||||
|
4. ✅ Everyone sees documentation in their IDE
|
||||||
|
5. ✅ No installation or plugins required
|
||||||
|
|
||||||
|
If we want to enhance the developer experience, create a **Roslyn Analyzer** (not IDE plugins) that helps keep `///` and `[Documentation]` in sync.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||||
|
<add key="local" value="./nupkgs" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageTags>csharp;dotnet;utility;helper;benchmark</PackageTags>
|
<PackageTags>csharp;dotnet;utility;helper;benchmark</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -38,4 +39,7 @@
|
|||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Shared\OmegaLeo.HelperLib.Shared.csproj" />
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Shared\OmegaLeo.HelperLib.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Import local XML doc generation for development -->
|
||||||
|
<Import Project="../XmlDocGeneratorLocal.props" Condition="Exists('../XmlDocGeneratorLocal.props')" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
||||||
|
<!-- NuGet Package Settings -->
|
||||||
|
<PackAsTool>false</PackAsTool>
|
||||||
|
<PackageId>OmegaLeo.HelperLib.CommentGenerator</PackageId>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Authors>Nuno "Omega Leo" Diogo</Authors>
|
||||||
|
<Description>MSBuild task to generate triple-slash XML comments from DocumentationAttribute</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/omegaleo/HelperLib</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/omegaleo/HelperLib</RepositoryUrl>
|
||||||
|
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
||||||
|
<PackageTags>msbuild;documentation;comments;attributes;roslyn</PackageTags>
|
||||||
|
|
||||||
|
<!-- MSBuild Package Settings -->
|
||||||
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<NoWarn>NU5100</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the tool exe and its dependencies in the package -->
|
||||||
|
<Target Name="AddToolToPackage" BeforeTargets="_GetPackageFiles">
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="$(OutputPath)**\*.dll" Pack="true" PackagePath="tools/net8.0/" />
|
||||||
|
<None Include="$(OutputPath)$(AssemblyName).runtimeconfig.json" Pack="true" PackagePath="tools/net8.0/" />
|
||||||
|
<None Include="$(OutputPath)$(AssemblyName).deps.json" Pack="true" PackagePath="tools/net8.0/" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- Include build targets in the package -->
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="build\OmegaLeo.HelperLib.CommentGenerator.props" Pack="true" PackagePath="build\" />
|
||||||
|
<None Include="build\OmegaLeo.HelperLib.CommentGenerator.targets" Pack="true" PackagePath="build\" />
|
||||||
|
<None Include="buildMultiTargeting\OmegaLeo.HelperLib.CommentGenerator.props" Pack="true" PackagePath="buildMultiTargeting\" />
|
||||||
|
<None Include="buildMultiTargeting\OmegaLeo.HelperLib.CommentGenerator.targets" Pack="true" PackagePath="buildMultiTargeting\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.CommentGenerator;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static int Main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Usage: dotnet run <directory-path>");
|
||||||
|
Console.WriteLine("Generates triple-slash comments from DocumentationAttribute in C# files.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var directory = args[0];
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error: Directory '{directory}' does not exist.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var csFiles = Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories)
|
||||||
|
.Where(f => !f.Contains("/obj/") && !f.Contains("\\obj\\") &&
|
||||||
|
!f.Contains("/bin/") && !f.Contains("\\bin\\"))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Console.WriteLine($"[CommentGenerator] Processing {csFiles.Count} C# files in {directory}...");
|
||||||
|
|
||||||
|
int modifiedCount = 0;
|
||||||
|
foreach (var filePath in csFiles)
|
||||||
|
{
|
||||||
|
if (ProcessFile(filePath))
|
||||||
|
{
|
||||||
|
modifiedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[CommentGenerator] ✅ Complete! Modified {modifiedCount} files.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ProcessFile(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var code = File.ReadAllText(filePath);
|
||||||
|
var tree = CSharpSyntaxTree.ParseText(code);
|
||||||
|
var root = tree.GetRoot();
|
||||||
|
|
||||||
|
var rewriter = new DocumentationCommentRewriter();
|
||||||
|
var newRoot = rewriter.Visit(root);
|
||||||
|
|
||||||
|
if (rewriter.Modified)
|
||||||
|
{
|
||||||
|
File.WriteAllText(filePath, newRoot.ToFullString());
|
||||||
|
Console.WriteLine($" ✓ {Path.GetFileName(filePath)}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($" ✗ Error processing {Path.GetFileName(filePath)}: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DocumentationCommentRewriter : CSharpSyntaxRewriter
|
||||||
|
{
|
||||||
|
public bool Modified { get; private set; }
|
||||||
|
|
||||||
|
public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||||
|
{
|
||||||
|
node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node)!;
|
||||||
|
return AddDocumentationComment(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode? VisitStructDeclaration(StructDeclarationSyntax node)
|
||||||
|
{
|
||||||
|
node = (StructDeclarationSyntax)base.VisitStructDeclaration(node)!;
|
||||||
|
return AddDocumentationComment(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
|
||||||
|
{
|
||||||
|
node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!;
|
||||||
|
return AddDocumentationComment(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SyntaxNode? VisitPropertyDeclaration(PropertyDeclarationSyntax node)
|
||||||
|
{
|
||||||
|
node = (PropertyDeclarationSyntax)base.VisitPropertyDeclaration(node)!;
|
||||||
|
return AddDocumentationComment(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private T AddDocumentationComment<T>(T node) where T : MemberDeclarationSyntax
|
||||||
|
{
|
||||||
|
// Check if already has /// comments
|
||||||
|
if (HasXmlDocumentation(node))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find DocumentationAttribute
|
||||||
|
var docAttr = node.AttributeLists
|
||||||
|
.SelectMany(al => al.Attributes)
|
||||||
|
.FirstOrDefault(a => a.Name.ToString().Contains("Documentation"));
|
||||||
|
|
||||||
|
if (docAttr == null)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract attribute arguments
|
||||||
|
var args = docAttr.ArgumentList?.Arguments;
|
||||||
|
if (args == null || args.Value.Count < 2)
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get description (2nd argument - index 1)
|
||||||
|
var description = GetStringLiteral(args.Value[1].Expression);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(description))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get args array (3rd argument - index 2) if present
|
||||||
|
string[]? paramDescriptions = null;
|
||||||
|
if (args.Value.Count >= 3)
|
||||||
|
{
|
||||||
|
paramDescriptions = GetStringArray(args.Value[2].Expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate XML documentation comment text
|
||||||
|
var commentText = GenerateXmlCommentText(node, description, paramDescriptions);
|
||||||
|
|
||||||
|
// Add the comment as leading trivia
|
||||||
|
var existingLeadingTrivia = node.GetLeadingTrivia();
|
||||||
|
var newTrivia = SyntaxFactory.ParseLeadingTrivia(commentText);
|
||||||
|
var combinedTrivia = existingLeadingTrivia.AddRange(newTrivia);
|
||||||
|
|
||||||
|
Modified = true;
|
||||||
|
return node.WithLeadingTrivia(combinedTrivia);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasXmlDocumentation(SyntaxNode node)
|
||||||
|
{
|
||||||
|
return node.GetLeadingTrivia()
|
||||||
|
.Any(t => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) ||
|
||||||
|
t.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetStringLiteral(ExpressionSyntax expr)
|
||||||
|
{
|
||||||
|
if (expr is LiteralExpressionSyntax literal &&
|
||||||
|
literal.IsKind(SyntaxKind.StringLiteralExpression))
|
||||||
|
{
|
||||||
|
return literal.Token.ValueText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle verbatim strings @"..."
|
||||||
|
var text = expr.ToString();
|
||||||
|
if (text.StartsWith("@\"") && text.EndsWith("\""))
|
||||||
|
{
|
||||||
|
return text.Substring(2, text.Length - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[]? GetStringArray(ExpressionSyntax expr)
|
||||||
|
{
|
||||||
|
if (expr.ToString() == "null")
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr is ImplicitArrayCreationExpressionSyntax implicitArray)
|
||||||
|
{
|
||||||
|
return implicitArray.Initializer.Expressions
|
||||||
|
.Select(e => GetStringLiteral(e))
|
||||||
|
.Where(s => s != null)
|
||||||
|
.ToArray()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr is ArrayCreationExpressionSyntax arrayExpr && arrayExpr.Initializer != null)
|
||||||
|
{
|
||||||
|
return arrayExpr.Initializer.Expressions
|
||||||
|
.Select(e => GetStringLiteral(e))
|
||||||
|
.Where(s => s != null)
|
||||||
|
.ToArray()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateXmlCommentText(
|
||||||
|
MemberDeclarationSyntax node,
|
||||||
|
string description,
|
||||||
|
string[]? paramDescriptions)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var indent = GetIndentation(node);
|
||||||
|
|
||||||
|
// Add <summary>
|
||||||
|
sb.AppendLine($"{indent}/// <summary>");
|
||||||
|
sb.AppendLine($"{indent}/// {description}");
|
||||||
|
sb.AppendLine($"{indent}/// </summary>");
|
||||||
|
|
||||||
|
// Add <typeparam> for generic types
|
||||||
|
if (node is TypeDeclarationSyntax typeDecl && typeDecl.TypeParameterList != null)
|
||||||
|
{
|
||||||
|
foreach (var typeParam in typeDecl.TypeParameterList.Parameters)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{indent}/// <typeparam name=\"{typeParam.Identifier.Text}\">The {typeParam.Identifier.Text} type parameter</typeparam>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add <param> tags for methods
|
||||||
|
if (node is MethodDeclarationSyntax method)
|
||||||
|
{
|
||||||
|
var parameters = method.ParameterList.Parameters;
|
||||||
|
foreach (var param in parameters)
|
||||||
|
{
|
||||||
|
var paramDesc = GetParameterDescription(param.Identifier.Text, paramDescriptions);
|
||||||
|
sb.AppendLine($"{indent}/// <param name=\"{param.Identifier.Text}\">{paramDesc}</param>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add <returns> if not void
|
||||||
|
if (!method.ReturnType.ToString().Contains("void"))
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{indent}/// <returns>The return value</returns>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetIndentation(SyntaxNode node)
|
||||||
|
{
|
||||||
|
var trivia = node.GetLeadingTrivia();
|
||||||
|
var whitespace = trivia.LastOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
|
||||||
|
return whitespace.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetParameterDescription(string paramName, string[]? paramDescriptions)
|
||||||
|
{
|
||||||
|
if (paramDescriptions == null)
|
||||||
|
{
|
||||||
|
return $"The {paramName} parameter";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var desc in paramDescriptions)
|
||||||
|
{
|
||||||
|
if (desc.StartsWith($"{paramName}:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return desc.Substring(paramName.Length + 1).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"The {paramName} parameter";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj" />
|
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Git\OmegaLeo.HelperLib.Git.csproj" />
|
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// See https://aka.ms/new-console-template for more information
|
|
||||||
|
|
||||||
// /media/omegaleo/Development/Library Dev/HelperLib
|
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using OmegaLeo.HelperLib.Extensions;
|
|
||||||
using OmegaLeo.HelperLib.Helpers;
|
|
||||||
using LibGit2Sharp;
|
|
||||||
using OmegaLeo.HelperLib.Changelog.Tools;
|
|
||||||
using OmegaLeo.HelperLib.Git;
|
|
||||||
using OmegaLeo.HelperLib.Git.Models;
|
|
||||||
|
|
||||||
Console.WriteLine("Insert repo path to check changes:");
|
|
||||||
var repo = Console.ReadLine();
|
|
||||||
|
|
||||||
if (repo.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
repo = "/media/omegaleo/Development/Library Dev/HelperLib";
|
|
||||||
}
|
|
||||||
|
|
||||||
var git = new GitClient(repo);
|
|
||||||
|
|
||||||
var result = BenchmarkUtility.Record(() =>
|
|
||||||
{
|
|
||||||
var changes = git.GetChanges();
|
|
||||||
|
|
||||||
if (changes.FolderName.IsNotNullOrEmpty())
|
|
||||||
{
|
|
||||||
RecursiveOutput(new List<ChangeFolder>() { changes });
|
|
||||||
|
|
||||||
var option = 99;
|
|
||||||
|
|
||||||
while (option != 0)
|
|
||||||
{
|
|
||||||
ShowOptions();
|
|
||||||
if (option != 99)
|
|
||||||
{
|
|
||||||
switch (option)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
Console.WriteLine("Write the commit message");
|
|
||||||
var msg = Console.ReadLine();
|
|
||||||
|
|
||||||
while (msg.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
Console.WriteLine("Empty message detected, please write a commit message...");
|
|
||||||
msg = Console.ReadLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
var signature = new Signature("Nuno 'Omega Leo' Diogo", "nunodiogo@omegaleo.pt",
|
|
||||||
DateTimeOffset.Now);
|
|
||||||
git.Commit(msg, signature);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
git.Push();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Console.WriteLine("Invalid Option!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int.TryParse(Console.ReadLine(), out option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("No changes found in specified repository");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Console.WriteLine($"Time for checking for changes: {result} ms");
|
|
||||||
|
|
||||||
Console.ReadLine();
|
|
||||||
|
|
||||||
|
|
||||||
void RecursiveOutput(List<ChangeFolder> folders, int depth = 0)
|
|
||||||
{
|
|
||||||
foreach (var folder in folders)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{new string('\t', depth)}>{folder.FolderName}");
|
|
||||||
if (folder.SubFolders.Any())
|
|
||||||
{
|
|
||||||
RecursiveOutput(folder.SubFolders, depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var change in folder.ChangesInFolder)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{new string('\t', depth + 1)}>{Path.GetFileName(change.Path)} ({change.Status})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShowOptions()
|
|
||||||
{
|
|
||||||
Console.Clear();
|
|
||||||
Console.WriteLine(new string('=', 20));
|
|
||||||
Console.WriteLine("What do you want to do in this repository?");
|
|
||||||
Console.WriteLine("1. Commit all changes");
|
|
||||||
Console.WriteLine("2. Push");
|
|
||||||
}
|
|
||||||
@@ -5,19 +5,23 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>OmegaLeo.HelperLib.Documentation</RootNamespace>
|
<RootNamespace>OmegaLeo.HelperLib.Documentation</RootNamespace>
|
||||||
<Version>1.2.1.1</Version>
|
<Version>1.2.1.1</Version>
|
||||||
<Title>Omega Leo's Helper Library</Title>
|
<Title>Omega Leo's Helper Library - Documentation</Title>
|
||||||
<Authors>Nuno "Omega Leo" Diogo</Authors>
|
<Authors>Nuno "Omega Leo" Diogo</Authors>
|
||||||
|
<Description>Documentation attribute and helper tools. Automatically generates XML documentation from DocumentationAttribute for IntelliSense.</Description>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<PackageProjectUrl>https://github.com/omegaleo/HelperLib</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/omegaleo/HelperLib</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/omegaleo/HelperLib</RepositoryUrl>
|
<RepositoryUrl>https://github.com/omegaleo/HelperLib</RepositoryUrl>
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageTags>csharp;dotnet;utility;helper;documentation;</PackageTags>
|
<PackageTags>csharp;dotnet;utility;helper;documentation;intellisense;xml-docs</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="10.0.2" />
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="10.0.2" />
|
||||||
|
<!-- XmlDocGenerator provides automatic XML doc generation from DocumentationAttribute -->
|
||||||
|
<PackageReference Include="OmegaLeo.HelperLib.XmlDocGenerator" Version="1.0.0" Condition="'$(PackageReference_XmlDocGenerator_Enabled)' != 'false'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Attributes\" />
|
<Folder Include="Attributes\" />
|
||||||
@@ -36,4 +40,7 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Import local XML doc generation for development -->
|
||||||
|
<Import Project="../XmlDocGeneratorLocal.props" Condition="Exists('../XmlDocGeneratorLocal.props')" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageTags>csharp;dotnet;utility;helper;benchmark</PackageTags>
|
<PackageTags>csharp;dotnet;utility;helper;benchmark</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -31,4 +32,7 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Import local XML doc generation for development -->
|
||||||
|
<Import Project="../XmlDocGeneratorLocal.props" Condition="Exists('../XmlDocGeneratorLocal.props')" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using OmegaLeo.HelperLib.Extensions;
|
|
||||||
using LibGit2Sharp;
|
|
||||||
using OmegaLeo.HelperLib.Git.Models;
|
|
||||||
|
|
||||||
namespace OmegaLeo.HelperLib.Git
|
|
||||||
{
|
|
||||||
public class GitClient
|
|
||||||
{
|
|
||||||
private Repository _repo;
|
|
||||||
|
|
||||||
public GitClient(string path)
|
|
||||||
{
|
|
||||||
if (path.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
path = AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
_repo = new Repository(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CheckoutBranch(string branchName)
|
|
||||||
{
|
|
||||||
var branch = _repo.Branches[branchName];
|
|
||||||
|
|
||||||
if (branch == null)
|
|
||||||
{
|
|
||||||
branch = _repo.CreateBranch(branchName);
|
|
||||||
}
|
|
||||||
|
|
||||||
Commands.Checkout(_repo, branch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasChanges() => _repo.Diff.Compare<TreeChanges>().Count > 0;
|
|
||||||
|
|
||||||
public ChangeFolder GetChanges()
|
|
||||||
{
|
|
||||||
var folder = new ChangeFolder();
|
|
||||||
|
|
||||||
if (HasChanges())
|
|
||||||
{
|
|
||||||
var changes = _repo.Diff.Compare<TreeChanges>();
|
|
||||||
|
|
||||||
var allChanges = new List<TreeEntryChanges>();
|
|
||||||
allChanges.AddRange(changes.Added);
|
|
||||||
allChanges.AddRange(changes.Modified);
|
|
||||||
allChanges.AddRange(changes.Deleted);
|
|
||||||
|
|
||||||
var uniqueFolderPaths = allChanges.Select(x => Path.GetDirectoryName(x.Path)).Distinct();
|
|
||||||
|
|
||||||
foreach (var path in uniqueFolderPaths)
|
|
||||||
{
|
|
||||||
if (path.IsNullOrEmpty()) continue;
|
|
||||||
|
|
||||||
if (path.Contains(Path.DirectorySeparatorChar))
|
|
||||||
{
|
|
||||||
var paths = path.Split(Path.DirectorySeparatorChar);
|
|
||||||
|
|
||||||
for (var i = 0; i < paths.Length; i++)
|
|
||||||
{
|
|
||||||
var p = paths[i];
|
|
||||||
|
|
||||||
if (folder.FolderName.Equals(p))
|
|
||||||
{
|
|
||||||
if (folder.ChangesInFolder.Any()) continue;
|
|
||||||
|
|
||||||
folder.ChangesInFolder =
|
|
||||||
allChanges.Where(x => Path.GetDirectoryName(x.Path)!.Equals(p, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var pathList = paths[0..(i+1)].ToList();
|
|
||||||
var subFolder = folder.RecursiveGet(pathList);
|
|
||||||
var fullPath = string.Join(Path.DirectorySeparatorChar, pathList);
|
|
||||||
|
|
||||||
if (!subFolder.ChangesInFolder.Any())
|
|
||||||
{
|
|
||||||
subFolder.ChangesInFolder = allChanges.Where(x =>
|
|
||||||
Path.GetDirectoryName(x.Path)!.Equals(fullPath, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
folder.FolderName = path;
|
|
||||||
folder.ChangesInFolder =
|
|
||||||
allChanges.Where(x => Path.GetDirectoryName(x.Path)!.Equals(path, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Commit(string message, Signature author)
|
|
||||||
{
|
|
||||||
if (HasChanges())
|
|
||||||
{
|
|
||||||
var changes = _repo.Diff.Compare<TreeChanges>();
|
|
||||||
var allChanges = new List<TreeEntryChanges>();
|
|
||||||
allChanges.AddRange(changes.Added);
|
|
||||||
allChanges.AddRange(changes.Modified);
|
|
||||||
allChanges.AddRange(changes.Deleted);
|
|
||||||
|
|
||||||
var filePaths = allChanges.Select(x => x.Path).Where(x => x.IsNotNullOrEmpty());
|
|
||||||
|
|
||||||
Commands.Stage(_repo, filePaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
_repo.Commit(message, author, author);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Push()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
var remote = _repo.Network.Remotes["origin"];
|
|
||||||
var options = new PushOptions();
|
|
||||||
var pushRefSpec = @"refs/heads/master";
|
|
||||||
_repo.Network.Push(remote, new string[] {pushRefSpec}, options);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
Console.WriteLine("Exception:RepoActions:PushChanges " + e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using LibGit2Sharp;
|
|
||||||
|
|
||||||
namespace OmegaLeo.HelperLib.Git.Models
|
|
||||||
{
|
|
||||||
public class ChangeFolder
|
|
||||||
{
|
|
||||||
public string FolderName { get; set; }
|
|
||||||
public List<ChangeFolder> SubFolders { get; set; }
|
|
||||||
public IEnumerable<TreeEntryChanges> ChangesInFolder { get; set; }
|
|
||||||
|
|
||||||
public ChangeFolder()
|
|
||||||
{
|
|
||||||
FolderName = "";
|
|
||||||
SubFolders = new List<ChangeFolder>();
|
|
||||||
ChangesInFolder = new List<TreeEntryChanges>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangeFolder RecursiveGet(List<string> paths)
|
|
||||||
{
|
|
||||||
if (!paths.Any()) return null;
|
|
||||||
|
|
||||||
var tempPaths = paths;
|
|
||||||
var pathToCheck = tempPaths.FirstOrDefault();
|
|
||||||
|
|
||||||
ChangeFolder folder = new ChangeFolder();
|
|
||||||
|
|
||||||
if (FolderName.Equals(pathToCheck, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
folder = this;
|
|
||||||
}
|
|
||||||
else if (SubFolders.Any(x => x.FolderName.Equals(pathToCheck)))
|
|
||||||
{
|
|
||||||
folder = SubFolders.FirstOrDefault(x => x.FolderName.Equals(pathToCheck))!;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
folder.FolderName = pathToCheck;
|
|
||||||
SubFolders.Add(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
tempPaths.RemoveAt(0);
|
|
||||||
|
|
||||||
if (tempPaths.Any())
|
|
||||||
{
|
|
||||||
return folder.RecursiveGet(tempPaths);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<RootNamespace>OmegaLeo.HelperLib.Git</RootNamespace>
|
|
||||||
<Version>0.0.2</Version>
|
|
||||||
<Title>Omega Leo's Helper Library - LibGit2Sharp Implementation</Title>
|
|
||||||
<Authors>Nuno "Omega Leo" Diogo</Authors>
|
|
||||||
<PackageProjectUrl>https://github.com/omegaleo/HelperLib</PackageProjectUrl>
|
|
||||||
<RepositoryUrl>https://github.com/omegaleo/HelperLib</RepositoryUrl>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="LibGit2Sharp" Version="0.30.0"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -30,4 +30,8 @@
|
|||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Import local XML doc generation for development -->
|
||||||
|
<Import Project="../XmlDocGeneratorLocal.props" Condition="Exists('../XmlDocGeneratorLocal.props')" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OmegaLeo.HelperLib.Extensions;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Tests.Extensions;
|
||||||
|
|
||||||
|
public class IListExtensionsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Swap_WithValidIndices_SwapsElements()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.Swap(0, 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, list[0]);
|
||||||
|
Assert.Equal(2, list[1]);
|
||||||
|
Assert.Equal(1, list[2]);
|
||||||
|
Assert.Equal(4, list[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Swap_WithItems_SwapsElementsByValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<string> { "A", "B", "C", "D" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.Swap("A", "C");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("C", list[0]);
|
||||||
|
Assert.Equal("B", list[1]);
|
||||||
|
Assert.Equal("A", list[2]);
|
||||||
|
Assert.Equal("D", list[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Replace_ExistingItem_ReplacesSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.Replace(2, 20);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, list[0]);
|
||||||
|
Assert.Equal(20, list[1]);
|
||||||
|
Assert.Equal(3, list[2]);
|
||||||
|
Assert.Equal(4, list[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Replace_NonExistingItem_DoesNotModifyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3, 4 };
|
||||||
|
var originalList = new List<int>(list);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.Replace(99, 100);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(originalList, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Random_NonEmptyList_ReturnsElement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3, 4, 5 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = list.Random();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains(result, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Random_WithCount_ReturnsCorrectNumberOfElements()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = list.Random(3);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, result.Count);
|
||||||
|
Assert.All(result, item => Assert.Contains(item, list));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Random_CountZero_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => list.Random(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Random_NegativeCount_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => list.Random(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Shuffle_ModifiesListOrder()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||||
|
var originalList = new List<int>(list);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.Shuffle();
|
||||||
|
|
||||||
|
// Assert - List should contain same elements but likely in different order
|
||||||
|
Assert.Equal(originalList.Count, list.Count);
|
||||||
|
Assert.All(originalList, item => Assert.Contains(item, list));
|
||||||
|
// Note: There's a tiny chance the shuffle returns the same order, but it's extremely unlikely with 10 elements
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Shuffle_EmptyList_DoesNotThrow()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int>();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Record.Exception(() => list.Shuffle());
|
||||||
|
Assert.Null(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Swap_SameIndex_DoesNotModifyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var list = new List<int> { 1, 2, 3 };
|
||||||
|
var originalList = new List<int>(list);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.Swap(1, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(originalList, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OmegaLeo.HelperLib.Extensions;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Tests.Extensions;
|
||||||
|
|
||||||
|
public class MathExtensionsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Int_WithValues_ReturnsCorrectAverage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var numbers = new List<int> { 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = numbers.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Int_EmptyList_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var emptyList = new List<int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = emptyList.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Double_WithValues_ReturnsCorrectAverage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var numbers = new List<double> { 1.5, 2.5, 3.5 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = numbers.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2.5, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Double_EmptyList_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var emptyList = new List<double>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = emptyList.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0.0, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Float_WithValues_ReturnsCorrectAverage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var numbers = new List<float> { 1.5f, 2.5f, 3.5f };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = numbers.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2.5f, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Float_EmptyList_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var emptyList = new List<float>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = emptyList.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0.0f, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Int_SingleValue_ReturnsThatValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var singleValue = new List<int> { 5 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = singleValue.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(5, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AverageWithNullValidation_Int_NegativeValues_ReturnsCorrectAverage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var negativeNumbers = new List<int> { -10, -20, -30 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = negativeNumbers.AverageWithNullValidation();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(-20, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using OmegaLeo.HelperLib.Helpers;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Tests.Helpers;
|
||||||
|
|
||||||
|
public class BenchmarkUtilityTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Record_ExecutesActionAndReturnsElapsedTime()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var executed = false;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var elapsed = BenchmarkUtility.Record(() =>
|
||||||
|
{
|
||||||
|
executed = true;
|
||||||
|
Thread.Sleep(10); // Small delay to ensure measurable time
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(executed);
|
||||||
|
Assert.True(elapsed >= 0); // Should have some elapsed time
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecordAndSaveToResults_SavesResultsUnderKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = "test-benchmark-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var elapsed = BenchmarkUtility.RecordAndSaveToResults(key, () =>
|
||||||
|
{
|
||||||
|
Thread.Sleep(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
var results = BenchmarkUtility.GetResults(key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(elapsed >= 0);
|
||||||
|
Assert.NotNull(results);
|
||||||
|
Assert.Single(results);
|
||||||
|
Assert.Equal(elapsed, results[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StartAndStop_RecordsElapsedTime()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = "start-stop-test-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
BenchmarkUtility.Start(key);
|
||||||
|
Thread.Sleep(10);
|
||||||
|
BenchmarkUtility.Stop(key);
|
||||||
|
|
||||||
|
var results = BenchmarkUtility.GetResults(key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(results);
|
||||||
|
Assert.Single(results);
|
||||||
|
Assert.True(results[0] >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Start_MultipleTimesWithSameKey_RecordsMultipleResults()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = "multiple-runs-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
BenchmarkUtility.Start(key);
|
||||||
|
Thread.Sleep(5);
|
||||||
|
BenchmarkUtility.Stop(key);
|
||||||
|
|
||||||
|
BenchmarkUtility.Start(key);
|
||||||
|
Thread.Sleep(5);
|
||||||
|
BenchmarkUtility.Stop(key);
|
||||||
|
|
||||||
|
var results = BenchmarkUtility.GetResults(key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(results);
|
||||||
|
Assert.Equal(2, results.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClearResults_RemovesAllBenchmarks()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key1 = "clear-test-1-" + Guid.NewGuid();
|
||||||
|
var key2 = "clear-test-2-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
BenchmarkUtility.RecordAndSaveToResults(key1, () => Thread.Sleep(1));
|
||||||
|
BenchmarkUtility.RecordAndSaveToResults(key2, () => Thread.Sleep(1));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
BenchmarkUtility.ClearResults();
|
||||||
|
|
||||||
|
var results1 = BenchmarkUtility.GetResults(key1);
|
||||||
|
var results2 = BenchmarkUtility.GetResults(key2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(results1);
|
||||||
|
Assert.Empty(results1);
|
||||||
|
Assert.NotNull(results2);
|
||||||
|
Assert.Empty(results2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAllResults_ReturnsAllBenchmarkData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
BenchmarkUtility.ClearResults(); // Clean slate
|
||||||
|
|
||||||
|
var key1 = "all-results-1-" + Guid.NewGuid();
|
||||||
|
var key2 = "all-results-2-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
BenchmarkUtility.RecordAndSaveToResults(key1, () => Thread.Sleep(1));
|
||||||
|
BenchmarkUtility.RecordAndSaveToResults(key2, () => Thread.Sleep(1));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var allResults = BenchmarkUtility.GetAllResults();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(allResults);
|
||||||
|
Assert.True(allResults.ContainsKey(key1));
|
||||||
|
Assert.True(allResults.ContainsKey(key2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetResults_NonExistentKey_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var nonExistentKey = "non-existent-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = BenchmarkUtility.GetResults(nonExistentKey);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(results);
|
||||||
|
Assert.Empty(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OmegaLeo.HelperLib.Models;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Tests.Models;
|
||||||
|
|
||||||
|
public class NeoDictionaryTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Add_AddsItemSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dict.Add("one", 1);
|
||||||
|
dict.Add("two", 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, dict.Items.Count);
|
||||||
|
Assert.Equal("one", dict.Items[0].Key);
|
||||||
|
Assert.Equal(1, dict.Items[0].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGetValue_ExistingKey_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("test", 42);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.TryGetValue("test", out var value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Equal(42, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGetValue_NonExistingKey_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("test", 42);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.TryGetValue("nonexistent", out var value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
Assert.Equal(default(int), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGetValueFromIndex_ValidIndex_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("first", 1);
|
||||||
|
dict.Add("second", 2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.TryGetValueFromIndex(1, out var value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.Equal(2, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGetValueFromIndex_InvalidIndex_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("first", 1);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.TryGetValueFromIndex(5, out var value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
Assert.Equal(default(int), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToDictionary_ConvertsToStandardDictionary()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var neoDict = new NeoDictionary<string, int>();
|
||||||
|
neoDict.Add("a", 1);
|
||||||
|
neoDict.Add("b", 2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var standardDict = neoDict.ToDictionary();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, standardDict.Count);
|
||||||
|
Assert.Equal(1, standardDict["a"]);
|
||||||
|
Assert.Equal(2, standardDict["b"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ImplicitConversion_ConvertsToStandardDictionary()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var neoDict = new NeoDictionary<string, int>();
|
||||||
|
neoDict.Add("x", 10);
|
||||||
|
neoDict.Add("y", 20);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Dictionary<string, int> standardDict = neoDict;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, standardDict.Count);
|
||||||
|
Assert.Equal(10, standardDict["x"]);
|
||||||
|
Assert.Equal(20, standardDict["y"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Any_EmptyDictionary_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.Any();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Any_WithItems_ReturnsTrue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("item", 1);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.Any();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Any_WithPredicate_FiltersCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("one", 1);
|
||||||
|
dict.Add("five", 5);
|
||||||
|
dict.Add("ten", 10);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.Any(item => item.Value > 5);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Where_FiltersItems()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("one", 1);
|
||||||
|
dict.Add("two", 2);
|
||||||
|
dict.Add("three", 3);
|
||||||
|
dict.Add("four", 4);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var filtered = dict.Where(item => item.Value > 2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, filtered.Count());
|
||||||
|
Assert.All(filtered, item => Assert.True(item.Value > 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FirstOrDefault_EmptyDictionary_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.FirstOrDefault();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FirstOrDefault_WithItems_ReturnsFirstItem()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("first", 1);
|
||||||
|
dict.Add("second", 2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.FirstOrDefault();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("first", result.Key);
|
||||||
|
Assert.Equal(1, result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LastOrDefault_WithItems_ReturnsLastItem()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("first", 1);
|
||||||
|
dict.Add("second", 2);
|
||||||
|
dict.Add("third", 3);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = dict.LastOrDefault();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("third", result.Key);
|
||||||
|
Assert.Equal(3, result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddRange_FromNeoDictionary_AddsAllItems()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict1 = new NeoDictionary<string, int>();
|
||||||
|
dict1.Add("a", 1);
|
||||||
|
dict1.Add("b", 2);
|
||||||
|
|
||||||
|
var dict2 = new NeoDictionary<string, int>();
|
||||||
|
dict2.Add("c", 3);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dict2.AddRange(dict1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, dict2.Items.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Count_ReturnsCorrectCount()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add("one", 1);
|
||||||
|
dict.Add("two", 2);
|
||||||
|
dict.Add("three", 3);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var count = dict.Count();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
# OmegaLeo.HelperLib.Tests
|
||||||
|
|
||||||
|
Comprehensive test suite for the OmegaLeo.HelperLib library.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This project contains xUnit tests that validate the functionality of the HelperLib library. The tests run automatically in CI/CD pipelines to catch errors before they reach production.
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
### Extension Methods
|
||||||
|
|
||||||
|
#### MathExtensions (8 tests)
|
||||||
|
- `AverageWithNullValidation` for int, double, and float types
|
||||||
|
- Empty list handling
|
||||||
|
- Single value handling
|
||||||
|
- Negative value handling
|
||||||
|
|
||||||
|
#### IListExtensions (14 tests)
|
||||||
|
- `Swap` - by indices and by items
|
||||||
|
- `Replace` - existing and non-existing items
|
||||||
|
- `Random` - single element and multiple elements
|
||||||
|
- `Shuffle` - list randomization
|
||||||
|
- Edge cases (empty lists, invalid parameters)
|
||||||
|
|
||||||
|
### Helper Utilities
|
||||||
|
|
||||||
|
#### BenchmarkUtility (8 tests)
|
||||||
|
- `Record` - execution time measurement
|
||||||
|
- `RecordAndSaveToResults` - saved benchmarks
|
||||||
|
- `Start` / `Stop` - manual timing
|
||||||
|
- `ClearResults` - cleanup functionality
|
||||||
|
- `GetResults` / `GetAllResults` - result retrieval
|
||||||
|
|
||||||
|
### Models
|
||||||
|
|
||||||
|
#### NeoDictionary (12 tests)
|
||||||
|
- `Add` and `TryGetValue` operations
|
||||||
|
- `TryGetValueFromIndex` - indexed access
|
||||||
|
- `ToDictionary` - conversion to standard Dictionary
|
||||||
|
- LINQ operations (`Any`, `Where`, `FirstOrDefault`, `LastOrDefault`)
|
||||||
|
- `AddRange` - bulk operations
|
||||||
|
- `Count` - size queries
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Run all tests
|
||||||
|
```bash
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run tests for a specific project
|
||||||
|
```bash
|
||||||
|
dotnet test OmegaLeo.HelperLib.Tests/OmegaLeo.HelperLib.Tests.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run tests with detailed output
|
||||||
|
```bash
|
||||||
|
dotnet test --verbosity normal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run tests with code coverage
|
||||||
|
```bash
|
||||||
|
dotnet test --collect:"XPlat Code Coverage"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Structure
|
||||||
|
|
||||||
|
Tests are organized by the area of code they test:
|
||||||
|
|
||||||
|
```
|
||||||
|
OmegaLeo.HelperLib.Tests/
|
||||||
|
├── Extensions/
|
||||||
|
│ ├── MathExtensionsTests.cs
|
||||||
|
│ └── IListExtensionsTests.cs
|
||||||
|
├── Helpers/
|
||||||
|
│ └── BenchmarkUtilityTests.cs
|
||||||
|
└── Models/
|
||||||
|
└── NeoDictionaryTests.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing New Tests
|
||||||
|
|
||||||
|
When adding new functionality to the library, follow these guidelines:
|
||||||
|
|
||||||
|
1. **Create tests first** (TDD approach recommended)
|
||||||
|
2. **Use descriptive test names** following the pattern: `MethodName_Scenario_ExpectedResult`
|
||||||
|
3. **Follow the AAA pattern**:
|
||||||
|
- **Arrange**: Set up test data
|
||||||
|
- **Act**: Execute the method being tested
|
||||||
|
- **Assert**: Verify the results
|
||||||
|
4. **Test edge cases**: empty collections, null values, boundary conditions
|
||||||
|
5. **Test error conditions**: ensure exceptions are thrown when expected
|
||||||
|
|
||||||
|
### Example Test Structure
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Fact]
|
||||||
|
public void MethodName_Scenario_ExpectedResult()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var input = new List<int> { 1, 2, 3 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = input.MyExtensionMethod();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedValue, result);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
Tests run automatically on:
|
||||||
|
- **Pull Requests** to the main branch
|
||||||
|
- **Pushes** to the main branch
|
||||||
|
|
||||||
|
The CI workflow (`.github/workflows/dotnet.yml`) is configured to:
|
||||||
|
1. Restore dependencies
|
||||||
|
2. Build the solution
|
||||||
|
3. Run all tests
|
||||||
|
4. Report results
|
||||||
|
|
||||||
|
Pull requests must pass all tests before they can be merged.
|
||||||
|
|
||||||
|
## Test Frameworks and Tools
|
||||||
|
|
||||||
|
- **xUnit** - Testing framework
|
||||||
|
- **coverlet.collector** - Code coverage collection
|
||||||
|
- **Microsoft.NET.Test.Sdk** - Test SDK
|
||||||
|
|
||||||
|
## Current Test Status
|
||||||
|
|
||||||
|
✅ **42 tests passing**
|
||||||
|
- MathExtensions: 8 tests
|
||||||
|
- IListExtensions: 14 tests
|
||||||
|
- BenchmarkUtility: 8 tests
|
||||||
|
- NeoDictionary: 12 tests
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
When contributing to this project:
|
||||||
|
|
||||||
|
1. Ensure all existing tests pass
|
||||||
|
2. Add tests for any new functionality
|
||||||
|
3. Maintain at least the current level of code coverage
|
||||||
|
4. Follow the existing test naming and structure conventions
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Tests fail locally but pass in CI
|
||||||
|
- Ensure you're using .NET 8.0 SDK
|
||||||
|
- Run `dotnet restore` to update dependencies
|
||||||
|
- Clear bin/obj folders and rebuild
|
||||||
|
|
||||||
|
### Tests pass locally but fail in CI
|
||||||
|
- Check for environment-specific assumptions
|
||||||
|
- Ensure tests are deterministic (not dependent on timing, randomness, etc.)
|
||||||
|
- Review test output logs in the CI workflow
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Main Library README](../README.md)
|
||||||
|
- [Contributing Guidelines](../CONTRIBUTING.md)
|
||||||
|
- [CI Workflow](.github/workflows/dotnet.yml)
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj" />
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj" />
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Game\OmegaLeo.HelperLib.Game.csproj" />
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Game\OmegaLeo.HelperLib.Game.csproj" />
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Git\OmegaLeo.HelperLib.Git.csproj" />
|
|
||||||
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# Documentation Attribute IDE Tooltip Example
|
||||||
|
|
||||||
|
## Recommended: Use Markdown for Code Examples
|
||||||
|
|
||||||
|
For the best IDE experience, we **strongly recommend using markdown code fences** in your `codeExample` parameter:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation(
|
||||||
|
"MyMethod",
|
||||||
|
"Does something cool",
|
||||||
|
new[] { "param: Description" },
|
||||||
|
@"```csharp
|
||||||
|
// Use markdown code fences like this!
|
||||||
|
MyMethod(""example"");
|
||||||
|
```"
|
||||||
|
)]
|
||||||
|
```
|
||||||
|
|
||||||
|
This provides:
|
||||||
|
- ✅ Proper syntax highlighting in IDEs
|
||||||
|
- ✅ Better formatting in IntelliSense tooltips
|
||||||
|
- ✅ Consistent appearance across Visual Studio, Rider, and VS Code
|
||||||
|
- ✅ Markdown rendering in documentation generators
|
||||||
|
|
||||||
|
## Before (Without XML Doc Generator)
|
||||||
|
|
||||||
|
When using the `DocumentationAttribute` without the XML Doc Generator, IDEs would not show the documentation:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation(
|
||||||
|
"BenchmarkUtility",
|
||||||
|
"Utility class for benchmarking code execution time.",
|
||||||
|
null,
|
||||||
|
@"```csharp
|
||||||
|
BenchmarkUtility.Start(""MyBenchmark"");
|
||||||
|
// Code to benchmark
|
||||||
|
BenchmarkUtility.Stop(""MyBenchmark"");
|
||||||
|
var results = BenchmarkUtility.GetResults(""MyBenchmark"");
|
||||||
|
```")]
|
||||||
|
public class BenchmarkUtility
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**IDE Hover Tooltip Shows:** No documentation (just the class signature)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## After (With XML Doc Generator)
|
||||||
|
|
||||||
|
With the XML Doc Generator running as part of the build, the same code automatically generates XML documentation that appears in IDE tooltips:
|
||||||
|
|
||||||
|
### Generated XML Documentation:
|
||||||
|
```xml
|
||||||
|
<member name="T:OmegaLeo.HelperLib.Helpers.BenchmarkUtility">
|
||||||
|
<summary>Utility class for benchmarking code execution time.</summary>
|
||||||
|
<example><![CDATA[```csharp
|
||||||
|
BenchmarkUtility.Start("MyBenchmark");
|
||||||
|
// Code to benchmark
|
||||||
|
BenchmarkUtility.Stop("MyBenchmark");
|
||||||
|
var results = BenchmarkUtility.GetResults("MyBenchmark");
|
||||||
|
```]]></example>
|
||||||
|
</member>
|
||||||
|
```
|
||||||
|
|
||||||
|
**IDE Hover Tooltip Now Shows:**
|
||||||
|
- **Summary:** "Utility class for benchmarking code execution time."
|
||||||
|
- **Example:** Full code example with syntax highlighting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Real-World Example: Method with Parameters
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation(
|
||||||
|
"AverageWithNullValidation",
|
||||||
|
"Calculates the average of a list of integers, returning 0 if the list is empty.",
|
||||||
|
new string[] { "list: The list of integers to calculate the average from." },
|
||||||
|
@"```csharp
|
||||||
|
var numbers = new List<int> { 1, 2, 3, 4 };
|
||||||
|
int average = numbers.AverageWithNullValidation(); // average will be 2
|
||||||
|
var emptyList = new List<int>();
|
||||||
|
int averageEmpty = emptyList.AverageWithNullValidation(); // averageEmpty will be 0
|
||||||
|
```")]
|
||||||
|
public static int AverageWithNullValidation(this IEnumerable<int> list)
|
||||||
|
{
|
||||||
|
return list.Any() ? (int)list.Average() : 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generated XML:
|
||||||
|
```xml
|
||||||
|
<member name="M:OmegaLeo.HelperLib.Extensions.MathExtensions.AverageWithNullValidation(System.Collections.Generic.IEnumerable{System.Int32})">
|
||||||
|
<summary>Calculates the average of a list of integers, returning 0 if the list is empty.</summary>
|
||||||
|
<remarks>
|
||||||
|
Parameters:
|
||||||
|
- list: The list of integers to calculate the average from.
|
||||||
|
</remarks>
|
||||||
|
<example><![CDATA[```csharp
|
||||||
|
var numbers = new List<int> { 1, 2, 3, 4 };
|
||||||
|
int average = numbers.AverageWithNullValidation(); // average will be 2
|
||||||
|
var emptyList = new List<int>();
|
||||||
|
int averageEmpty = emptyList.AverageWithNullValidation(); // averageEmpty will be 0
|
||||||
|
```]]></example>
|
||||||
|
</member>
|
||||||
|
```
|
||||||
|
|
||||||
|
**IDE Hover Tooltip Now Shows:**
|
||||||
|
- **Summary:** "Calculates the average of a list of integers, returning 0 if the list is empty."
|
||||||
|
- **Remarks:** Parameter documentation
|
||||||
|
- **Example:** Complete usage example with expected behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Rich Documentation in IDE:** See full descriptions, parameters, and examples without leaving your code
|
||||||
|
2. **Automatic Generation:** No manual XML comment writing - it's all generated from the attribute
|
||||||
|
3. **DRY Principle:** Define documentation once in the attribute, use it for both runtime and design-time
|
||||||
|
4. **Better IntelliSense:** Enhanced code completion with examples and detailed descriptions
|
||||||
|
5. **Consistent Format:** All documentation follows .NET XML documentation standards
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to View in Your IDE
|
||||||
|
|
||||||
|
### Visual Studio
|
||||||
|
- **Hover** over any class/method/property with DocumentationAttribute
|
||||||
|
- **Quick Info** (Ctrl+K, Ctrl+I) for detailed view
|
||||||
|
|
||||||
|
### JetBrains Rider
|
||||||
|
- **Hover** over the symbol
|
||||||
|
- **Quick Documentation** (Ctrl+Q or F1) for full documentation panel
|
||||||
|
|
||||||
|
### VS Code (with C# extension)
|
||||||
|
- **Hover** over the symbol for IntelliSense popup
|
||||||
|
- Shows summary and examples inline
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<PackAsTool>false</PackAsTool>
|
||||||
|
<PackageId>OmegaLeo.HelperLib.XmlDocGenerator</PackageId>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Authors>Nuno "Omega Leo" Diogo</Authors>
|
||||||
|
<Description>MSBuild task to generate XML documentation from DocumentationAttribute</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/omegaleo/HelperLib</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/omegaleo/HelperLib</RepositoryUrl>
|
||||||
|
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
|
||||||
|
<PackageTags>msbuild;xml;documentation;attributes</PackageTags>
|
||||||
|
|
||||||
|
<!-- MSBuild Package Settings -->
|
||||||
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<NoWarn>NU5100</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Reflection.Metadata" Version="8.0.0" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Include the tool exe and its dependencies in the package -->
|
||||||
|
<Target Name="AddToolToPackage" BeforeTargets="_GetPackageFiles">
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="$(OutputPath)**\*.dll" Pack="true" PackagePath="tools/net8.0/" />
|
||||||
|
<None Include="$(OutputPath)$(AssemblyName).runtimeconfig.json" Pack="true" PackagePath="tools/net8.0/" />
|
||||||
|
<None Include="$(OutputPath)$(AssemblyName).deps.json" Pack="true" PackagePath="tools/net8.0/" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- Include build targets in the package -->
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="build\OmegaLeo.HelperLib.XmlDocGenerator.props" Pack="true" PackagePath="build\" />
|
||||||
|
<None Include="build\OmegaLeo.HelperLib.XmlDocGenerator.targets" Pack="true" PackagePath="build\" />
|
||||||
|
<None Include="buildMultiTargeting\OmegaLeo.HelperLib.XmlDocGenerator.props" Pack="true" PackagePath="buildMultiTargeting\" />
|
||||||
|
<None Include="buildMultiTargeting\OmegaLeo.HelperLib.XmlDocGenerator.targets" Pack="true" PackagePath="buildMultiTargeting\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Pack the generator DLL into the analyzers folder -->
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
if (args.Length < 1)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Usage: xmldocgen <assembly-path> [output-xml-path]");
|
||||||
|
Console.WriteLine("Generates or augments XML documentation from DocumentationAttribute values.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblyPath = args[0];
|
||||||
|
var outputPath = args.Length > 1 ? args[1] : Path.ChangeExtension(assemblyPath, ".xml");
|
||||||
|
|
||||||
|
if (!File.Exists(assemblyPath))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error: Assembly not found: {assemblyPath}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GenerateXmlDocumentation(assemblyPath, outputPath);
|
||||||
|
Console.WriteLine($"XML documentation generated: {outputPath}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error: {ex.Message}");
|
||||||
|
Console.Error.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GenerateXmlDocumentation(string assemblyPath, string outputPath)
|
||||||
|
{
|
||||||
|
// Create a custom assembly load context to properly load dependencies
|
||||||
|
var loadContext = new AssemblyLoadContext("XmlDocGen", isCollectible: true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Load the assembly and its dependencies
|
||||||
|
var assemblyDir = Path.GetDirectoryName(assemblyPath);
|
||||||
|
if (assemblyDir != null)
|
||||||
|
{
|
||||||
|
loadContext.Resolving += (context, name) =>
|
||||||
|
{
|
||||||
|
var dllPath = Path.Combine(assemblyDir, $"{name.Name}.dll");
|
||||||
|
if (File.Exists(dllPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return context.LoadFromAssemblyPath(dllPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var assembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath(assemblyPath));
|
||||||
|
|
||||||
|
// Force load referenced assemblies BEFORE searching for the attribute
|
||||||
|
if (assemblyDir != null)
|
||||||
|
{
|
||||||
|
foreach (var refAsm in assembly.GetReferencedAssemblies())
|
||||||
|
{
|
||||||
|
var refPath = Path.GetFullPath(Path.Combine(assemblyDir, $"{refAsm.Name}.dll"));
|
||||||
|
if (File.Exists(refPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadContext.LoadFromAssemblyPath(refPath);
|
||||||
|
Console.WriteLine($"Loaded reference: {refAsm.Name}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Could not load {refAsm.Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load existing XML documentation if it exists
|
||||||
|
XDocument? existingDoc = null;
|
||||||
|
if (File.Exists(outputPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
existingDoc = XDocument.Load(outputPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If we can't load it, we'll create a new one
|
||||||
|
existingDoc = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or get the root elements
|
||||||
|
XDocument doc;
|
||||||
|
XElement? membersElement;
|
||||||
|
|
||||||
|
if (existingDoc != null)
|
||||||
|
{
|
||||||
|
doc = existingDoc;
|
||||||
|
membersElement = doc.Root?.Element("members");
|
||||||
|
if (membersElement == null)
|
||||||
|
{
|
||||||
|
membersElement = new XElement("members");
|
||||||
|
doc.Root?.Add(membersElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
doc = new XDocument(
|
||||||
|
new XElement("doc",
|
||||||
|
new XElement("assembly",
|
||||||
|
new XElement("name", assembly.GetName().Name)),
|
||||||
|
new XElement("members")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
membersElement = doc.Root?.Element("members");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (membersElement == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Could not create or find members element");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the DocumentationAttribute type from all loaded assemblies
|
||||||
|
Type? docAttrType = null;
|
||||||
|
foreach (var asm in loadContext.Assemblies)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
docAttrType = asm.GetTypes()
|
||||||
|
.FirstOrDefault(t => t.Name == "DocumentationAttribute");
|
||||||
|
if (docAttrType != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Found DocumentationAttribute in {docAttrType.Assembly.GetName().Name}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException)
|
||||||
|
{
|
||||||
|
// Skip assemblies that can't be loaded
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (docAttrType == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Warning: DocumentationAttribute not found in assembly or its references");
|
||||||
|
Console.WriteLine($"Loaded assemblies: {string.Join(", ", loadContext.Assemblies.Select(a => a.GetName().Name))}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all types in the assembly
|
||||||
|
var processedCount = 0;
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (ProcessType(type, docAttrType, membersElement))
|
||||||
|
{
|
||||||
|
processedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Processed {processedCount} types with Documentation attributes");
|
||||||
|
|
||||||
|
// Save the XML document
|
||||||
|
var settings = new XmlWriterSettings
|
||||||
|
{
|
||||||
|
Indent = true,
|
||||||
|
IndentChars = " ",
|
||||||
|
Encoding = new UTF8Encoding(false) // UTF-8 without BOM
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var writer = XmlWriter.Create(outputPath, settings))
|
||||||
|
{
|
||||||
|
doc.Save(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
loadContext.Unload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ProcessType(Type type, Type docAttrType, XElement membersElement)
|
||||||
|
{
|
||||||
|
bool hasAnyDoc = false;
|
||||||
|
|
||||||
|
// Process class/struct/interface documentation
|
||||||
|
var typeAttrs = type.GetCustomAttributes(docAttrType, true);
|
||||||
|
if (typeAttrs.Length > 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateMember(membersElement, $"T:{type.FullName}", typeAttrs[0], docAttrType);
|
||||||
|
hasAnyDoc = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process fields
|
||||||
|
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
var fieldAttrs = field.GetCustomAttributes(docAttrType, true);
|
||||||
|
if (fieldAttrs.Length > 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateMember(membersElement, $"F:{type.FullName}.{field.Name}", fieldAttrs[0], docAttrType);
|
||||||
|
hasAnyDoc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process properties
|
||||||
|
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
var propAttrs = prop.GetCustomAttributes(docAttrType, true);
|
||||||
|
if (propAttrs.Length > 0)
|
||||||
|
{
|
||||||
|
AddOrUpdateMember(membersElement, $"P:{type.FullName}.{prop.Name}", propAttrs[0], docAttrType);
|
||||||
|
hasAnyDoc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process methods
|
||||||
|
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (method.IsSpecialName) continue; // Skip property accessors, etc.
|
||||||
|
|
||||||
|
var methodAttrs = method.GetCustomAttributes(docAttrType, true);
|
||||||
|
if (methodAttrs.Length > 0)
|
||||||
|
{
|
||||||
|
var memberName = GetMethodMemberName(method);
|
||||||
|
AddOrUpdateMember(membersElement, memberName, methodAttrs[0], docAttrType);
|
||||||
|
hasAnyDoc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasAnyDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string GetMethodMemberName(MethodInfo method)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append($"M:{method.DeclaringType?.FullName ?? "Unknown"}.{method.Name}");
|
||||||
|
|
||||||
|
var parameters = method.GetParameters();
|
||||||
|
if (parameters.Length > 0)
|
||||||
|
{
|
||||||
|
sb.Append('(');
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0) sb.Append(',');
|
||||||
|
sb.Append(GetTypeName(parameters[i].ParameterType));
|
||||||
|
}
|
||||||
|
sb.Append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string GetTypeName(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType)
|
||||||
|
{
|
||||||
|
var genericType = type.GetGenericTypeDefinition();
|
||||||
|
var genericArgs = type.GetGenericArguments();
|
||||||
|
var name = genericType.FullName?.Substring(0, genericType.FullName.IndexOf('`')) ?? genericType.Name;
|
||||||
|
name += "{" + string.Join(",", genericArgs.Select(GetTypeName)) + "}";
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return type.FullName ?? type.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddOrUpdateMember(XElement membersElement, string memberName, object attribute, Type docAttrType)
|
||||||
|
{
|
||||||
|
// Extract attribute values using reflection - check both fields and properties
|
||||||
|
var title = GetMemberValue<string>(docAttrType, attribute, "Title") ?? "";
|
||||||
|
var description = GetMemberValue<string>(docAttrType, attribute, "Description") ?? "";
|
||||||
|
var args = GetMemberValue<string[]>(docAttrType, attribute, "Args") ?? Array.Empty<string>();
|
||||||
|
var codeExample = GetMemberValue<string>(docAttrType, attribute, "CodeExample") ?? "";
|
||||||
|
|
||||||
|
// Find or create the member element
|
||||||
|
var existingMember = membersElement.Elements("member")
|
||||||
|
.FirstOrDefault(m => m.Attribute("name")?.Value == memberName);
|
||||||
|
|
||||||
|
XElement memberElement;
|
||||||
|
if (existingMember != null)
|
||||||
|
{
|
||||||
|
memberElement = existingMember;
|
||||||
|
// Remove existing auto-generated elements (we'll recreate them)
|
||||||
|
memberElement.Elements("summary").Remove();
|
||||||
|
memberElement.Elements("remarks").Remove();
|
||||||
|
memberElement.Elements("example").Remove();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memberElement = new XElement("member", new XAttribute("name", memberName));
|
||||||
|
membersElement.Add(memberElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add summary
|
||||||
|
if (!string.IsNullOrEmpty(description))
|
||||||
|
{
|
||||||
|
memberElement.Add(new XElement("summary", new XText(description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parameter descriptions as remarks
|
||||||
|
if (args.Length > 0)
|
||||||
|
{
|
||||||
|
var remarksContent = new StringBuilder();
|
||||||
|
remarksContent.AppendLine();
|
||||||
|
remarksContent.AppendLine("Parameters:");
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
remarksContent.AppendLine($" - {arg}");
|
||||||
|
}
|
||||||
|
memberElement.Add(new XElement("remarks", new XText(remarksContent.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add code example
|
||||||
|
if (!string.IsNullOrEmpty(codeExample))
|
||||||
|
{
|
||||||
|
memberElement.Add(new XElement("example", new XCData(codeExample)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static T? GetMemberValue<T>(Type type, object instance, string memberName) where T : class
|
||||||
|
{
|
||||||
|
// Try as field first
|
||||||
|
var field = type.GetField(memberName);
|
||||||
|
if (field != null)
|
||||||
|
{
|
||||||
|
return field.GetValue(instance) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try as property
|
||||||
|
var property = type.GetProperty(memberName);
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
return property.GetValue(instance) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# OmegaLeo.HelperLib.XmlDocGenerator
|
||||||
|
|
||||||
|
Automatic XML documentation generator for DocumentationAttribute. This MSBuild package automatically generates XML documentation comments from your `DocumentationAttribute` declarations, making them visible in IDE IntelliSense and tooltips.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Automatic (Recommended)
|
||||||
|
|
||||||
|
Simply install the OmegaLeo.HelperLib.Documentation package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet add package OmegaLeo.HelperLib.Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
The XmlDocGenerator is automatically included as a dependency and will work out of the box!
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
|
||||||
|
If you want to use the XmlDocGenerator standalone:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet add package OmegaLeo.HelperLib.XmlDocGenerator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Best Practices
|
||||||
|
|
||||||
|
### ✨ Recommended: Use Markdown in Code Examples
|
||||||
|
|
||||||
|
For the best IDE experience, **use markdown code fences** in your `codeExample` parameter:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation(
|
||||||
|
"MyMethod",
|
||||||
|
"Does something cool",
|
||||||
|
new[] { "param: Description" },
|
||||||
|
@"```csharp
|
||||||
|
// Markdown code fence provides syntax highlighting!
|
||||||
|
MyMethod(""example"");
|
||||||
|
```"
|
||||||
|
)]
|
||||||
|
public void MyMethod(string param) { }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Proper syntax highlighting in Visual Studio, Rider, and VS Code
|
||||||
|
- ✅ Better formatting in IntelliSense tooltips
|
||||||
|
- ✅ Professional appearance
|
||||||
|
- ✅ Works with documentation generators
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **MSBuild Integration**: The package includes MSBuild targets that are automatically imported
|
||||||
|
2. **Build-Time Generation**: Runs after Build to augment your final XML documentation
|
||||||
|
3. **Attribute Reading**: Uses reflection to read DocumentationAttribute instances
|
||||||
|
4. **XML Generation**: Creates/augments XML documentation files in your output directory
|
||||||
|
5. **IntelliSense Ready**: IDEs automatically pick up the generated XML
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ **Automatic**: Zero configuration needed
|
||||||
|
- ✅ **Build-time**: No runtime overhead
|
||||||
|
- ✅ **Non-breaking**: Works alongside existing XML comments
|
||||||
|
- ✅ **Cross-platform**: Works on Windows, Linux, macOS
|
||||||
|
- ✅ **Multi-targeting**: Supports all .NET target frameworks
|
||||||
|
- ✅ **IDE Support**: Works with Visual Studio, Rider, VS Code
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
By default, XML generation is enabled. To disable it:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PropertyGroup>
|
||||||
|
<GenerateXmlDocFromDocumentationAttribute>false</GenerateXmlDocFromDocumentationAttribute>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
AGPL-3.0-only
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- Enable XML documentation generation from DocumentationAttribute by default -->
|
||||||
|
<GenerateXmlDocFromDocumentationAttribute Condition="'$(GenerateXmlDocFromDocumentationAttribute)' == ''">true</GenerateXmlDocFromDocumentationAttribute>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<Target Name="GenerateXmlDocFromDocumentationAttribute"
|
||||||
|
AfterTargets="Build"
|
||||||
|
Condition="'$(GenerateXmlDocFromDocumentationAttribute)' == 'true' And '$(TargetFramework)' != ''">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- Path to the XmlDocGenerator tool in the NuGet package -->
|
||||||
|
<XmlDocGenToolPath>$(MSBuildThisFileDirectory)..\tools\net8.0\OmegaLeo.HelperLib.XmlDocGenerator.dll</XmlDocGenToolPath>
|
||||||
|
<!-- Target the final output assembly and XML, not intermediate -->
|
||||||
|
<TargetAssemblyPath>$(TargetDir)$(TargetFileName)</TargetAssemblyPath>
|
||||||
|
<TargetXmlDocPath>$(TargetDir)$(TargetName).xml</TargetXmlDocPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Message Text="[DocumentationAttribute] Generating XML documentation from DocumentationAttribute for $(TargetName)"
|
||||||
|
Importance="high"
|
||||||
|
Condition="Exists('$(XmlDocGenToolPath)') And Exists('$(TargetAssemblyPath)')" />
|
||||||
|
|
||||||
|
<Message Text="[DocumentationAttribute] Tool not found at: $(XmlDocGenToolPath)"
|
||||||
|
Importance="high"
|
||||||
|
Condition="!Exists('$(XmlDocGenToolPath)')" />
|
||||||
|
|
||||||
|
<!-- Run the XmlDocGenerator tool -->
|
||||||
|
<Exec Command="dotnet "$(XmlDocGenToolPath)" "$(TargetAssemblyPath)" "$(TargetXmlDocPath)""
|
||||||
|
Condition="Exists('$(XmlDocGenToolPath)') And Exists('$(TargetAssemblyPath)')"
|
||||||
|
IgnoreExitCode="true"
|
||||||
|
ContinueOnError="true" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="..\build\OmegaLeo.HelperLib.XmlDocGenerator.props" />
|
||||||
|
</Project>
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="..\build\OmegaLeo.HelperLib.XmlDocGenerator.targets" />
|
||||||
|
</Project>
|
||||||
+91
-12
@@ -2,10 +2,6 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib", "OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj", "{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib", "OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj", "{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Git", "OmegaLeo.HelperLib.Git\OmegaLeo.HelperLib.Git.csproj", "{A9579FFB-9A05-4EBF-B298-513C48FB0F91}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.ConsoleApp", "OmegaLeo.HelperLib.ConsoleApp\OmegaLeo.HelperLib.ConsoleApp.csproj", "{3B77B177-FD47-470A-B0EF-01EF8B30CE09}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Changelog", "OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj", "{378D8125-66B5-42B8-9881-33F951E5E04A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Changelog", "OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj", "{378D8125-66B5-42B8-9881-33F951E5E04A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.ChangelogHandler", "OmegaLeo.HelperLib.ChangelogHandler\OmegaLeo.HelperLib.ChangelogHandler.csproj", "{359536F7-02DE-43E9-A383-D83853227B0F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.ChangelogHandler", "OmegaLeo.HelperLib.ChangelogHandler\OmegaLeo.HelperLib.ChangelogHandler.csproj", "{359536F7-02DE-43E9-A383-D83853227B0F}"
|
||||||
@@ -34,48 +30,131 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Document
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Shared", "OmegaLeo.HelperLib.Shared\OmegaLeo.HelperLib.Shared.csproj", "{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Shared", "OmegaLeo.HelperLib.Shared\OmegaLeo.HelperLib.Shared.csproj", "{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.XmlDocGenerator", "OmegaLeo.HelperLib.XmlDocGenerator\OmegaLeo.HelperLib.XmlDocGenerator.csproj", "{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Tests", "OmegaLeo.HelperLib.Tests\OmegaLeo.HelperLib.Tests.csproj", "{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A9579FFB-9A05-4EBF-B298-513C48FB0F91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{A9579FFB-9A05-4EBF-B298-513C48FB0F91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{A9579FFB-9A05-4EBF-B298-513C48FB0F91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{A9579FFB-9A05-4EBF-B298-513C48FB0F91}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A7256931-5D66-46FA-BA50-22B2FA4CD9E9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{3B77B177-FD47-470A-B0EF-01EF8B30CE09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{3B77B177-FD47-470A-B0EF-01EF8B30CE09}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{3B77B177-FD47-470A-B0EF-01EF8B30CE09}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{3B77B177-FD47-470A-B0EF-01EF8B30CE09}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{378D8125-66B5-42B8-9881-33F951E5E04A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|Any CPU.Build.0 = Release|Any CPU
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{AE69480D-5956-46CB-9028-E4723735E37E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{FE6CA992-8BEB-41DD-A8E4-60FCEF96C59A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1BB073B8-F03B-4F9A-A526-F38CB3800AA5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{55C4F738-3B98-42D6-BCD2-6A9D6E0FC07D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{750F2E9A-2703-4732-98D7-29C658368E48} = {00CF0543-37F3-41DE-B78F-8ADA6DE796ED}
|
{750F2E9A-2703-4732-98D7-29C658368E48} = {00CF0543-37F3-41DE-B78F-8ADA6DE796ED}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
OmegaLeo
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RiderProjectSettingsUpdater">
|
||||||
|
<option name="singleClickDiffPreview" value="1" />
|
||||||
|
<option name="unhandledExceptionsIgnoreList" value="1" />
|
||||||
|
<option name="vcsConfiguration" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="ceea394f-e8d4-4740-8811-5a2e04f8668a" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="CopilotPersistence">
|
||||||
|
<persistenceIdMap>
|
||||||
|
<entry key="_/media/omegaleo/Development/Library Dev/HelperLib/OmegaLeo.HelperLib" value="39XkS7QO7RhDgjuWFpL5WzfrjxL" />
|
||||||
|
</persistenceIdMap>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
|
"associatedIndex": 6
|
||||||
|
}]]></component>
|
||||||
|
<component name="ProjectId" id="39XkS7QO7RhDgjuWFpL5WzfrjxL" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<created>1770849800217</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1770849800217</updated>
|
||||||
|
<workItem from="1770849800331" duration="1000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="XDebuggerManager">
|
||||||
|
<breakpoint-manager>
|
||||||
|
<breakpoints>
|
||||||
|
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
|
||||||
|
<properties exception="System.OperationCanceledException" breakIfHandledByOtherCode="false" displayValue="System.OperationCanceledException" />
|
||||||
|
<option name="timeStamp" value="1" />
|
||||||
|
</breakpoint>
|
||||||
|
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
|
||||||
|
<properties exception="System.Threading.Tasks.TaskCanceledException" breakIfHandledByOtherCode="false" displayValue="System.Threading.Tasks.TaskCanceledException" />
|
||||||
|
<option name="timeStamp" value="2" />
|
||||||
|
</breakpoint>
|
||||||
|
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
|
||||||
|
<properties exception="System.Threading.ThreadAbortException" breakIfHandledByOtherCode="false" displayValue="System.Threading.ThreadAbortException" />
|
||||||
|
<option name="timeStamp" value="3" />
|
||||||
|
</breakpoint>
|
||||||
|
</breakpoints>
|
||||||
|
</breakpoint-manager>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -33,4 +33,7 @@
|
|||||||
<ProjectReference Include="..\OmegaLeo.HelperLib.Shared\OmegaLeo.HelperLib.Shared.csproj" />
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Shared\OmegaLeo.HelperLib.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Import local XML doc generation for development -->
|
||||||
|
<Import Project="../XmlDocGeneratorLocal.props" Condition="Exists('../XmlDocGeneratorLocal.props')" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
# Rider-Specific: Troubleshooting XML Documentation Display
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
You're consuming OmegaLeo.HelperLib in your project (e.g., in HomeController.cs) and when hovering over methods like `GetChangelogMarkdown` or `BenchmarkUtility.Start`, you only see:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Documentation("GetChangelogMarkdown", "Generates...", null, "")]
|
||||||
|
public static string GetChangelogMarkdown(IEnumerable<Assembly> assemblies)
|
||||||
|
in class OmegaLeo.HelperLib.Changelog.Helpers.ChangelogHelper
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of the rich XML documentation.
|
||||||
|
|
||||||
|
## Why This Happens in Rider
|
||||||
|
|
||||||
|
Rider has several caching mechanisms that can prevent it from picking up XML documentation files, especially after they've been regenerated or updated.
|
||||||
|
|
||||||
|
## Verification: Are XML Files Present?
|
||||||
|
|
||||||
|
First, verify the XML files exist and have content:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if XML files exist next to the DLLs
|
||||||
|
ls -la bin/Debug/net*/OmegaLeo.HelperLib*.xml
|
||||||
|
|
||||||
|
# Check content of Changelog XML
|
||||||
|
cat bin/Debug/net*/OmegaLeo.HelperLib.Changelog.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see files like:
|
||||||
|
- `OmegaLeo.HelperLib.xml`
|
||||||
|
- `OmegaLeo.HelperLib.Changelog.xml`
|
||||||
|
- `OmegaLeo.HelperLib.Game.xml`
|
||||||
|
|
||||||
|
Each should contain `<member>` tags with `<summary>` elements.
|
||||||
|
|
||||||
|
## Solution Steps for Rider
|
||||||
|
|
||||||
|
### Step 1: Invalidate Caches (Most Common Fix)
|
||||||
|
|
||||||
|
1. In Rider: **File → Invalidate Caches...**
|
||||||
|
2. Check **all boxes**:
|
||||||
|
- ✅ Clear file system cache and Local History
|
||||||
|
- ✅ Clear VCS Log caches and indexes
|
||||||
|
- ✅ Clear downloaded shared indexes
|
||||||
|
- ✅ Clear workspace model
|
||||||
|
- ✅ Clean NuGet cache
|
||||||
|
3. Click **Invalidate and Restart**
|
||||||
|
|
||||||
|
### Step 2: Clean and Rebuild
|
||||||
|
|
||||||
|
After Rider restarts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet clean
|
||||||
|
dotnet build --configuration Debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in Rider:
|
||||||
|
1. **Build → Clean Solution**
|
||||||
|
2. **Build → Rebuild All**
|
||||||
|
|
||||||
|
### Step 3: Check XML File Location
|
||||||
|
|
||||||
|
Rider needs the XML file to be **in the same directory as the DLL**:
|
||||||
|
|
||||||
|
```
|
||||||
|
bin/Debug/net8.0/
|
||||||
|
├── OmegaLeo.HelperLib.Changelog.dll ← Must be here
|
||||||
|
└── OmegaLeo.HelperLib.Changelog.xml ← And here
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
```bash
|
||||||
|
# Should show both .dll and .xml
|
||||||
|
ls -1 bin/Debug/net*/OmegaLeo.HelperLib.Changelog.*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Verify XML Content
|
||||||
|
|
||||||
|
Check the XML actually has the documentation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -A 5 "GetChangelogMarkdown" bin/Debug/net*/OmegaLeo.HelperLib.Changelog.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
Should show:
|
||||||
|
```xml
|
||||||
|
<member name="M:OmegaLeo.HelperLib.Changelog.Helpers.ChangelogHelper.GetChangelogMarkdown...">
|
||||||
|
<summary>Generates a markdown formatted changelog from the changelog attributes in the provided assemblies.</summary>
|
||||||
|
</member>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Force Rider to Reload References
|
||||||
|
|
||||||
|
1. Right-click on your project in Solution Explorer
|
||||||
|
2. **Properties**
|
||||||
|
3. Go to **Build → Output**
|
||||||
|
4. Note the output path
|
||||||
|
5. Check that path has both .dll and .xml files
|
||||||
|
|
||||||
|
Or try:
|
||||||
|
1. Remove the reference to OmegaLeo.HelperLib.Changelog
|
||||||
|
2. Rebuild
|
||||||
|
3. Re-add the reference
|
||||||
|
4. Rebuild
|
||||||
|
|
||||||
|
### Step 6: Check Quick Documentation Window
|
||||||
|
|
||||||
|
Instead of hover tooltip, try:
|
||||||
|
1. Place cursor on `GetChangelogMarkdown`
|
||||||
|
2. Press **Ctrl+Q** (Windows/Linux) or **F1** (macOS)
|
||||||
|
3. This opens the Quick Documentation window
|
||||||
|
4. It should show the full documentation
|
||||||
|
|
||||||
|
### Step 7: Verify Reference Type
|
||||||
|
|
||||||
|
Check how you're referencing the library:
|
||||||
|
|
||||||
|
**If using ProjectReference:**
|
||||||
|
```xml
|
||||||
|
<ProjectReference Include="..\HelperLib\OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj" />
|
||||||
|
```
|
||||||
|
Rider might prioritize source code over XML. Consider using a built DLL reference or PackageReference instead.
|
||||||
|
|
||||||
|
**If using PackageReference (NuGet):**
|
||||||
|
```xml
|
||||||
|
<PackageReference Include="OmegaLeo.HelperLib.Changelog" Version="1.2.1.1" />
|
||||||
|
```
|
||||||
|
Ensure the package includes the .xml files in the lib/ folder.
|
||||||
|
|
||||||
|
**If using DLL Reference:**
|
||||||
|
```xml
|
||||||
|
<Reference Include="OmegaLeo.HelperLib.Changelog">
|
||||||
|
<HintPath>path\to\OmegaLeo.HelperLib.Changelog.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
```
|
||||||
|
Ensure the .xml file is in the same directory as the .dll.
|
||||||
|
|
||||||
|
## Advanced: Rider's External Annotations Cache
|
||||||
|
|
||||||
|
If the above doesn't work, Rider might have cached old annotations:
|
||||||
|
|
||||||
|
### Windows:
|
||||||
|
```
|
||||||
|
%LOCALAPPDATA%\JetBrains\Rider<version>\resharper-host\local\Transient\ReSharperHost\
|
||||||
|
```
|
||||||
|
|
||||||
|
### macOS:
|
||||||
|
```
|
||||||
|
~/Library/Caches/JetBrains/Rider<version>/resharper-host/local/Transient/ReSharperHost/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux:
|
||||||
|
```
|
||||||
|
~/.cache/JetBrains/Rider<version>/resharper-host/local/Transient/ReSharperHost/
|
||||||
|
```
|
||||||
|
|
||||||
|
Try deleting these caches while Rider is closed.
|
||||||
|
|
||||||
|
## Testing the XML Documentation
|
||||||
|
|
||||||
|
Create a simple test:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using OmegaLeo.HelperLib.Changelog.Helpers;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
// Hover over GetChangelogMarkdown - should show docs
|
||||||
|
var markdown = ChangelogHelper.GetChangelogMarkdown(
|
||||||
|
new[] { Assembly.GetExecutingAssembly() }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and open in Rider. If hover still doesn't work:
|
||||||
|
1. Try Ctrl+Q (Quick Documentation)
|
||||||
|
2. Check Find Usages → see if it shows documentation
|
||||||
|
|
||||||
|
## Still Not Working?
|
||||||
|
|
||||||
|
### Check Rider Settings
|
||||||
|
|
||||||
|
1. **File → Settings → Editor → General → Code Completion**
|
||||||
|
2. Ensure **Show the documentation popup in (ms)**: is not 0
|
||||||
|
3. Try increasing to 500-1000ms
|
||||||
|
|
||||||
|
### Check ReSharper Settings
|
||||||
|
|
||||||
|
1. **File → Settings → Tools → ReSharper**
|
||||||
|
2. **External Sources → Enable XML Documentation comments**
|
||||||
|
3. Ensure it's enabled
|
||||||
|
|
||||||
|
### Last Resort: Complete Reset
|
||||||
|
|
||||||
|
1. Close Rider
|
||||||
|
2. Delete `.idea` folder in your solution directory
|
||||||
|
3. Delete Rider cache:
|
||||||
|
- Windows: `%LOCALAPPDATA%\JetBrains\Rider<version>\`
|
||||||
|
- macOS: `~/Library/Caches/JetBrains/Rider<version>/`
|
||||||
|
- Linux: `~/.cache/JetBrains/Rider<version>/`
|
||||||
|
4. Reopen solution in Rider
|
||||||
|
5. Let it reindex everything
|
||||||
|
|
||||||
|
## Expected Result
|
||||||
|
|
||||||
|
After following these steps, hovering over `GetChangelogMarkdown` should show:
|
||||||
|
|
||||||
|
```
|
||||||
|
GetChangelogMarkdown(IEnumerable<Assembly> assemblies): string
|
||||||
|
|
||||||
|
Generates a markdown formatted changelog from the changelog
|
||||||
|
attributes in the provided assemblies.
|
||||||
|
|
||||||
|
Returns: The generated markdown string
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparison: What You See vs. What You Should See
|
||||||
|
|
||||||
|
### What You're Seeing (Wrong):
|
||||||
|
```
|
||||||
|
[Documentation("GetChangelogMarkdown", "Generates...", null, "")]
|
||||||
|
public static string GetChangelogMarkdown(IEnumerable<Assembly> assemblies)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What You Should See (Correct):
|
||||||
|
```
|
||||||
|
GetChangelogMarkdown(IEnumerable<Assembly> assemblies): string
|
||||||
|
|
||||||
|
Generates a markdown formatted changelog from the changelog
|
||||||
|
attributes in the provided assemblies.
|
||||||
|
```
|
||||||
|
|
||||||
|
## If All Else Fails
|
||||||
|
|
||||||
|
The issue might be with how the library is being referenced. Try:
|
||||||
|
|
||||||
|
1. **Create a fresh test project:**
|
||||||
|
```bash
|
||||||
|
dotnet new console -n TestRider
|
||||||
|
cd TestRider
|
||||||
|
dotnet add reference path/to/OmegaLeo.HelperLib.Changelog.csproj
|
||||||
|
dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Open ONLY this test project in Rider**
|
||||||
|
|
||||||
|
3. **Test if documentation appears**
|
||||||
|
|
||||||
|
If documentation works in the test project but not in your main project, the issue is with your main project's configuration or Rider's cache for that specific solution.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The XML documentation IS being generated correctly. Rider-specific caching and reference resolution can prevent it from being displayed. The most common fix is:
|
||||||
|
|
||||||
|
1. **Invalidate Caches and Restart**
|
||||||
|
2. **Clean and Rebuild**
|
||||||
|
3. **Ensure .xml files are next to .dll files**
|
||||||
|
|
||||||
|
These steps should resolve the issue in 99% of cases!
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
# Troubleshooting: XML Documentation Not Showing in IDE
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
You've built the project and XML documentation files are generated, but when you hover over classes like `BenchmarkUtility` or methods like `GetStopwatch` in your IDE (Rider, Visual Studio), you don't see the rich documentation from `DocumentationAttribute`.
|
||||||
|
|
||||||
|
## Why This Happens
|
||||||
|
|
||||||
|
### Key Insight: Source Projects vs. Consuming Projects
|
||||||
|
|
||||||
|
When you have the **source code open** in your IDE, the IDE prioritizes showing information directly from the source code (the actual C# files) rather than from XML documentation files. This is by design - IDEs assume if you have the source, you don't need the XML summary.
|
||||||
|
|
||||||
|
**XML documentation is primarily for library consumers** who only have the compiled DLL, not the source code.
|
||||||
|
|
||||||
|
## The Solution
|
||||||
|
|
||||||
|
To see your XML documentation in action, you need to:
|
||||||
|
|
||||||
|
1. **Build your library** (the XML is generated correctly)
|
||||||
|
2. **Create a separate test/consumer project** that references the built DLL
|
||||||
|
3. **Use the library in that project** - now you'll see the XML documentation!
|
||||||
|
|
||||||
|
### Step-by-Step Example
|
||||||
|
|
||||||
|
#### 1. Build the Library
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet build OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj --configuration Debug
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
- `bin/Debug/netstandard2.1/OmegaLeo.HelperLib.dll`
|
||||||
|
- `bin/Debug/netstandard2.1/OmegaLeo.HelperLib.xml` ← The documentation!
|
||||||
|
|
||||||
|
#### 2. Create a Test Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/your/workspace
|
||||||
|
dotnet new console -n TestHelperLib
|
||||||
|
cd TestHelperLib
|
||||||
|
dotnet add reference ../HelperLib/OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Use the Library
|
||||||
|
|
||||||
|
In `Program.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using OmegaLeo.HelperLib.Helpers;
|
||||||
|
|
||||||
|
// Now hover over BenchmarkUtility in your IDE
|
||||||
|
BenchmarkUtility.Start("test");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Now you'll see the documentation!** 🎉
|
||||||
|
|
||||||
|
## Alternative: Testing with NuGet Package
|
||||||
|
|
||||||
|
If you want to test the NuGet package experience:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pack the library
|
||||||
|
dotnet pack OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj
|
||||||
|
|
||||||
|
# Create test project
|
||||||
|
dotnet new console -n TestNuGet
|
||||||
|
cd TestNuGet
|
||||||
|
|
||||||
|
# Add local package source
|
||||||
|
dotnet nuget add source /path/to/HelperLib/bin/Debug -n local
|
||||||
|
|
||||||
|
# Install package
|
||||||
|
dotnet add package OmegaLeo.HelperLib
|
||||||
|
```
|
||||||
|
|
||||||
|
## IDE-Specific Troubleshooting
|
||||||
|
|
||||||
|
### JetBrains Rider
|
||||||
|
|
||||||
|
If you're consuming the library and still not seeing docs:
|
||||||
|
|
||||||
|
1. **Invalidate Caches**: `File → Invalidate Caches / Restart`
|
||||||
|
2. **Rebuild Solution**: `Build → Rebuild All`
|
||||||
|
3. **Check XML Location**: Ensure `.xml` file is next to `.dll`
|
||||||
|
|
||||||
|
To verify XML is loaded:
|
||||||
|
- Navigate to a type: `Ctrl+N` → type "BenchmarkUtility"
|
||||||
|
- Check Quick Documentation: `Ctrl+Q`
|
||||||
|
|
||||||
|
### Visual Studio
|
||||||
|
|
||||||
|
1. **Clean Solution**: `Build → Clean Solution`
|
||||||
|
2. **Rebuild**: `Build → Rebuild Solution`
|
||||||
|
3. **Clear Component Cache**: Close VS, delete `.vs` folder
|
||||||
|
|
||||||
|
### VS Code
|
||||||
|
|
||||||
|
1. **Reload Window**: `Ctrl+Shift+P` → "Developer: Reload Window"
|
||||||
|
2. **Restart OmniSharp**: `Ctrl+Shift+P` → "OmniSharp: Restart OmniSharp"
|
||||||
|
|
||||||
|
## Verifying XML Documentation Exists
|
||||||
|
|
||||||
|
Check that documentation was generated:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List XML files
|
||||||
|
ls -la bin/Debug/netstandard2.1/*.xml
|
||||||
|
|
||||||
|
# Check BenchmarkUtility documentation
|
||||||
|
grep -A 10 "BenchmarkUtility" bin/Debug/netstandard2.1/OmegaLeo.HelperLib.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
```xml
|
||||||
|
<member name="T:OmegaLeo.HelperLib.Helpers.BenchmarkUtility">
|
||||||
|
<summary>Utility class for benchmarking code execution time.</summary>
|
||||||
|
<example>...</example>
|
||||||
|
</member>
|
||||||
|
```
|
||||||
|
|
||||||
|
## For NuGet Package Authors
|
||||||
|
|
||||||
|
To ensure consumers get XML documentation:
|
||||||
|
|
||||||
|
1. **Verify `GenerateDocumentationFile` is enabled** in `.csproj`:
|
||||||
|
```xml
|
||||||
|
<PropertyGroup>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Ensure XML is included in package**:
|
||||||
|
```bash
|
||||||
|
# Pack the project
|
||||||
|
dotnet pack
|
||||||
|
|
||||||
|
# Verify XML is in the package
|
||||||
|
unzip -l bin/Debug/YourPackage.nupkg | grep .xml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test the package** by consuming it in another project
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
✅ **XML documentation IS being generated** from DocumentationAttribute
|
||||||
|
✅ **It's in the correct location** (bin folder next to DLL)
|
||||||
|
✅ **IDEs use source code when available** (not XML)
|
||||||
|
✅ **To see XML docs in action**: Reference the library from another project
|
||||||
|
✅ **For package consumers**: Everything works automatically!
|
||||||
|
|
||||||
|
The documentation system is working correctly - it's designed to provide IntelliSense for **users** of your library, not while you're developing it!
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# Why Rider Shows Documentation Attribute Instead of XML Documentation
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
When working in the HelperLib repository, hovering over methods in Rider shows:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...", null, "")]
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of rich IntelliSense documentation.
|
||||||
|
|
||||||
|
## Root Cause: Source Code Takes Priority
|
||||||
|
|
||||||
|
### How IDEs Resolve Documentation
|
||||||
|
|
||||||
|
IDEs like Rider, Visual Studio, and VS Code follow this priority order:
|
||||||
|
|
||||||
|
1. **Triple-slash comments (`///`) in source code** ← HIGHEST PRIORITY
|
||||||
|
2. XML documentation files (`.xml`)
|
||||||
|
3. Decompiled/reflected information
|
||||||
|
4. Raw signature
|
||||||
|
|
||||||
|
### What's Happening in HelperLib
|
||||||
|
|
||||||
|
Current state:
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...")]
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
{
|
||||||
|
// No /// comments here!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**The test project uses `<ProjectReference>`:**
|
||||||
|
- Rider can access the source code directly
|
||||||
|
- No `///` comments exist in source
|
||||||
|
- Rider shows the raw source code (including the attribute)
|
||||||
|
- XML file is ignored because source is available
|
||||||
|
|
||||||
|
## The Solution: Add Triple-Slash Comments
|
||||||
|
|
||||||
|
### Option 1: Minimal Comments (Recommended)
|
||||||
|
|
||||||
|
Add simple `/// <summary>` that references the documentation:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value from the NeoDictionary for the given key.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Rider shows documentation immediately
|
||||||
|
- ✅ Works for developers with source access
|
||||||
|
- ✅ XML generator can still augment with additional info
|
||||||
|
- ✅ Standard .NET practice
|
||||||
|
|
||||||
|
### Option 2: Generated Comments (Advanced)
|
||||||
|
|
||||||
|
Create a Source Generator that reads `DocumentationAttribute` and generates `/// <summary>` comments automatically.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Single source of truth (the attribute)
|
||||||
|
- ✅ No duplication
|
||||||
|
|
||||||
|
**Drawbacks:**
|
||||||
|
- ❌ More complex
|
||||||
|
- ❌ Requires Source Generator infrastructure
|
||||||
|
- ❌ Comments generated at compile-time (not visible in source)
|
||||||
|
|
||||||
|
### Option 3: Accept Current Behavior
|
||||||
|
|
||||||
|
Keep as-is and document that developers see attributes, consumers see XML.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ No code changes needed
|
||||||
|
- ✅ XML documentation works perfectly for NuGet consumers
|
||||||
|
|
||||||
|
**Drawbacks:**
|
||||||
|
- ❌ Poor developer experience in IDE
|
||||||
|
- ❌ Makes development harder
|
||||||
|
|
||||||
|
## Why This Matters
|
||||||
|
|
||||||
|
### For Library Developers (Us)
|
||||||
|
|
||||||
|
Working on HelperLib with ProjectReference:
|
||||||
|
- ❌ **Without `///`:** See raw attributes, no IntelliSense
|
||||||
|
- ✅ **With `///`:** See rich documentation in IDE
|
||||||
|
|
||||||
|
### For Library Consumers
|
||||||
|
|
||||||
|
Installing HelperLib NuGet package:
|
||||||
|
- ✅ Only have DLL + XML
|
||||||
|
- ✅ Always see rich documentation
|
||||||
|
- ✅ Not affected by source code
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
**Add `/// <summary>` comments to all public APIs.**
|
||||||
|
|
||||||
|
This is standard practice in .NET libraries:
|
||||||
|
- Microsoft does this (see .NET source code)
|
||||||
|
- Popular libraries do this (Newtonsoft.Json, etc.)
|
||||||
|
- Provides best developer experience
|
||||||
|
|
||||||
|
The DocumentationAttribute can still provide additional metadata:
|
||||||
|
- Code examples
|
||||||
|
- Extended remarks
|
||||||
|
- Structured argument descriptions
|
||||||
|
|
||||||
|
The XmlDocGenerator augments the XML with this additional content.
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
1. Add `/// <summary>` to all public types and members
|
||||||
|
2. Keep DocumentationAttribute for extended metadata
|
||||||
|
3. XmlDocGenerator augments XML with attribute data
|
||||||
|
4. Best of both worlds:
|
||||||
|
- Developers see documentation in IDE
|
||||||
|
- XML files have rich content from attributes
|
||||||
|
- Consumers get full documentation
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("BenchmarkUtility", "Utility class for benchmarking code execution time.")]
|
||||||
|
public class BenchmarkUtility
|
||||||
|
{
|
||||||
|
[Documentation("Start", "Starts or restarts the stopwatch for the given key.")]
|
||||||
|
public static void Start(string key)
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("BenchmarkUtility", "Utility class for benchmarking code execution time.", null, @"```csharp
|
||||||
|
BenchmarkUtility.Start(""MyBenchmark"");
|
||||||
|
// Code to benchmark
|
||||||
|
BenchmarkUtility.Stop(""MyBenchmark"");
|
||||||
|
```")]
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class for benchmarking code execution time.
|
||||||
|
/// </summary>
|
||||||
|
public class BenchmarkUtility
|
||||||
|
{
|
||||||
|
[Documentation("Start", "Starts or restarts the stopwatch for the given key.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Starts or restarts the stopwatch for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The benchmark identifier</param>
|
||||||
|
public static void Start(string key)
|
||||||
|
```
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- ✅ Rider shows documentation immediately
|
||||||
|
- ✅ XML file has base documentation
|
||||||
|
- ✅ XmlDocGenerator adds code examples from attribute
|
||||||
|
- ✅ Perfect IDE experience for everyone
|
||||||
|
|
||||||
|
## Automated Solution
|
||||||
|
|
||||||
|
Could create a tool or analyzer that:
|
||||||
|
1. Reads DocumentationAttribute
|
||||||
|
2. Warns if no `/// <summary>` exists
|
||||||
|
3. Suggests adding the summary from the attribute
|
||||||
|
|
||||||
|
This enforces the pattern and maintains single source of truth.
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
# Understanding XML Documentation in IDEs
|
||||||
|
|
||||||
|
## The Question
|
||||||
|
|
||||||
|
*"In Rider I still can't see anything new when I hover over BenchmarkUtility or GetStopwatch, I was expecting something like what ///"*
|
||||||
|
|
||||||
|
## The Answer
|
||||||
|
|
||||||
|
**Your XML documentation IS working correctly!** The reason you don't see it is due to how IDEs handle XML documentation vs. source code.
|
||||||
|
|
||||||
|
## How IDE Documentation Works
|
||||||
|
|
||||||
|
### When You Have Source Code Open
|
||||||
|
|
||||||
|
IDEs like Rider, Visual Studio, and VS Code follow this priority:
|
||||||
|
|
||||||
|
1. **Source code** (the actual C# files) - Highest priority
|
||||||
|
2. XML documentation files (.xml)
|
||||||
|
3. Decompiled/reflected information
|
||||||
|
|
||||||
|
When you're working **inside the HelperLib project** with all the source code open, the IDE uses the source code directly. It sees:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation(...)] // IDE ignores attributes for tooltips
|
||||||
|
public class BenchmarkUtility
|
||||||
|
{
|
||||||
|
// IDE shows this code directly
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The IDE doesn't consult XML files because it has something "better" - the actual source!
|
||||||
|
|
||||||
|
### When You Reference a Library
|
||||||
|
|
||||||
|
When you reference the library from **another project** (like a NuGet package consumer would), the IDE only has:
|
||||||
|
|
||||||
|
1. The compiled DLL
|
||||||
|
2. The XML documentation file
|
||||||
|
|
||||||
|
Now the IDE **must use** the XML documentation, and you see all the rich information from DocumentationAttribute!
|
||||||
|
|
||||||
|
## Proof It Works
|
||||||
|
|
||||||
|
We've verified the XML documentation is:
|
||||||
|
|
||||||
|
✅ **Generated correctly** in `bin/Debug/netstandard2.1/OmegaLeo.HelperLib.xml`
|
||||||
|
✅ **Contains all DocumentationAttribute content**:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<member name="T:OmegaLeo.HelperLib.Helpers.BenchmarkUtility">
|
||||||
|
<summary>Utility class for benchmarking code execution time.</summary>
|
||||||
|
<example><![CDATA[```csharp
|
||||||
|
BenchmarkUtility.Start("MyBenchmark");
|
||||||
|
// Code to benchmark
|
||||||
|
BenchmarkUtility.Stop("MyBenchmark");
|
||||||
|
var results = BenchmarkUtility.GetResults("MyBenchmark");
|
||||||
|
```]]></example>
|
||||||
|
</member>
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Works when consumed** - See the `TestConsumer/TestXmlDocs` project
|
||||||
|
|
||||||
|
## How to See Your Documentation
|
||||||
|
|
||||||
|
### Option 1: Use the Test Consumer Project
|
||||||
|
|
||||||
|
1. Navigate to `TestConsumer/TestXmlDocs/`
|
||||||
|
2. Open `Program.cs` in your IDE
|
||||||
|
3. Hover over `BenchmarkUtility`, `Start()`, `GetStopwatch()`, etc.
|
||||||
|
4. You'll see the full documentation!
|
||||||
|
|
||||||
|
### Option 2: Create Your Own Test Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new project
|
||||||
|
dotnet new console -n MyTest
|
||||||
|
cd MyTest
|
||||||
|
|
||||||
|
# Reference the library
|
||||||
|
dotnet add reference ../OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj
|
||||||
|
|
||||||
|
# Create a simple test file
|
||||||
|
cat > Program.cs << 'EOF'
|
||||||
|
using OmegaLeo.HelperLib.Helpers;
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
static void Main() {
|
||||||
|
// Hover over BenchmarkUtility - you'll see docs!
|
||||||
|
BenchmarkUtility.Start("test");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Build
|
||||||
|
dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
Now open this project in Rider and hover over `BenchmarkUtility` - the documentation appears!
|
||||||
|
|
||||||
|
### Option 3: Test with NuGet Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pack the library
|
||||||
|
cd OmegaLeo.HelperLib
|
||||||
|
dotnet pack
|
||||||
|
|
||||||
|
# Create test project
|
||||||
|
cd ../..
|
||||||
|
dotnet new console -n NuGetTest
|
||||||
|
cd NuGetTest
|
||||||
|
|
||||||
|
# Add your local package
|
||||||
|
dotnet add package OmegaLeo.HelperLib --source ../HelperLib/bin/Debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the XML documentation works exactly like it will for real NuGet consumers!
|
||||||
|
|
||||||
|
## Why This Design?
|
||||||
|
|
||||||
|
This is **standard .NET behavior** and makes sense:
|
||||||
|
|
||||||
|
1. **For Library Authors** (you):
|
||||||
|
- You have the source code
|
||||||
|
- You don't need XML summaries of code you wrote
|
||||||
|
- You can read the actual implementation
|
||||||
|
|
||||||
|
2. **For Library Users**:
|
||||||
|
- They only have the DLL
|
||||||
|
- They need XML documentation for IntelliSense
|
||||||
|
- They can't see your source code
|
||||||
|
|
||||||
|
## What This Means for Users
|
||||||
|
|
||||||
|
When someone installs your NuGet package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet add package OmegaLeo.HelperLib
|
||||||
|
```
|
||||||
|
|
||||||
|
They'll see rich documentation in their IDE:
|
||||||
|
- ✅ Class descriptions
|
||||||
|
- ✅ Method summaries
|
||||||
|
- ✅ Parameter descriptions
|
||||||
|
- ✅ Code examples
|
||||||
|
- ✅ All from your DocumentationAttribute!
|
||||||
|
|
||||||
|
## Visual Comparison
|
||||||
|
|
||||||
|
### In Your Development Environment (Source Code Open)
|
||||||
|
|
||||||
|
```
|
||||||
|
Hover over BenchmarkUtility:
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ public class BenchmarkUtility │
|
||||||
|
│ (shows class signature only) │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### In Consumer's Environment (DLL + XML)
|
||||||
|
|
||||||
|
```
|
||||||
|
Hover over BenchmarkUtility:
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ BenchmarkUtility │
|
||||||
|
│ │
|
||||||
|
│ Utility class for benchmarking code execution time. │
|
||||||
|
│ │
|
||||||
|
│ Example: │
|
||||||
|
│ BenchmarkUtility.Start("MyBenchmark"); │
|
||||||
|
│ // Code to benchmark │
|
||||||
|
│ BenchmarkUtility.Stop("MyBenchmark"); │
|
||||||
|
│ var results = BenchmarkUtility.GetResults(...); │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
**Everything is working perfectly!**
|
||||||
|
|
||||||
|
The XML documentation generation is:
|
||||||
|
- ✅ Running on every build
|
||||||
|
- ✅ Creating correct XML files
|
||||||
|
- ✅ Including all DocumentationAttribute content
|
||||||
|
- ✅ Ready for NuGet package consumers
|
||||||
|
|
||||||
|
You just can't see it in your own IDE because you're the author with source code access. This is exactly how it should work!
|
||||||
|
|
||||||
|
To verify it works, either:
|
||||||
|
1. Use the `TestConsumer/TestXmlDocs` project
|
||||||
|
2. Create your own test consumer
|
||||||
|
3. Package and test as a NuGet package
|
||||||
|
|
||||||
|
Your library users will have a great experience with rich IntelliSense documentation! 🎉
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
This file provides local development support for XML documentation generation.
|
||||||
|
Import this file in projects that use DocumentationAttribute during development.
|
||||||
|
For NuGet consumers, the XmlDocGenerator package will provide this automatically.
|
||||||
|
-->
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- Enable XML documentation generation from DocumentationAttribute by default -->
|
||||||
|
<GenerateXmlDocFromDocumentationAttribute Condition="'$(GenerateXmlDocFromDocumentationAttribute)' == ''">true</GenerateXmlDocFromDocumentationAttribute>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="GenerateXmlDocFromDocumentationAttribute_Local"
|
||||||
|
AfterTargets="Build"
|
||||||
|
Condition="'$(GenerateXmlDocFromDocumentationAttribute)' == 'true' And '$(TargetFramework)' != ''">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- For local development, use the built tool from the sibling project -->
|
||||||
|
<XmlDocGenToolPath_Local>$(MSBuildThisFileDirectory)OmegaLeo.HelperLib.XmlDocGenerator/bin/$(Configuration)/net9.0/OmegaLeo.HelperLib.XmlDocGenerator.dll</XmlDocGenToolPath_Local>
|
||||||
|
<!-- Target the final output assembly and XML, not intermediate -->
|
||||||
|
<TargetAssemblyPath>$(TargetDir)$(TargetFileName)</TargetAssemblyPath>
|
||||||
|
<TargetXmlDocPath>$(TargetDir)$(TargetName).xml</TargetXmlDocPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Message Text="[DocumentationAttribute] Generating XML documentation from DocumentationAttribute for $(TargetName)"
|
||||||
|
Importance="high"
|
||||||
|
Condition="Exists('$(XmlDocGenToolPath_Local)') And Exists('$(TargetAssemblyPath)')" />
|
||||||
|
|
||||||
|
<Message Text="[DocumentationAttribute] Local tool not found at: $(XmlDocGenToolPath_Local)"
|
||||||
|
Importance="high"
|
||||||
|
Condition="!Exists('$(XmlDocGenToolPath_Local)')" />
|
||||||
|
|
||||||
|
<Message Text="[DocumentationAttribute] Target assembly not found at: $(TargetAssemblyPath)"
|
||||||
|
Importance="high"
|
||||||
|
Condition="!Exists('$(TargetAssemblyPath)')" />
|
||||||
|
|
||||||
|
<!-- Run the XmlDocGenerator tool -->
|
||||||
|
<Exec Command="dotnet "$(XmlDocGenToolPath_Local)" "$(TargetAssemblyPath)" "$(TargetXmlDocPath)""
|
||||||
|
Condition="Exists('$(XmlDocGenToolPath_Local)') And Exists('$(TargetAssemblyPath)')"
|
||||||
|
IgnoreExitCode="true"
|
||||||
|
ContinueOnError="true" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Binary file not shown.
Reference in New Issue
Block a user