├── tests ├── IDisposableGenerator.Tests.csproj ├── CSGeneratorTest.cs ├── VBGeneratorTest.cs ├── IGeneratorTestBase.cs ├── Directory.Build.props ├── ConfigOptions.cs ├── Directory.Build.targets ├── OptionsProvider.cs ├── CSharpIncrementalGeneratorTest.cs ├── VisualBasicIncrementalGeneratorTest.cs ├── Directory.Packages.props ├── IDisposableGeneratorTests.cs ├── IDisposableGeneratorTests.CSharp9.cs ├── IDisposableGeneratorTests.VisualBasic.cs └── IDisposableGeneratorTests.CSharp10.cs ├── src ├── IDisposableGenerator │ ├── IDisposableGenerator.csproj │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── Directory.Packages.props │ ├── Directory.Build.props │ ├── Directory.Build.targets │ ├── SemanticHelper.cs │ ├── DisposableCodeWriter.cs │ ├── WorkItem.cs │ ├── WorkItemCollection.cs │ └── ClassItems.cs ├── IDisposableGenerator.CSharp │ ├── IDisposableGenerator.CSharp.csproj │ ├── Directory.Build.targets │ ├── Directory.Packages.props │ ├── Directory.Build.props │ └── IDisposableGenerator.cs └── IDisposableGenerator.VisualBasic │ ├── IDisposableGenerator.VisualBasic.csproj │ ├── Directory.Build.targets │ ├── Directory.Packages.props │ ├── Directory.Build.props │ └── IDisposableGenerator.cs ├── pkg └── IDisposableGenerator │ ├── IDisposableGenerator.csproj │ ├── Directory.Packages.props │ ├── Directory.Build.targets │ └── Directory.Build.props ├── NuGet.config ├── .github ├── dependabot.yml ├── workflows │ ├── dotnetcore.yml │ ├── dotnetcore-build.yml │ ├── dotnetcore-publish.yml │ └── codacy-analysis.yml ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── optimization_request.md │ └── bug_report.md └── mergify.yml ├── SECURITY.md ├── Directory.Build.props ├── LICENSE ├── tools ├── install.ps1 └── uninstall.ps1 ├── README.md ├── CODE_OF_CONDUCT.md ├── IDisposableGenerator.sln ├── .gitignore └── .editorconfig /tests/IDisposableGenerator.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/IDisposableGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pkg/IDisposableGenerator/IDisposableGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.CSharp/IDisposableGenerator.CSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.VisualBasic/IDisposableGenerator.VisualBasic.csproj: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/CSGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public class CSGeneratorTest : CSharpIncrementalGeneratorTest 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /tests/VBGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public class VBGeneratorTest : VisualBasicIncrementalGeneratorTest 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.VisualBasic/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.CSharp/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/IGeneratorTestBase.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public interface IGeneratorTestBase 4 | { 5 | /// 6 | /// Allows you to specify additional global options that will appear in the context.AnalyzerConfigOptions.GlobalOptions object. 7 | /// 8 | public List<(string, string)> GlobalOptions { get; } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | preview 6 | false 7 | enable 8 | true 9 | opencover 10 | $(MSBuildThisFileDirectory)coverage.codacy.opencover.xml 11 | enable 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /pkg/IDisposableGenerator/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Properties; 2 | 3 | internal class Resources 4 | { 5 | internal static string AttributeCodeCSharp => ResourceManager.GetString( 6 | nameof(AttributeCodeCSharp)); 7 | 8 | internal static string AttributeCodeVisualBasic => ResourceManager.GetString( 9 | nameof(AttributeCodeVisualBasic)); 10 | 11 | [EditorBrowsable(EditorBrowsableState.Advanced)] 12 | private static ResourceManager ResourceManager { get; } = new ResourceManager( 13 | "IDisposableGenerator.Properties.Resources", 14 | typeof(Resources).Assembly); 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | 14 | - package-ecosystem: "nuget" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core (build pull request) 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 10 | DOTNET_CLI_TELEMETRY_OPTOUT: true 11 | DOTNET_NOLOGO: true 12 | steps: 13 | - uses: actions/checkout@main 14 | - name: Install latest .NET 8 SDK 15 | uses: Elskom/setup-latest-dotnet@main 16 | with: 17 | SDK_VERSION: '8.0.200' 18 | RUNTIME_VERSIONS: '' 19 | 20 | - name: Restore, Build, test, and pack 21 | uses: Elskom/build-dotnet@main 22 | with: 23 | TEST: true 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | --------- | ------------------ | 10 | | 1.0.0 | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Vulnerabilities should be reported in this repository's issues tab. 15 | 16 | It is possible that they might go unnoticed for some time. That is normal 17 | since we all got different timezones. They are accepted case by case basis 18 | and it depends on what type of vulnerability it is as well. 19 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore-build.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core (build) 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | env: 11 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 12 | DOTNET_CLI_TELEMETRY_OPTOUT: true 13 | DOTNET_NOLOGO: true 14 | steps: 15 | - uses: actions/checkout@main 16 | - name: Install latest .NET 8 SDK 17 | uses: Elskom/setup-latest-dotnet@main 18 | with: 19 | SDK_VERSION: '8.0.200' 20 | RUNTIME_VERSIONS: '' 21 | 22 | - name: Restore, Build, test, and pack 23 | uses: Elskom/build-dotnet@main 24 | with: 25 | TEST: true 26 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Before you open an issue read the [Code of Conduct](../CODE_OF_CONDUCT.md) first. 4 | This details what the expected behavior should be. 5 | 6 | 2. Ensure the issue or pull request was not already suggested. 7 | However if the pull request needed reopened due to some issues in it it would be detailed. 8 | 9 | 3. A pull request should not be opened until a issue is tracked on the issues tab. 10 | After that the pull request should reference the issue and when merge should automatically close it. 11 | 12 | 4. This file could change at any time so be sure to read this every time you open a issue or pull request to stay up to date. 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Checklist 2 | 3 | - [ ] I have read the Code of Conduct and the Contributing files before opening this issue. 4 | - [ ] I have verified the issue this pull request fixes or feature this pull request implements is to the current release. 5 | - [ ] I am using the latest stable release with the above code fix or the current main branch if code changes are present (prerelease code changes). 6 | - [ ] I have debugged and tested my changes before submitting this issue and that everything should just work. 7 | 8 | ## Describe the issue this fixes / feature this adds 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | 7 | 8 | 9 | 10 | all 11 | 12 | 13 | 14 | all 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore-publish.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core (build & publish release) 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | env: 12 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 13 | DOTNET_CLI_TELEMETRY_OPTOUT: true 14 | DOTNET_NOLOGO: true 15 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 16 | steps: 17 | - uses: actions/checkout@main 18 | - name: Install latest .NET 8 SDK 19 | uses: Elskom/setup-latest-dotnet@main 20 | with: 21 | SDK_VERSION: '8.0.200' 22 | RUNTIME_VERSIONS: '' 23 | 24 | - name: Restore, Build, test, pack, and push 25 | uses: Elskom/build-dotnet@main 26 | with: 27 | TEST: true 28 | PUSH: true 29 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.CSharp/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | 7 | 8 | 9 | 10 | 11 | all 12 | 13 | 14 | 15 | all 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/ConfigOptions.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | /// 4 | /// Allows adding additional global options 5 | /// 6 | [method: ExcludeFromCodeCoverage] 7 | internal class ConfigOptions(AnalyzerConfigOptions workspaceOptions, List<(string, string)> globalOptions) : AnalyzerConfigOptions 8 | { 9 | private readonly AnalyzerConfigOptions _workspaceOptions = workspaceOptions; 10 | private readonly Dictionary _globalOptions = globalOptions.ToDictionary(t => t.Item1, t => t.Item2); 11 | 12 | [ExcludeFromCodeCoverage] 13 | public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) 14 | => this._workspaceOptions.TryGetValue(key, out value) || this._globalOptions.TryGetValue(key, out value); 15 | } 16 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.VisualBasic/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | 7 | 8 | 9 | 10 | 11 | all 12 | 13 | 14 | 15 | all 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDisposableGeneratorTests.cs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | # Issue Details 8 | 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/optimization_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Optimization request 3 | about: Suggest an optimization idea for this project. 4 | 5 | --- 6 | 7 | # Issue Details 8 | 9 | 10 | **Is your optimization request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the optimization request here. 21 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | Els_kom org. 6 | Els_kom org. 7 | git 8 | https://github.com/Elskom/IDisposableGenerator/ 9 | https://github.com/Elskom/IDisposableGenerator/ 10 | $(MSBuildThisFileDirectory)artifacts\ 11 | MIT 12 | 13 | true 14 | embedded 15 | true 16 | 17 | true 18 | true 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | # Issue Details 8 | 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Step 1. 17 | 2. Step 2. 18 | 3. Step 3. 19 | 4. Any other steps. 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Actual behavior** 25 | The actual result. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Version used** 31 | The version affected by this issue (Can also be "main" for code in latest main branch but reference commit then if possible). 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | netstandard2.0 6 | preview 7 | false 8 | 1.2.0 9 | Copyright (c) 2021~2025 10 | 11 | false 12 | false 13 | $(NoWarn);NU5128;NU5127 14 | true 15 | true 16 | preview 17 | AllEnabledByDefault 18 | enable 19 | true 20 | Els_kom org. 21 | embedded 22 | true 23 | true 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.CSharp/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | netstandard2.0 6 | preview 7 | false 8 | 1.2.0 9 | Copyright (c) 2021~2025 10 | 11 | false 12 | false 13 | $(NoWarn);NU5128;NU5127 14 | true 15 | true 16 | preview 17 | AllEnabledByDefault 18 | enable 19 | true 20 | Els_kom org. 21 | embedded 22 | true 23 | true 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.VisualBasic/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | netstandard2.0 6 | preview 7 | false 8 | 1.2.0 9 | Copyright (c) 2021~2025 10 | 11 | false 12 | false 13 | $(NoWarn);NU5128;NU5127 14 | true 15 | true 16 | preview 17 | AllEnabledByDefault 18 | enable 19 | true 20 | Els_kom org. 21 | embedded 22 | true 23 | true 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 Els_kom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/IDisposableGenerator/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 20 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Resources.resx 7 | 8 | 9 | <_Parameter1>$(AssemblyName).Tests 10 | 11 | 12 | <_Parameter1>$(AssemblyName).CSharp 13 | 14 | 15 | <_Parameter1>$(AssemblyName).VisualBasic 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/OptionsProvider.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | /// 4 | /// This class just passes argument through to the projects options provider and it used to provider custom global options 5 | /// 6 | internal class OptionsProvider : AnalyzerConfigOptionsProvider 7 | { 8 | private readonly AnalyzerConfigOptionsProvider _analyzerConfigOptionsProvider; 9 | 10 | public OptionsProvider(AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, List<(string, string)> globalOptions) 11 | { 12 | this._analyzerConfigOptionsProvider = analyzerConfigOptionsProvider; 13 | this.GlobalOptions = new ConfigOptions(this._analyzerConfigOptionsProvider.GlobalOptions, globalOptions); 14 | } 15 | 16 | [ExcludeFromCodeCoverage] 17 | public override AnalyzerConfigOptions GlobalOptions { get; } 18 | 19 | [ExcludeFromCodeCoverage] 20 | public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) 21 | => this._analyzerConfigOptionsProvider.GetOptions(tree); 22 | 23 | [ExcludeFromCodeCoverage] 24 | public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) 25 | => this._analyzerConfigOptionsProvider.GetOptions(textFile); 26 | } 27 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/SemanticHelper.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | internal static class SemanticHelper 4 | { 5 | public static string FullNamespace(this ISymbol symbol) 6 | { 7 | var parts = new Stack(); 8 | var iterator = symbol as INamespaceSymbol ?? symbol.ContainingNamespace; 9 | while (iterator != null) 10 | { 11 | if (!string.IsNullOrEmpty(iterator.Name)) 12 | { 13 | parts.Push(iterator.Name); 14 | } 15 | 16 | iterator = iterator.ContainingNamespace; 17 | } 18 | 19 | return parts.Count == 0 ? string.Empty : string.Join(".", parts); 20 | } 21 | 22 | public static bool FullNamespaceEquals(this ISymbol symbol, string @namespace) 23 | => symbol.FullNamespace().Equals(@namespace, StringComparison.Ordinal); 24 | 25 | public static void ToSourceFile( 26 | this StringBuilder source, 27 | string sourceName, 28 | ref SourceProductionContext context) 29 | => context.AddSource(sourceName, source.ToString()); 30 | 31 | public static void ToSourceFile( 32 | this StringBuilder source, 33 | string sourceName, 34 | ref IncrementalGeneratorPostInitializationContext context) 35 | => context.AddSource(sourceName, source.ToString()); 36 | } 37 | -------------------------------------------------------------------------------- /tests/CSharpIncrementalGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public class CSharpIncrementalGeneratorTest : SourceGeneratorTest, IGeneratorTestBase 4 | where TSourceGenerator : IIncrementalGenerator, new() 5 | where TVerifier : IVerifier, new() 6 | { 7 | protected override IEnumerable GetSourceGenerators() 8 | => [typeof(TSourceGenerator)]; 9 | 10 | protected override string DefaultFileExt => "cs"; 11 | 12 | public override string Language => LanguageNames.CSharp; 13 | 14 | public List<(string, string)> GlobalOptions { get; } = []; 15 | 16 | public LanguageVersion LanguageVersion { get; set; } 17 | 18 | [ExcludeFromCodeCoverage] 19 | protected override CompilationOptions CreateCompilationOptions() 20 | => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); 21 | 22 | [ExcludeFromCodeCoverage] 23 | protected override ParseOptions CreateParseOptions() 24 | => new CSharpParseOptions(this.LanguageVersion, DocumentationMode.Diagnose); 25 | 26 | [ExcludeFromCodeCoverage] 27 | protected override AnalyzerOptions GetAnalyzerOptions(Project project) 28 | => new( 29 | project.AnalyzerOptions.AdditionalFiles, 30 | new OptionsProvider(project.AnalyzerOptions.AnalyzerConfigOptionsProvider, this.GlobalOptions)); 31 | } 32 | -------------------------------------------------------------------------------- /tests/VisualBasicIncrementalGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public class VisualBasicIncrementalGeneratorTest : SourceGeneratorTest, IGeneratorTestBase 4 | where TSourceGenerator : IIncrementalGenerator, new() 5 | where TVerifier : IVerifier, new() 6 | { 7 | protected override IEnumerable GetSourceGenerators() 8 | => [typeof(TSourceGenerator)]; 9 | 10 | protected override string DefaultFileExt => "vb"; 11 | 12 | public override string Language => LanguageNames.VisualBasic; 13 | 14 | public List<(string, string)> GlobalOptions { get; } = []; 15 | 16 | public Microsoft.CodeAnalysis.VisualBasic.LanguageVersion LanguageVersion { get; set; } = Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.Default; 17 | 18 | [ExcludeFromCodeCoverage] 19 | protected override CompilationOptions CreateCompilationOptions() 20 | => new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary); 21 | 22 | [ExcludeFromCodeCoverage] 23 | protected override ParseOptions CreateParseOptions() 24 | => new VisualBasicParseOptions(this.LanguageVersion, DocumentationMode.Diagnose); 25 | 26 | [ExcludeFromCodeCoverage] 27 | protected override AnalyzerOptions GetAnalyzerOptions(Project project) 28 | => new( 29 | project.AnalyzerOptions.AdditionalFiles, 30 | new OptionsProvider(project.AnalyzerOptions.AnalyzerConfigOptionsProvider, this.GlobalOptions)); 31 | } 32 | -------------------------------------------------------------------------------- /tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersDir = Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" 4 | if (-Not (Test-Path $analyzersDir)) 5 | { 6 | return 7 | } 8 | 9 | $analyzersPaths = Join-Path ( $analyzersDir ) * -Resolve 10 | 11 | foreach($analyzersPath in $analyzersPaths) 12 | { 13 | # Install the language agnostic analyzers. 14 | if (Test-Path $analyzersPath) 15 | { 16 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 17 | { 18 | if($project.Object.AnalyzerReferences) 19 | { 20 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 21 | } 22 | } 23 | } 24 | } 25 | 26 | # $project.Type gives the language name like (C# or VB.NET) 27 | $languageFolder = "" 28 | if($project.Type -eq "C#") 29 | { 30 | $languageFolder = "cs" 31 | } 32 | if($project.Type -eq "VB.NET") 33 | { 34 | $languageFolder = "vb" 35 | } 36 | if($languageFolder -eq "") 37 | { 38 | return 39 | } 40 | 41 | foreach($analyzersPath in $analyzersPaths) 42 | { 43 | # Install language specific analyzers. 44 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 45 | if (Test-Path $languageAnalyzersPath) 46 | { 47 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 48 | { 49 | if($project.Object.AnalyzerReferences) 50 | { 51 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /tests/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /pkg/IDisposableGenerator/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 1.2.0 7 | Changes in this release: 8 | - Fixed bug in the KeepOpen dispose check in generated code. 9 | Copyright (c) 2021~2025 10 | 11 | false 12 | true 13 | $(NoWarn);NU5128;NU5127 14 | 15 | true 16 | Els_kom org. 17 | MIT 18 | https://github.com/Elskom/IDisposableGenerator 19 | https://github.com/Elskom/IDisposableGenerator/ 20 | Source Generator Generating the Dispose functions in Disposables. 21 | ../../artifacts/ 22 | git 23 | true 24 | 25 | 26 | 27 | 28 | $(NuspecProperties);Version=$(Version);BaseOutputPath=$(OutputPath);PackageReleaseNotes=$(PackageReleaseNotes); 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersDir = Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" 4 | if (-Not (Test-Path $analyzersDir)) 5 | { 6 | return 7 | } 8 | 9 | $analyzersPaths = Join-Path ( $analyzersDir ) * -Resolve 10 | 11 | foreach($analyzersPath in $analyzersPaths) 12 | { 13 | # Uninstall the language agnostic analyzers. 14 | if (Test-Path $analyzersPath) 15 | { 16 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 17 | { 18 | if($project.Object.AnalyzerReferences) 19 | { 20 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 21 | } 22 | } 23 | } 24 | } 25 | 26 | # $project.Type gives the language name like (C# or VB.NET) 27 | $languageFolder = "" 28 | if($project.Type -eq "C#") 29 | { 30 | $languageFolder = "cs" 31 | } 32 | if($project.Type -eq "VB.NET") 33 | { 34 | $languageFolder = "vb" 35 | } 36 | if($languageFolder -eq "") 37 | { 38 | return 39 | } 40 | 41 | foreach($analyzersPath in $analyzersPaths) 42 | { 43 | # Uninstall language specific analyzers. 44 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 45 | if (Test-Path $languageAnalyzersPath) 46 | { 47 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 48 | { 49 | if($project.Object.AnalyzerReferences) 50 | { 51 | try 52 | { 53 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 54 | } 55 | catch 56 | { 57 | 58 | } 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDisposableGenerator 2 | Source Generator Generating the Dispose functions in Disposables. 3 | 4 | ## Code Ownership 5 | 6 | All code used is copyright of Elskom org, with the exception of Roslyn which is copyright of the .NET Foundation and it's contributors. 7 | 8 | The dependencies of the unit tests are copyright of their respective owners. 9 | 10 | ## Status 11 | 12 | This project is currently actively maintained whenever an issue happens (or whenever major roslyn changes happens that break it). 13 | 14 | ## Purpose 15 | 16 | This project is for easily generating the dispose functions of disposable types using attributes to control the generator on how it writes the generated code. This results in code that is more maintainable and cleaner than if you had to implement the IDisposable interface yourself. Disposable types require marking the type as partial to properly compile the generated code. 17 | 18 | ## Documentation 19 | 20 | It is currently in the works. 21 | 22 | ## Badges 23 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/4764a3b231ad40c798ea3d193ff3dfe7)](https://www.codacy.com/gh/Elskom/IDisposableGenerator/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Elskom/IDisposableGenerator&utm_campaign=Badge_Grade) 24 | [![Codacy Coverage Badge](https://app.codacy.com/project/badge/Coverage/4764a3b231ad40c798ea3d193ff3dfe7)](https://www.codacy.com/gh/Elskom/IDisposableGenerator/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Elskom/IDisposableGenerator&utm_campaign=Badge_Coverage) 25 | 26 | | Package | Version | 27 | |:-------:|:-------:| 28 | | IDisposableGenerator | [![NuGet Badge](https://buildstats.info/nuget/IDisposableGenerator?includePreReleases=true)](https://www.nuget.org/packages/IDisposableGenerator/) | 29 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.VisualBasic/IDisposableGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | using System.Text; 4 | 5 | [Generator(LanguageNames.VisualBasic)] 6 | public class IDisposableGeneratorVB : IIncrementalGenerator 7 | { 8 | // on MacOS add "SpinWait.SpinUntil(() => Debugger.IsAttached);" to debug in rider. 9 | public void Initialize(IncrementalGeneratorInitializationContext context) 10 | { 11 | var workItemCollection = context.CompilationProvider.Select( 12 | static (c, _) => new WorkItemCollection(c)); 13 | var workItems = context.SyntaxProvider.CreateSyntaxProvider( 14 | static (n, _) => n is ClassBlockSyntax, 15 | static (n, ct) => (INamedTypeSymbol)n.SemanticModel.GetDeclaredSymbol(n.Node, ct)! 16 | ).Combine(workItemCollection).Select( 17 | static (testClass, ct) => 18 | { 19 | testClass.Right.Process(testClass.Left, ct); 20 | return true; 21 | }); 22 | var combined = workItems.Collect().Combine(workItemCollection); 23 | context.RegisterSourceOutput(combined, (ctx, items) => 24 | { 25 | // begin creating the source we'll inject into the users compilation 26 | DisposableCodeWriter.WriteDisposableCodeVisualBasic( 27 | items.Right, 28 | ref ctx); 29 | }); 30 | context.RegisterPostInitializationOutput(ctx => 31 | { 32 | // Always generate the attributes. 33 | var attributeSource = new StringBuilder(); 34 | _ = attributeSource.Append(Properties.Resources.AttributeCodeVisualBasic!); 35 | attributeSource.ToSourceFile("GeneratedAttributes.g.vb", ref ctx); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/DisposableCodeWriter.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | internal static class DisposableCodeWriter 4 | { 5 | public static void WriteDisposableCodeVisualBasic( 6 | WorkItemCollection workItemCollection, 7 | ref SourceProductionContext context) 8 | { 9 | StringBuilder sourceBuilder = new("' "); 10 | _ = sourceBuilder.Append(@" 11 | Imports System 12 | "); 13 | foreach (var workItem in workItemCollection.GetWorkItems()) 14 | { 15 | _ = sourceBuilder.Append(workItem.ToVisualBasicCodeString()); 16 | } 17 | 18 | // inject the created sources into the users compilation. 19 | sourceBuilder.ToSourceFile("Disposables.g.vb", ref context); 20 | } 21 | 22 | public static void WriteDisposableCodeCSharp10( 23 | WorkItemCollection workItemCollection, 24 | ref SourceProductionContext context) 25 | { 26 | foreach (var workItem in workItemCollection.GetWorkItems()) 27 | { 28 | var sourceBuilder = workItem.ToCSharp10CodeString(); 29 | 30 | // inject the created sources into the users compilation. 31 | sourceBuilder.ToSourceFile($@"Disposables{( 32 | workItemCollection.Count > 1 33 | ? $".{workItemCollection.IndexOf(workItem)}" : 34 | string.Empty)}.g.cs", ref context); 35 | } 36 | } 37 | 38 | public static void WriteDisposableCodeCSharp9( 39 | WorkItemCollection workItemCollection, 40 | ref SourceProductionContext context) 41 | { 42 | StringBuilder sourceBuilder = new("// "); 43 | foreach (var workItem in workItemCollection.GetWorkItems()) 44 | { 45 | _ = sourceBuilder.Append(workItem.ToCSharp9CodeString()); 46 | } 47 | 48 | // inject the created source into the users compilation. 49 | sourceBuilder.ToSourceFile("Disposables.g.cs", ref context); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/codacy-analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow checks out code, performs a Codacy security scan 2 | # and integrates the results with the 3 | # GitHub Advanced Security code scanning feature. For more information on 4 | # the Codacy security scan action usage and parameters, see 5 | # https://github.com/codacy/codacy-analysis-cli-action. 6 | # For more information on Codacy Analysis CLI in general, see 7 | # https://github.com/codacy/codacy-analysis-cli. 8 | 9 | name: Codacy Security Scan 10 | 11 | on: 12 | push: 13 | branches: [ main ] 14 | pull_request: 15 | branches: [ main ] 16 | 17 | jobs: 18 | codacy-security-scan: 19 | name: Codacy Security Scan 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Checkout the repository to the GitHub Actions runner 23 | - name: Checkout code 24 | uses: actions/checkout@main 25 | 26 | - name: Install latest .NET 8 SDK 27 | uses: Elskom/setup-latest-dotnet@main 28 | with: 29 | SDK_VERSION: '8.0.200' 30 | RUNTIME_VERSIONS: '' 31 | 32 | - name: Restore, Build, test, and pack 33 | uses: Elskom/build-dotnet@main 34 | with: 35 | TEST: true 36 | PACK: false 37 | 38 | - name: Run codacy-coverage-reporter 39 | uses: codacy/codacy-coverage-reporter-action@master 40 | with: 41 | api-token: ${{ secrets.CODACY_API_TOKEN }} 42 | coverage-reports: tests/coverage.codacy.opencover.xml 43 | 44 | # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis 45 | - name: Run Codacy Analysis CLI 46 | uses: codacy/codacy-analysis-cli-action@master 47 | with: 48 | api-token: ${{ secrets.CODACY_API_TOKEN }} 49 | verbose: true 50 | output: results.sarif 51 | format: sarif 52 | # Adjust severity of non-security issues 53 | gh-code-scanning-compat: true 54 | # Force 0 exit code to allow SARIF file generation 55 | # This will handover control about PR rejection to the GitHub side 56 | max-allowed-issues: 2147483647 57 | 58 | # Upload the SARIF file generated in the previous step 59 | - name: Upload SARIF results file 60 | uses: github/codeql-action/upload-sarif@main 61 | with: 62 | sarif_file: results.sarif 63 | -------------------------------------------------------------------------------- /src/IDisposableGenerator.CSharp/IDisposableGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | using System.Text; 4 | 5 | [Generator] 6 | public class IDisposableGenerator : IIncrementalGenerator 7 | { 8 | private delegate void WriteDisposableCode( 9 | WorkItemCollection workItemCollection, 10 | ref SourceProductionContext context); 11 | 12 | // on MacOS add "SpinWait.SpinUntil(() => Debugger.IsAttached);" to debug in rider. 13 | public void Initialize(IncrementalGeneratorInitializationContext context) 14 | { 15 | var workItemCollection = context.CompilationProvider.Select( 16 | static (c, _) => new WorkItemCollection(c)); 17 | var workItems = context.SyntaxProvider.CreateSyntaxProvider( 18 | static (n, _) => n is ClassDeclarationSyntax, 19 | (n, ct) => (INamedTypeSymbol)n.SemanticModel.GetDeclaredSymbol(n.Node, ct)! 20 | ).Combine(workItemCollection).Select( 21 | static (testClass, ct) => 22 | { 23 | testClass.Right.Process(testClass.Left, ct); 24 | return true; 25 | }); 26 | var combined = workItems.Collect().Combine(workItemCollection); 27 | context.RegisterSourceOutput(combined, (ctx, items) => 28 | { 29 | // NOTE: for debugging the tests. 30 | // var workItemQuery = 31 | // from item in items.Right.GetWorkItems() 32 | // where items.Right.Count > 1 33 | // select (WorkItem: item, Index: items.Right.IndexOf(item)); 34 | // foreach (var (workItem, index) in workItemQuery) 35 | // { 36 | // Console.WriteLine($"Work Item {index}: {workItem}"); 37 | // } 38 | 39 | WriteDisposableCode writeDisposableCode = 40 | ((CSharpCompilation)items.Right.Compilation).LanguageVersion > LanguageVersion.CSharp9 41 | ? DisposableCodeWriter.WriteDisposableCodeCSharp10 42 | : DisposableCodeWriter.WriteDisposableCodeCSharp9; 43 | 44 | // TODO: Find some way to ensure if the 0th index is actually the right one all the time. 45 | writeDisposableCode(items.Right, ref ctx); 46 | }); 47 | context.RegisterPostInitializationOutput(ctx => 48 | { 49 | // Always generate the attributes. 50 | var attributeSource = new StringBuilder(); 51 | _ = attributeSource.Append(Properties.Resources.AttributeCodeCSharp!); 52 | attributeSource.ToSourceFile("GeneratedAttributes.g.cs", ref ctx); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/IDisposableGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public partial class IDisposableGeneratorTests 4 | { 5 | [Fact] 6 | public async Task TestGeneratingNoInput() 7 | => await RunTest(string.Empty, string.Empty); 8 | 9 | private static async Task RunTest( 10 | string generatedSource, 11 | string testSource, 12 | LanguageVersion? languageVersion = LanguageVersion.CSharp9, 13 | List? testSources = null, 14 | Dictionary? generatedSources = null) 15 | where TestType : SourceGeneratorTest, IGeneratorTestBase, new() 16 | { 17 | var test = new TestType 18 | { 19 | ReferenceAssemblies = ReferenceAssemblies.Net.Net80, 20 | TestState = 21 | { 22 | Sources = 23 | { 24 | testSource 25 | }, 26 | }, 27 | }; 28 | 29 | switch (string.IsNullOrEmpty(testSource)) 30 | { 31 | case false when test is CSGeneratorTest tst: 32 | { 33 | tst.LanguageVersion = languageVersion!.Value; 34 | test.TestState.GeneratedSources.Add( 35 | (typeof(IDisposableGenerator), "GeneratedAttributes.g.cs", Properties.Resources.AttributeCodeCSharp!)); 36 | if (generatedSources is not null 37 | && languageVersion == LanguageVersion.CSharp10) 38 | { 39 | foreach (var source in testSources!) 40 | { 41 | test.TestState.Sources.Add(source); 42 | } 43 | 44 | foreach (var (key, value) in generatedSources) 45 | { 46 | test.TestState.GeneratedSources.Add( 47 | (typeof(IDisposableGenerator), key, value)); 48 | } 49 | } 50 | else 51 | { 52 | test.TestState.GeneratedSources.Add( 53 | (typeof(IDisposableGenerator), "Disposables.g.cs", generatedSource)); 54 | } 55 | 56 | break; 57 | } 58 | case false when test is VBGeneratorTest: 59 | { 60 | test.TestState.GeneratedSources.Add( 61 | (typeof(IDisposableGeneratorVB), "GeneratedAttributes.g.vb", Properties.Resources.AttributeCodeVisualBasic!)); 62 | test.TestState.GeneratedSources.Add( 63 | (typeof(IDisposableGeneratorVB), "Disposables.g.vb", generatedSource)); 64 | break; 65 | } 66 | default: 67 | test.TestState.GeneratedSources.Add( 68 | (typeof(IDisposableGenerator), "GeneratedAttributes.g.cs", Properties.Resources.AttributeCodeCSharp!)); 69 | test.TestState.Sources.Clear(); 70 | break; 71 | } 72 | 73 | await test.RunAsync(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at seandhunt_7@yahoo.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/WorkItem.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | using System; 4 | 5 | internal class WorkItem 6 | { 7 | public string Namespace { get; set; } = null!; 8 | public List Classes { get; } = []; 9 | 10 | public ClassItems? GetClassItems(INamedTypeSymbol testClass) 11 | => this.Classes.FirstOrDefault( 12 | classItem => classItem.NameEquals(testClass.Name)); 13 | 14 | [ExcludeFromCodeCoverage] 15 | public override string ToString() 16 | { 17 | var sb = new StringBuilder($"Namespace: Name: {this.Namespace}"); 18 | foreach (var classItems in this.Classes) 19 | { 20 | _ = sb.AppendLine(); 21 | _ = sb.Append($"Class Item {this.Classes.IndexOf(classItems)}: {classItems}"); 22 | } 23 | 24 | return sb.ToString(); 25 | } 26 | 27 | public string ToCSharp9CodeString() 28 | { 29 | var result = new StringBuilder(); 30 | _ = result.Append($@" 31 | {(!string.IsNullOrEmpty(this.Namespace) ? $@"namespace {this.Namespace} 32 | {{ 33 | " : string.Empty)}using global::System; 34 | "); 35 | foreach (var classItem in this.Classes) 36 | { 37 | var code = classItem.ToCSharp9CodeString(); 38 | if (string.IsNullOrEmpty(this.Namespace)) 39 | { 40 | code = ReduceIndentation(code); 41 | } 42 | 43 | _ = result.Append(code); 44 | } 45 | 46 | if (!string.IsNullOrEmpty(this.Namespace)) 47 | { 48 | _ = result.Append(@"} 49 | "); 50 | } 51 | 52 | return result.ToString(); 53 | } 54 | 55 | public StringBuilder ToCSharp10CodeString() 56 | { 57 | var result = new StringBuilder("// "); 58 | _ = result.Append($@" 59 | {(!string.IsNullOrEmpty(this.Namespace) ? $@"namespace {this.Namespace}; 60 | " : string.Empty)}"); 61 | foreach (var classItem in this.Classes) 62 | { 63 | _ = result.Append(classItem.ToCSharp10CodeString()); 64 | } 65 | 66 | return result; 67 | } 68 | 69 | public string ToVisualBasicCodeString() 70 | { 71 | var result = new StringBuilder(); 72 | if (!string.IsNullOrEmpty(this.Namespace)) 73 | { 74 | _ = result.Append($@" 75 | Namespace {this.Namespace} 76 | "); 77 | } 78 | 79 | foreach (var classItem in this.Classes) 80 | { 81 | var code = classItem.ToVisualBasicCodeString(); 82 | if (string.IsNullOrEmpty(this.Namespace)) 83 | { 84 | code = ReduceIndentation(code); 85 | } 86 | 87 | _ = result.Append(code); 88 | } 89 | 90 | if (!string.IsNullOrEmpty(this.Namespace)) 91 | { 92 | _ = result.Append(@"End Namespace 93 | "); 94 | } 95 | 96 | return result.ToString(); 97 | } 98 | 99 | private static string ReduceIndentation(string code) 100 | { 101 | var eol = (code.Contains("\r\n"), code.Contains("\n")) switch 102 | { 103 | (true, true) => "\r\n", 104 | (false, true) => "\n", 105 | (false, false) => "\r", 106 | _ => throw new InvalidOperationException("bug"), 107 | }; 108 | 109 | var lines = code.Split([eol], StringSplitOptions.None); 110 | for (var i = 0; i < lines.Length; i++) 111 | { 112 | if (lines[i].StartsWith(" ", StringComparison.Ordinal)) 113 | { 114 | lines[i] = lines[i].Substring(4); 115 | } 116 | } 117 | 118 | return string.Join(eol, lines); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | queue_conditions: 4 | - -conflict # skip PRs with conflicts 5 | - -draft # filter-out GH draft PRs 6 | - -closed # filter-out closed GH PRs 7 | - base=main 8 | - label=automerge 9 | - check-success=build 10 | - check-success=Codacy Static Code Analysis 11 | - check-success=DCO 12 | - check-success=WIP 13 | merge_method: squash 14 | update_method: rebase 15 | 16 | pull_request_rules: 17 | # for check failures / WIP pending check. 18 | - name: Add enhancement label 19 | conditions: 20 | - check-pending=build 21 | - -closed 22 | - -label=enhancement 23 | actions: 24 | label: 25 | add: 26 | - enhancement 27 | 28 | - name: Add automerge label 29 | conditions: 30 | - check-success=build 31 | - check-success=Codacy Security Scan 32 | - -draft 33 | - -closed 34 | - -label=automerge 35 | - author~=^(AraHaan|CatGirlsAreLife|JunichiSama|xioke|Elskom-gitsync)$ 36 | actions: 37 | label: 38 | add: 39 | - automerge 40 | 41 | - name: Automatic message on build failure 42 | conditions: 43 | - check-failure=build 44 | - -draft 45 | - -closed 46 | actions: 47 | comment: 48 | message: | 49 | This PR's build has failed. Ensure it passes before they can be scheduled for automatic merge. 50 | 51 | - name: Automatic message on security failure 52 | conditions: 53 | - check-failure=Codacy Static Code Analysis 54 | - -draft 55 | - -closed 56 | actions: 57 | comment: 58 | message: | 59 | This PR's security check has failed. Ensure it passes before they can be scheduled for automatic merge. 60 | 61 | - name: Automatic message on signoff failure 62 | conditions: 63 | - check-failure=DCO 64 | - -draft 65 | - -closed 66 | actions: 67 | comment: 68 | message: | 69 | This PR does not add the DCO signoff. Ensure it does before they can be scheduled for automatic merge. 70 | To do that click the details link on the check below to find the steps to resolve this and then force push using ``git push --force`` after it has been completed. 71 | If the PR has multiple commits to simplify it you can also run ``git rebase -i HEAD~x`` where x is the number of commits inside of the PR, then in the interactive program that is opened to edit the data change every line under the first one that starts with ``pick`` to ``fixup`` then save and exit the program git opens, after that git will rebase them into each other and squashes them into a single commit where then you can run ``git commit --amend --signoff`` then force push. 72 | 73 | - name: Automatic message on WIP 74 | conditions: 75 | - check-pending=WIP 76 | - -draft 77 | - -closed 78 | actions: 79 | comment: 80 | message: This PR is WIP, when you complete the work remember to unset the WIP state in the title of this PR. 81 | 82 | - name: Automatic merge when automerge label added 83 | conditions: 84 | - -conflict # skip PRs with conflicts 85 | - -draft # filter-out GH draft PRs 86 | - -closed # filter-out closed GH PRs 87 | - base=main 88 | - label=automerge 89 | - check-success=build 90 | - check-success=Codacy Security Scan 91 | - check-success=Codacy Static Code Analysis 92 | - check-success=Sonarscharp (reported by Codacy) 93 | - check-success=DCO 94 | - check-success=WIP 95 | actions: 96 | comment: 97 | message: This pull request is about to be automerged. 98 | review: 99 | type: APPROVE 100 | message: Automatically approving since automerge label was found. 101 | queue: 102 | name: default 103 | 104 | - name: Remove automerge label 105 | conditions: 106 | - closed 107 | - label=automerge 108 | actions: 109 | label: 110 | remove: 111 | - automerge 112 | -------------------------------------------------------------------------------- /IDisposableGenerator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IDisposableGenerator", "src\IDisposableGenerator\IDisposableGenerator.csproj", "{56A28977-2F30-476F-8456-CA91F98891E6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{313466D2-D6AF-48A0-A8D5-7247B8759AA4}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{92269EEE-1113-4E58-BB5F-E2D032D418ED}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IDisposableGenerator.Tests", "tests\IDisposableGenerator.Tests.csproj", "{5AA62ED4-AB2E-44E6-A935-D6EC8301C7E0}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IDisposableGenerator.CSharp", "src\IDisposableGenerator.CSharp\IDisposableGenerator.CSharp.csproj", "{964D8915-A3EB-4865-9838-25D4823A45C9}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IDisposableGenerator.VisualBasic", "src\IDisposableGenerator.VisualBasic\IDisposableGenerator.VisualBasic.csproj", "{EC840660-6919-48C2-A1F6-AF834D24BC88}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pkg", "pkg", "{33A6ED18-F1B8-455F-99E7-90CF995FEE66}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IDisposableGenerator", "pkg\IDisposableGenerator\IDisposableGenerator.csproj", "{F382B1B7-7FA6-42A9-A421-C048487AAC65}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {56A28977-2F30-476F-8456-CA91F98891E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {56A28977-2F30-476F-8456-CA91F98891E6}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {56A28977-2F30-476F-8456-CA91F98891E6}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {56A28977-2F30-476F-8456-CA91F98891E6}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {5AA62ED4-AB2E-44E6-A935-D6EC8301C7E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {5AA62ED4-AB2E-44E6-A935-D6EC8301C7E0}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {5AA62ED4-AB2E-44E6-A935-D6EC8301C7E0}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {5AA62ED4-AB2E-44E6-A935-D6EC8301C7E0}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {964D8915-A3EB-4865-9838-25D4823A45C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {964D8915-A3EB-4865-9838-25D4823A45C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {964D8915-A3EB-4865-9838-25D4823A45C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {964D8915-A3EB-4865-9838-25D4823A45C9}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {EC840660-6919-48C2-A1F6-AF834D24BC88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {EC840660-6919-48C2-A1F6-AF834D24BC88}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {EC840660-6919-48C2-A1F6-AF834D24BC88}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {EC840660-6919-48C2-A1F6-AF834D24BC88}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {F382B1B7-7FA6-42A9-A421-C048487AAC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {F382B1B7-7FA6-42A9-A421-C048487AAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {F382B1B7-7FA6-42A9-A421-C048487AAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {F382B1B7-7FA6-42A9-A421-C048487AAC65}.Release|Any CPU.Build.0 = Release|Any CPU 51 | EndGlobalSection 52 | GlobalSection(NestedProjects) = preSolution 53 | {56A28977-2F30-476F-8456-CA91F98891E6} = {313466D2-D6AF-48A0-A8D5-7247B8759AA4} 54 | {5AA62ED4-AB2E-44E6-A935-D6EC8301C7E0} = {92269EEE-1113-4E58-BB5F-E2D032D418ED} 55 | {964D8915-A3EB-4865-9838-25D4823A45C9} = {313466D2-D6AF-48A0-A8D5-7247B8759AA4} 56 | {EC840660-6919-48C2-A1F6-AF834D24BC88} = {313466D2-D6AF-48A0-A8D5-7247B8759AA4} 57 | {F382B1B7-7FA6-42A9-A421-C048487AAC65} = {33A6ED18-F1B8-455F-99E7-90CF995FEE66} 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/WorkItemCollection.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | internal class WorkItemCollection(Compilation compilation) 4 | { 5 | internal Compilation Compilation { get; } = compilation; 6 | private List WorkItems { get; } = []; 7 | 8 | public int Count => this.WorkItems.Count; 9 | 10 | public void Process(INamedTypeSymbol testClass, CancellationToken ct) 11 | { 12 | ct.ThrowIfCancellationRequested(); 13 | this.AddFromNamespace(testClass.FullNamespace()); 14 | var workItem = this.FindWithNamespace(testClass.FullNamespace()); 15 | ct.ThrowIfCancellationRequested(); 16 | 17 | // Avoid a bug that would set namespace to "IDisposableGenerator" 18 | // instead of the namespace that the WorkItem's classes are in. 19 | if (testClass.FullNamespaceEquals("IDisposableGenerator")) 20 | { 21 | return; 22 | } 23 | 24 | ct.ThrowIfCancellationRequested(); 25 | var classItem = GetClassItem(testClass); 26 | 27 | if (classItem is null) 28 | { 29 | return; 30 | } 31 | 32 | ct.ThrowIfCancellationRequested(); 33 | workItem!.Classes.Add(classItem); 34 | 35 | var memberQuery = 36 | from member in testClass.GetMembers() 37 | select member; 38 | 39 | foreach (var member in memberQuery) 40 | { 41 | ct.ThrowIfCancellationRequested(); 42 | CheckAttributesOnMember(member, testClass, ref workItem!, ct); 43 | } 44 | } 45 | 46 | public List GetWorkItems() 47 | => this.WorkItems; 48 | 49 | public int IndexOf(WorkItem item) 50 | => this.WorkItems.IndexOf(item); 51 | 52 | private static ClassItems? GetClassItem(INamedTypeSymbol testClass) 53 | { 54 | var result = new ClassItems(); 55 | var hasDisposalGeneration = false; 56 | 57 | foreach (var attr in testClass.GetAttributes()) 58 | { 59 | #pragma warning disable IDE0010 // Add missing cases 60 | switch (attr.AttributeClass!.Name) 61 | { 62 | case "GenerateDisposeAttribute": 63 | hasDisposalGeneration = true; 64 | result.Name = testClass.Name; 65 | result.Accessibility = testClass.DeclaredAccessibility; 66 | result.Stream = (bool)attr.ConstructorArguments[0].Value!; 67 | break; 68 | case "WithoutThrowIfDisposedAttribute": 69 | result.WithoutThrowIfDisposed = true; 70 | break; 71 | } 72 | #pragma warning restore IDE0010 // Add missing cases 73 | } 74 | 75 | return hasDisposalGeneration ? result : null; 76 | } 77 | 78 | private static void CheckAttributesOnMember(ISymbol member, 79 | INamedTypeSymbol testClass, 80 | ref WorkItem workItem, 81 | CancellationToken ct) 82 | { 83 | var classItem = workItem.GetClassItems(testClass)!; 84 | ct.ThrowIfCancellationRequested(); 85 | foreach (var attr in member.GetAttributes()) 86 | { 87 | ct.ThrowIfCancellationRequested(); 88 | _ = attr!.AttributeClass!.Name switch 89 | { 90 | "DisposeFieldAttribute" => classItem.AddField(attr.ConstructorArguments[0], member), 91 | "NullOnDisposeAttribute" => classItem.AddSetNull(member), 92 | "CallOnDisposeAttribute" => classItem.AddMethod(member), 93 | 94 | // cannot throw here because the attribute in this case should be ignored. 95 | _ => false, 96 | }; 97 | } 98 | } 99 | 100 | private void AddFromNamespace(string nameSpace) 101 | { 102 | if (this.FindWithNamespace(nameSpace) is not null 103 | || nameSpace.Equals("IDisposableGenerator", StringComparison.Ordinal)) 104 | { 105 | return; 106 | } 107 | 108 | this.WorkItems.Add(new WorkItem 109 | { 110 | Namespace = nameSpace, 111 | }); 112 | } 113 | 114 | private WorkItem? FindWithNamespace(string nameSpace) 115 | => this.WorkItems.FirstOrDefault( 116 | workItem => workItem.Namespace.Equals( 117 | nameSpace, 118 | StringComparison.Ordinal)); 119 | } 120 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | // <autogenerated/> 122 | #pragma warning disable SA1636, 8618 123 | namespace IDisposableGenerator 124 | { 125 | using System; 126 | 127 | // used only by a source generator to generate Dispose() and Dispose(bool). 128 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] 129 | internal class CallOnDisposeAttribute : Attribute 130 | { 131 | public CallOnDisposeAttribute() 132 | { 133 | } 134 | } 135 | 136 | // used only by a source generator to generate Dispose() and Dispose(bool). 137 | [AttributeUsage(AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] 138 | internal class DisposeFieldAttribute : Attribute 139 | { 140 | public DisposeFieldAttribute(bool owner) 141 | { 142 | } 143 | } 144 | 145 | // used only by a source generator to generate Dispose() and Dispose(bool). 146 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 147 | internal class GenerateDisposeAttribute : Attribute 148 | { 149 | public GenerateDisposeAttribute(bool stream) 150 | { 151 | } 152 | } 153 | 154 | // used only by a source generator to generate Dispose() and Dispose(bool). 155 | [AttributeUsage(AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] 156 | internal class NullOnDisposeAttribute : Attribute 157 | { 158 | public NullOnDisposeAttribute() 159 | { 160 | } 161 | } 162 | 163 | // used only by a source generator to generate Dispose() and Dispose(bool). 164 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 165 | internal class WithoutThrowIfDisposedAttribute : Attribute 166 | { 167 | } 168 | } 169 | #pragma warning restore SA1636, 8618 170 | 171 | 172 | 173 | ' <autogenerated/> 174 | #Disable Warning SA1636 175 | Imports System 176 | 177 | Namespace IDisposableGenerator 178 | 179 | ' used only by a source generator to generate Dispose() and Dispose(bool). 180 | <AttributeUsage(AttributeTargets.Method, Inherited:=False, AllowMultiple:=False)> 181 | Friend Class CallOnDisposeAttribute 182 | Inherits Attribute 183 | End Class 184 | 185 | ' used only by a source generator to generate Dispose() and Dispose(bool). 186 | <AttributeUsage(AttributeTargets.Event Or AttributeTargets.Field Or AttributeTargets.Property, Inherited:=False, AllowMultiple:=False)> 187 | Friend Class DisposeFieldAttribute 188 | Inherits Attribute 189 | 190 | Public Sub New(owner As Boolean) 191 | End Sub 192 | End Class 193 | 194 | ' used only by a source generator to generate Dispose() and Dispose(bool). 195 | <AttributeUsage(AttributeTargets.Class, Inherited:=False, AllowMultiple:=False)> 196 | Friend Class GenerateDisposeAttribute 197 | Inherits Attribute 198 | 199 | Public Sub New(stream As Boolean) 200 | End Sub 201 | End Class 202 | 203 | ' used only by a source generator to generate Dispose() and Dispose(bool). 204 | <AttributeUsage(AttributeTargets.Event Or AttributeTargets.Field Or AttributeTargets.Property, Inherited:=False, AllowMultiple:=False)> 205 | Friend Class NullOnDisposeAttribute 206 | Inherits Attribute 207 | End Class 208 | 209 | ' used only by a source generator to generate Dispose() and Dispose(bool). 210 | <AttributeUsage(AttributeTargets.Class, Inherited:=False, AllowMultiple:=False)> 211 | Friend Class WithoutThrowIfDisposedAttribute 212 | Inherits Attribute 213 | End Class 214 | 215 | End Namespace 216 | #Enable Warning SA1636 217 | 218 | 219 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # Ionide (cross platform F# VS Code tools) working folder 362 | .ionide/ 363 | 364 | # Fody - auto-generated XML schema 365 | FodyWeavers.xsd 366 | 367 | ## 368 | ## Visual studio for Mac 369 | ## 370 | 371 | 372 | # globs 373 | Makefile.in 374 | *.userprefs 375 | *.usertasks 376 | config.make 377 | config.status 378 | aclocal.m4 379 | install-sh 380 | autom4te.cache/ 381 | *.tar.gz 382 | tarballs/ 383 | test-results/ 384 | 385 | # Mac bundle stuff 386 | *.dmg 387 | *.app 388 | 389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 390 | # General 391 | .DS_Store 392 | .AppleDouble 393 | .LSOverride 394 | 395 | # Icon must end with two \r 396 | Icon 397 | 398 | 399 | # Thumbnails 400 | ._* 401 | 402 | # Files that might appear in the root of a volume 403 | .DocumentRevisions-V100 404 | .fseventsd 405 | .Spotlight-V100 406 | .TemporaryItems 407 | .Trashes 408 | .VolumeIcon.icns 409 | .com.apple.timemachine.donotpresent 410 | 411 | # Directories potentially created on remote AFP share 412 | .AppleDB 413 | .AppleDesktop 414 | Network Trash Folder 415 | Temporary Items 416 | .apdisk 417 | 418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 419 | # Windows thumbnail cache files 420 | Thumbs.db 421 | ehthumbs.db 422 | ehthumbs_vista.db 423 | 424 | # Dump file 425 | *.stackdump 426 | 427 | # Folder config file 428 | [Dd]esktop.ini 429 | 430 | # Recycle Bin used on file shares 431 | $RECYCLE.BIN/ 432 | 433 | # Windows Installer files 434 | *.cab 435 | *.msi 436 | *.msix 437 | *.msm 438 | *.msp 439 | 440 | # Windows shortcuts 441 | *.lnk 442 | 443 | # JetBrains Rider 444 | .idea/ 445 | *.sln.iml 446 | 447 | ## 448 | ## Visual Studio Code 449 | ## 450 | .vscode/* 451 | !.vscode/settings.json 452 | !.vscode/tasks.json 453 | !.vscode/launch.json 454 | !.vscode/extensions.json 455 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = space 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Coding Conventions #### 19 | 20 | # Organize usings 21 | dotnet_separate_import_directive_groups = false 22 | dotnet_sort_system_directives_first = false 23 | file_header_template = unset 24 | 25 | # this. and Me. preferences 26 | dotnet_style_qualification_for_event = true:warning 27 | dotnet_style_qualification_for_field = true 28 | dotnet_style_qualification_for_method = true:warning 29 | dotnet_style_qualification_for_property = true:warning 30 | 31 | # Language keywords vs BCL types preferences 32 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 33 | dotnet_style_predefined_type_for_member_access = true:warning 34 | 35 | # Parentheses preferences 36 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning 37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning 40 | 41 | # Modifier preferences 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 43 | 44 | # Expression-level preferences 45 | dotnet_style_coalesce_expression = true:warning 46 | dotnet_style_collection_initializer = true:warning 47 | dotnet_style_explicit_tuple_names = true:warning 48 | dotnet_style_namespace_match_folder = true 49 | dotnet_style_null_propagation = true:warning 50 | dotnet_style_object_initializer = true:warning 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true:warning 53 | dotnet_style_prefer_collection_expression = when_types_loosely_match 54 | dotnet_style_prefer_compound_assignment = true:warning 55 | dotnet_style_prefer_conditional_expression_over_assignment = true:warning 56 | dotnet_style_prefer_conditional_expression_over_return = true:warning 57 | dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed 58 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 59 | dotnet_style_prefer_inferred_tuple_names = true:warning 60 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 61 | dotnet_style_prefer_simplified_boolean_expressions = true:warning 62 | dotnet_style_prefer_simplified_interpolation = true 63 | 64 | # Field preferences 65 | dotnet_style_readonly_field = true:warning 66 | 67 | # Parameter preferences 68 | dotnet_code_quality_unused_parameters = all:warning 69 | 70 | # Suppression preferences 71 | dotnet_remove_unnecessary_suppression_exclusions = none 72 | 73 | # New line preferences 74 | dotnet_style_allow_multiple_blank_lines_experimental = true 75 | dotnet_style_allow_statement_immediately_after_block_experimental = true 76 | 77 | #### C# Coding Conventions #### 78 | 79 | # var preferences 80 | csharp_style_var_elsewhere = true:warning 81 | csharp_style_var_for_built_in_types = true:warning 82 | csharp_style_var_when_type_is_apparent = true:warning 83 | 84 | # Expression-bodied members 85 | csharp_style_expression_bodied_accessors = true:warning 86 | csharp_style_expression_bodied_constructors = true:warning 87 | csharp_style_expression_bodied_indexers = true:warning 88 | csharp_style_expression_bodied_lambdas = true:suggestion 89 | csharp_style_expression_bodied_local_functions = true 90 | csharp_style_expression_bodied_methods = true:warning 91 | csharp_style_expression_bodied_operators = true:warning 92 | csharp_style_expression_bodied_properties = true:warning 93 | 94 | # Pattern matching preferences 95 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 96 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 97 | csharp_style_prefer_extended_property_pattern = true 98 | csharp_style_prefer_not_pattern = true:warning 99 | csharp_style_prefer_pattern_matching = true:warning 100 | csharp_style_prefer_switch_expression = true:warning 101 | 102 | # Null-checking preferences 103 | csharp_style_conditional_delegate_call = true:warning 104 | 105 | # Modifier preferences 106 | csharp_prefer_static_local_function = true 107 | csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async 108 | csharp_style_prefer_readonly_struct = true 109 | csharp_style_prefer_readonly_struct_member = true 110 | 111 | # Code-block preferences 112 | csharp_prefer_braces = true:warning 113 | csharp_prefer_simple_using_statement = true 114 | csharp_style_namespace_declarations = file_scoped:warning 115 | csharp_style_prefer_method_group_conversion = true 116 | csharp_style_prefer_primary_constructors = true 117 | csharp_style_prefer_top_level_statements = true 118 | 119 | # Expression-level preferences 120 | csharp_prefer_simple_default_expression = true:warning 121 | csharp_style_deconstructed_variable_declaration = true:warning 122 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning 123 | csharp_style_inlined_variable_declaration = true:warning 124 | csharp_style_prefer_index_operator = true:warning 125 | csharp_style_prefer_local_over_anonymous_function = true:warning 126 | csharp_style_prefer_null_check_over_type_check = true 127 | csharp_style_prefer_range_operator = true:warning 128 | csharp_style_prefer_tuple_swap = true 129 | csharp_style_prefer_utf8_string_literals = true 130 | csharp_style_throw_expression = true:warning 131 | csharp_style_unused_value_assignment_preference = discard_variable 132 | csharp_style_unused_value_expression_statement_preference = discard_variable 133 | 134 | # 'using' directive preferences 135 | csharp_using_directive_placement = inside_namespace:warning 136 | 137 | # New line preferences 138 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true 139 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true 140 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true 141 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true 142 | csharp_style_allow_embedded_statements_on_same_line_experimental = true 143 | 144 | #### C# Formatting Rules #### 145 | 146 | # New line preferences 147 | csharp_new_line_before_catch = true 148 | csharp_new_line_before_else = true 149 | csharp_new_line_before_finally = true 150 | csharp_new_line_before_members_in_anonymous_types = true 151 | csharp_new_line_before_members_in_object_initializers = true 152 | csharp_new_line_before_open_brace = all 153 | csharp_new_line_between_query_expression_clauses = true 154 | 155 | # Indentation preferences 156 | csharp_indent_block_contents = true 157 | csharp_indent_braces = false 158 | csharp_indent_case_contents = true 159 | csharp_indent_case_contents_when_block = false 160 | csharp_indent_labels = one_less_than_current 161 | csharp_indent_switch_labels = true 162 | 163 | # Space preferences 164 | csharp_space_after_cast = false 165 | csharp_space_after_colon_in_inheritance_clause = true 166 | csharp_space_after_comma = true 167 | csharp_space_after_dot = false 168 | csharp_space_after_keywords_in_control_flow_statements = true 169 | csharp_space_after_semicolon_in_for_statement = true 170 | csharp_space_around_binary_operators = before_and_after 171 | csharp_space_around_declaration_statements = false 172 | csharp_space_before_colon_in_inheritance_clause = true 173 | csharp_space_before_comma = false 174 | csharp_space_before_dot = false 175 | csharp_space_before_open_square_brackets = false 176 | csharp_space_before_semicolon_in_for_statement = false 177 | csharp_space_between_empty_square_brackets = false 178 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 179 | csharp_space_between_method_call_name_and_opening_parenthesis = false 180 | csharp_space_between_method_call_parameter_list_parentheses = false 181 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 182 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 183 | csharp_space_between_method_declaration_parameter_list_parentheses = false 184 | csharp_space_between_parentheses = false 185 | csharp_space_between_square_brackets = false 186 | 187 | # Wrapping preferences 188 | csharp_preserve_single_line_blocks = true 189 | csharp_preserve_single_line_statements = false 190 | 191 | #### Naming styles #### 192 | 193 | # Naming rules 194 | 195 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 196 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 197 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 198 | 199 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 200 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 201 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 202 | 203 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 204 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 205 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 206 | 207 | # Symbol specifications 208 | 209 | dotnet_naming_symbols.interface.applicable_kinds = interface 210 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 211 | dotnet_naming_symbols.interface.required_modifiers = 212 | 213 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 214 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 215 | dotnet_naming_symbols.types.required_modifiers = 216 | 217 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 218 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 219 | dotnet_naming_symbols.non_field_members.required_modifiers = 220 | 221 | # Naming styles 222 | 223 | dotnet_naming_style.pascal_case.required_prefix = 224 | dotnet_naming_style.pascal_case.required_suffix = 225 | dotnet_naming_style.pascal_case.word_separator = 226 | dotnet_naming_style.pascal_case.capitalization = pascal_case 227 | 228 | dotnet_naming_style.begins_with_i.required_prefix = I 229 | dotnet_naming_style.begins_with_i.required_suffix = 230 | dotnet_naming_style.begins_with_i.word_separator = 231 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 232 | 233 | # IDE0005: Remove unnecessary usings/imports 234 | dotnet_diagnostic.IDE0005.severity = suggestion 235 | -------------------------------------------------------------------------------- /src/IDisposableGenerator/ClassItems.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator; 2 | 3 | internal class ClassItems 4 | { 5 | public string? Name { get; set; } 6 | public Accessibility Accessibility { get; set; } 7 | public bool Stream { get; set; } 8 | public bool WithoutThrowIfDisposed { get; set; } 9 | public List Owns { get; } = []; 10 | public List Fields { get; } = []; 11 | public List SetNull { get; } = []; 12 | public List Methods { get; } = []; 13 | 14 | public static bool IsReadOnlyField(ISymbol member) 15 | => (member is IFieldSymbol fieldSymbol && fieldSymbol.IsReadOnly) || 16 | (member is IPropertySymbol propertySymbol && propertySymbol.IsReadOnly); 17 | 18 | public bool AddSetNull(ISymbol member) 19 | { 20 | if (!IsReadOnlyField(member) || member is IEventSymbol) 21 | { 22 | this.SetNull.Add(member.Name); 23 | } 24 | 25 | return true; 26 | } 27 | 28 | public bool AddMethod(ISymbol member) 29 | { 30 | this.Methods.Add(member.Name); 31 | return true; 32 | } 33 | 34 | public bool AddField(TypedConstant arg, ISymbol member) 35 | { 36 | if ((bool)arg.Value!) 37 | { 38 | this.Owns.Add(member); 39 | } 40 | else 41 | { 42 | this.Fields.Add(member); 43 | } 44 | 45 | return true; 46 | } 47 | 48 | public bool NameEquals(string name) 49 | => this.Name!.Equals(name, StringComparison.Ordinal); 50 | 51 | [ExcludeFromCodeCoverage] 52 | public override string ToString() 53 | { 54 | var result = new StringBuilder(); 55 | _ = result.Append($"Class: Name {this.Name}") 56 | .Append($", Accessibility: {this.Accessibility}") 57 | .Append($", Stream: {this.Stream}") 58 | .Append($", Without ThrowIfDisposed: {this.WithoutThrowIfDisposed}") 59 | .Append($", Owns Count: {this.Owns.Count}") 60 | .Append($", Fields Count: {this.Fields.Count}") 61 | .Append($", SetNull Count: {this.SetNull.Count}") 62 | .Append($", Methods Count: {this.Methods.Count}"); 63 | return result.ToString(); 64 | } 65 | 66 | public string ToCSharp9CodeString() 67 | { 68 | var result = new StringBuilder(); 69 | _ = result.Append($@" 70 | {(this.Accessibility == Accessibility.Public ? "public" : "internal")} partial class {this.Name}{(!this.Stream ? " : IDisposable" : "")} 71 | {{ 72 | private bool isDisposed; 73 | "); 74 | if (this.Owns.Count is not 0) 75 | { 76 | _ = result.Append(this.Stream ? @" 77 | internal bool KeepOpen { get; } 78 | " : @" 79 | internal bool IsOwned { get; set; } 80 | "); 81 | } 82 | 83 | _ = result.Append($@" 84 | {(!this.Stream ? $@"/// 85 | /// Cleans up the resources used by . 86 | /// 87 | public void Dispose() 88 | {{ 89 | this.Dispose(true); 90 | GC.SuppressFinalize(this); 91 | }} 92 | 93 | private" : @"/// 94 | protected override")} void Dispose(bool disposing) 95 | {{ 96 | if (!this.isDisposed && disposing) 97 | {{ 98 | "); 99 | if (this.Methods.Count is not 0) 100 | { 101 | foreach (var methodItem in this.Methods) 102 | { 103 | _ = result.Append($@" this.{methodItem}(); 104 | "); 105 | } 106 | } 107 | 108 | if (this.Owns.Count is not 0) 109 | { 110 | _ = result.Append($@" if ({(this.Stream ? "!this.KeepOpen" : "this.IsOwned")}) 111 | {{ 112 | "); 113 | foreach (var ownedItem in this.Owns) 114 | { 115 | // automatically set to null after Dispose(). 116 | _ = result.Append($@" this.{ownedItem.Name}?.Dispose(); 117 | {(!IsReadOnlyField(ownedItem) ? $@" this.{ownedItem.Name} = null; 118 | " : string.Empty)}"); 119 | } 120 | 121 | _ = result.Append(@" } 122 | "); 123 | } 124 | 125 | if (this.Fields.Count is not 0) 126 | { 127 | foreach (var fieldItem in this.Fields) 128 | { 129 | // automatically set to null after Dispose(). 130 | _ = result.Append($@" this.{fieldItem.Name}?.Dispose(); 131 | {(!IsReadOnlyField(fieldItem) ? $@" this.{fieldItem.Name} = null; 132 | " : string.Empty)}"); 133 | } 134 | } 135 | 136 | if (this.SetNull.Count is not 0) 137 | { 138 | foreach (var nullItem in this.SetNull) 139 | { 140 | _ = result.Append($@" this.{nullItem} = null; 141 | "); 142 | } 143 | } 144 | 145 | _ = result.Append(@" this.isDisposed = true; 146 | } 147 | "); 148 | if (this.Stream) 149 | { 150 | _ = result.Append(@" 151 | // On Streams call base.Dispose(disposing)!!! 152 | base.Dispose(disposing); 153 | "); 154 | } 155 | 156 | _ = result.Append(@" } 157 | "); 158 | if (!this.WithoutThrowIfDisposed) 159 | { 160 | _ = result.Append($@" 161 | internal void ThrowIfDisposed() 162 | {{ 163 | if (this.isDisposed) 164 | {{ 165 | throw new ObjectDisposedException(nameof({this.Name})); 166 | }} 167 | }} 168 | "); 169 | } 170 | 171 | _ = result.Append(@" } 172 | "); 173 | 174 | return result.ToString(); 175 | } 176 | 177 | public string ToCSharp10CodeString() 178 | { 179 | var result = new StringBuilder(); 180 | _ = result.Append($@" 181 | {(this.Accessibility == Accessibility.Public ? "public" : "internal")} partial class {this.Name}{(!this.Stream ? " : IDisposable" : "")} 182 | {{ 183 | private bool isDisposed; 184 | "); 185 | if (this.Owns.Count is not 0) 186 | { 187 | _ = result.Append(this.Stream ? @" 188 | internal bool KeepOpen { get; } 189 | " : @" 190 | internal bool IsOwned { get; set; } 191 | "); 192 | } 193 | 194 | _ = result.Append($@" 195 | {(!this.Stream ? $@"/// 196 | /// Cleans up the resources used by . 197 | /// 198 | public void Dispose() 199 | {{ 200 | this.Dispose(true); 201 | GC.SuppressFinalize(this); 202 | }} 203 | 204 | private" : @"/// 205 | protected override")} void Dispose(bool disposing) 206 | {{ 207 | if (!this.isDisposed && disposing) 208 | {{ 209 | "); 210 | if (this.Methods.Count is not 0) 211 | { 212 | foreach (var methodItem in this.Methods) 213 | { 214 | _ = result.Append($@" this.{methodItem}(); 215 | "); 216 | } 217 | } 218 | 219 | if (this.Owns.Count is not 0) 220 | { 221 | _ = result.Append($@" if ({(this.Stream ? "!this.KeepOpen" : "this.IsOwned")}) 222 | {{ 223 | "); 224 | foreach (var ownedItem in this.Owns) 225 | { 226 | // automatically set to null after Dispose(). 227 | _ = result.Append($@" this.{ownedItem.Name}?.Dispose(); 228 | {(!IsReadOnlyField(ownedItem) ? $@" this.{ownedItem.Name} = null; 229 | " : string.Empty)}"); 230 | } 231 | 232 | _ = result.Append(@" } 233 | "); 234 | } 235 | 236 | if (this.Fields.Count is not 0) 237 | { 238 | foreach (var fieldItem in this.Fields) 239 | { 240 | // automatically set to null after Dispose(). 241 | _ = result.Append($@" this.{fieldItem.Name}?.Dispose(); 242 | {(!IsReadOnlyField(fieldItem) ? $@" this.{fieldItem.Name} = null; 243 | " : string.Empty)}"); 244 | } 245 | } 246 | 247 | if (this.SetNull.Count is not 0) 248 | { 249 | foreach (var nullItem in this.SetNull) 250 | { 251 | _ = result.Append($@" this.{nullItem} = null; 252 | "); 253 | } 254 | } 255 | 256 | _ = result.Append(@" this.isDisposed = true; 257 | } 258 | "); 259 | if (this.Stream) 260 | { 261 | _ = result.Append(@" 262 | // On Streams call base.Dispose(disposing)!!! 263 | base.Dispose(disposing); 264 | "); 265 | } 266 | 267 | _ = result.Append(""" 268 | } 269 | 270 | """); 271 | 272 | if (!this.WithoutThrowIfDisposed) 273 | { 274 | _ = result.Append($@" 275 | internal void ThrowIfDisposed() 276 | {{ 277 | if (this.isDisposed) 278 | {{ 279 | throw new ObjectDisposedException(nameof({this.Name})); 280 | }} 281 | }} 282 | "); 283 | } 284 | 285 | _ = result.Append(@"} 286 | "); 287 | return result.ToString(); 288 | } 289 | 290 | public string ToVisualBasicCodeString() 291 | { 292 | var result = new StringBuilder(); 293 | _ = result.Append($@" 294 | {(this.Accessibility == Accessibility.Public ? "Public" : "Friend")} Partial Class {this.Name}{(!this.Stream ? @" 295 | Implements IDisposable 296 | " : "")} 297 | Private isDisposed As Boolean 298 | "); 299 | if (this.Owns.Count is not 0) 300 | { 301 | _ = result.Append(this.Stream ? @" 302 | Friend ReadOnly Property KeepOpen As Boolean 303 | " : @" 304 | Friend Property IsOwned As Boolean 305 | "); 306 | } 307 | 308 | _ = result.Append($@" 309 | {(!this.Stream ? $@"''' 310 | ''' Cleans up the resources used by . 311 | ''' 312 | Public Sub Dispose() Implements IDisposable.Dispose 313 | Me.Dispose(True) 314 | GC.SuppressFinalize(Me) 315 | End Sub 316 | 317 | Private" : @"''' 318 | Protected Overrides")} Sub Dispose(ByVal disposing As Boolean) 319 | If Not Me.isDisposed AndAlso disposing Then 320 | "); 321 | if (this.Methods.Count is not 0) 322 | { 323 | foreach (var methodItem in this.Methods) 324 | { 325 | _ = result.Append($@" Me.{methodItem}() 326 | "); 327 | } 328 | } 329 | 330 | if (this.Owns.Count is not 0) 331 | { 332 | _ = result.Append($@" If {(this.Stream ? "Not Me.KeepOpen" : "Me.IsOwned")} Then 333 | "); 334 | foreach (var ownedItem in this.Owns) 335 | { 336 | // automatically set to null after Dispose(). 337 | _ = result.Append($@" Me.{ownedItem.Name}?.Dispose() 338 | {(!IsReadOnlyField(ownedItem) ? $@" Me.{ownedItem.Name} = Nothing 339 | " : string.Empty)}"); 340 | } 341 | 342 | _ = result.Append(@" End If 343 | "); 344 | } 345 | 346 | if (this.Fields.Count is not 0) 347 | { 348 | foreach (var fieldItem in this.Fields) 349 | { 350 | // automatically set to null after Dispose(). 351 | _ = result.Append($@" Me.{fieldItem.Name}?.Dispose() 352 | {(!IsReadOnlyField(fieldItem) ? $@" Me.{fieldItem.Name} = Nothing 353 | " : string.Empty)}"); 354 | } 355 | } 356 | 357 | if (this.SetNull.Count is not 0) 358 | { 359 | foreach (var nullItem in this.SetNull) 360 | { 361 | _ = result.Append($@" Me.{nullItem} = Nothing 362 | "); 363 | } 364 | } 365 | 366 | _ = result.Append(@" Me.isDisposed = True 367 | End If 368 | "); 369 | if (this.Stream) 370 | { 371 | _ = result.Append(@" 372 | ' On Streams call MyBase.Dispose(disposing)!!! 373 | MyBase.Dispose(disposing) 374 | "); 375 | } 376 | 377 | _ = result.Append(""" 378 | End Sub 379 | 380 | """); 381 | 382 | if (!this.WithoutThrowIfDisposed) 383 | { 384 | _ = result.Append($$""" 385 | 386 | Friend Sub ThrowIfDisposed() 387 | If Me.isDisposed Then 388 | Throw New ObjectDisposedException(NameOf({{this.Name}})) 389 | End If 390 | End Sub 391 | 392 | """); 393 | } 394 | 395 | _ = result.Append(""" 396 | End Class 397 | 398 | """); 399 | return result.ToString(); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /tests/IDisposableGeneratorTests.CSharp9.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public partial class IDisposableGeneratorTests 4 | { 5 | [Fact] 6 | public async Task TestGeneratingPublicDisposableNotOwnsCSharp9() 7 | => await RunTest(@"// 8 | namespace MyApp 9 | { 10 | using global::System; 11 | 12 | public partial class TestDisposable : IDisposable 13 | { 14 | private bool isDisposed; 15 | 16 | /// 17 | /// Cleans up the resources used by . 18 | /// 19 | public void Dispose() 20 | { 21 | this.Dispose(true); 22 | GC.SuppressFinalize(this); 23 | } 24 | 25 | private void Dispose(bool disposing) 26 | { 27 | if (!this.isDisposed && disposing) 28 | { 29 | this.testDispose?.Dispose(); 30 | this.testDispose = null; 31 | this.testsetnull = null; 32 | this.isDisposed = true; 33 | } 34 | } 35 | 36 | internal void ThrowIfDisposed() 37 | { 38 | if (this.isDisposed) 39 | { 40 | throw new ObjectDisposedException(nameof(TestDisposable)); 41 | } 42 | } 43 | } 44 | } 45 | ", @"namespace MyApp 46 | { 47 | using System; 48 | using IDisposableGenerator; 49 | 50 | [GenerateDispose(false)] 51 | public partial class TestDisposable 52 | { 53 | [DisposeField(false)] 54 | private IDisposable testDispose; 55 | 56 | [NullOnDispose] 57 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 58 | } 59 | } 60 | "); 61 | 62 | [Fact] 63 | public async Task TestGeneratingDisposableNotOwnsCSharp9() 64 | => await RunTest(@"// 65 | namespace MyApp 66 | { 67 | using global::System; 68 | 69 | internal partial class TestDisposable : IDisposable 70 | { 71 | private bool isDisposed; 72 | 73 | /// 74 | /// Cleans up the resources used by . 75 | /// 76 | public void Dispose() 77 | { 78 | this.Dispose(true); 79 | GC.SuppressFinalize(this); 80 | } 81 | 82 | private void Dispose(bool disposing) 83 | { 84 | if (!this.isDisposed && disposing) 85 | { 86 | this.testDispose?.Dispose(); 87 | this.testDispose = null; 88 | this.testsetnull = null; 89 | this.isDisposed = true; 90 | } 91 | } 92 | 93 | internal void ThrowIfDisposed() 94 | { 95 | if (this.isDisposed) 96 | { 97 | throw new ObjectDisposedException(nameof(TestDisposable)); 98 | } 99 | } 100 | } 101 | } 102 | ", @"namespace MyApp 103 | { 104 | using System; 105 | using IDisposableGenerator; 106 | 107 | [GenerateDispose(false)] 108 | internal partial class TestDisposable 109 | { 110 | [DisposeField(false)] 111 | private IDisposable testDispose; 112 | 113 | [NullOnDispose] 114 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 115 | } 116 | } 117 | "); 118 | 119 | [Fact] 120 | public async Task TestGeneratingDisposableOwnsCSharp9() 121 | => await RunTest(@"// 122 | namespace MyApp 123 | { 124 | using global::System; 125 | 126 | internal partial class TestDisposable : IDisposable 127 | { 128 | private bool isDisposed; 129 | 130 | internal bool IsOwned { get; set; } 131 | 132 | /// 133 | /// Cleans up the resources used by . 134 | /// 135 | public void Dispose() 136 | { 137 | this.Dispose(true); 138 | GC.SuppressFinalize(this); 139 | } 140 | 141 | private void Dispose(bool disposing) 142 | { 143 | if (!this.isDisposed && disposing) 144 | { 145 | if (this.IsOwned) 146 | { 147 | this.testDispose?.Dispose(); 148 | this.testDispose = null; 149 | } 150 | this.testsetnull = null; 151 | this.isDisposed = true; 152 | } 153 | } 154 | 155 | internal void ThrowIfDisposed() 156 | { 157 | if (this.isDisposed) 158 | { 159 | throw new ObjectDisposedException(nameof(TestDisposable)); 160 | } 161 | } 162 | } 163 | } 164 | ", @"namespace MyApp 165 | { 166 | using System; 167 | using IDisposableGenerator; 168 | 169 | [GenerateDispose(false)] 170 | internal partial class TestDisposable 171 | { 172 | [DisposeField(true)] 173 | private IDisposable testDispose; 174 | 175 | [NullOnDispose] 176 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 177 | } 178 | } 179 | "); 180 | 181 | [Fact] 182 | public async Task TestGeneratingStreamNotOwnsCSharp9() 183 | => await RunTest(@"// 184 | namespace MyApp 185 | { 186 | using global::System; 187 | 188 | internal partial class TestDisposable 189 | { 190 | private bool isDisposed; 191 | 192 | /// 193 | protected override void Dispose(bool disposing) 194 | { 195 | if (!this.isDisposed && disposing) 196 | { 197 | this.testDispose?.Dispose(); 198 | this.testDispose = null; 199 | this.testsetnull = null; 200 | this.isDisposed = true; 201 | } 202 | 203 | // On Streams call base.Dispose(disposing)!!! 204 | base.Dispose(disposing); 205 | } 206 | 207 | internal void ThrowIfDisposed() 208 | { 209 | if (this.isDisposed) 210 | { 211 | throw new ObjectDisposedException(nameof(TestDisposable)); 212 | } 213 | } 214 | } 215 | } 216 | ", @"namespace MyApp 217 | { 218 | using System; 219 | using System.IO; 220 | using IDisposableGenerator; 221 | 222 | [GenerateDispose(true)] 223 | internal partial class TestDisposable : Stream 224 | { 225 | [DisposeField(false)] 226 | private IDisposable testDispose; 227 | 228 | [NullOnDispose] 229 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 230 | 231 | public override bool CanRead { get => throw new NotSupportedException(); } 232 | public override bool CanSeek { get => throw new NotSupportedException(); } 233 | public override bool CanWrite { get => throw new NotSupportedException(); } 234 | public override void Flush() => throw new NotSupportedException(); 235 | public override long Length { get => throw new NotSupportedException(); } 236 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 237 | public override int Read(byte[] _, int _1, int _2) => throw new NotSupportedException(); 238 | public override long Seek(long _, SeekOrigin _1) => throw new NotSupportedException(); 239 | public override void SetLength(long _) => throw new NotSupportedException(); 240 | public override void Write(byte[] _, int _1, int _2) => throw new NotSupportedException(); 241 | } 242 | } 243 | "); 244 | 245 | [Fact] 246 | public async Task TestGeneratingStreamOwnsCSharp9() 247 | => await RunTest(@"// 248 | namespace MyApp 249 | { 250 | using global::System; 251 | 252 | internal partial class TestDisposable 253 | { 254 | private bool isDisposed; 255 | 256 | internal bool KeepOpen { get; } 257 | 258 | /// 259 | protected override void Dispose(bool disposing) 260 | { 261 | if (!this.isDisposed && disposing) 262 | { 263 | if (!this.KeepOpen) 264 | { 265 | this.testDispose?.Dispose(); 266 | this.testDispose = null; 267 | } 268 | this.testsetnull = null; 269 | this.isDisposed = true; 270 | } 271 | 272 | // On Streams call base.Dispose(disposing)!!! 273 | base.Dispose(disposing); 274 | } 275 | 276 | internal void ThrowIfDisposed() 277 | { 278 | if (this.isDisposed) 279 | { 280 | throw new ObjectDisposedException(nameof(TestDisposable)); 281 | } 282 | } 283 | } 284 | } 285 | ", @"namespace MyApp 286 | { 287 | using System; 288 | using System.IO; 289 | using IDisposableGenerator; 290 | 291 | [GenerateDispose(true)] 292 | internal partial class TestDisposable : Stream 293 | { 294 | [DisposeField(true)] 295 | private IDisposable testDispose; 296 | 297 | [NullOnDispose] 298 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 299 | 300 | public override bool CanRead { get => throw new NotSupportedException(); } 301 | public override bool CanSeek { get => throw new NotSupportedException(); } 302 | public override bool CanWrite { get => throw new NotSupportedException(); } 303 | public override void Flush() => throw new NotSupportedException(); 304 | public override long Length { get => throw new NotSupportedException(); } 305 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 306 | public override int Read(byte[] _, int _1, int _2) => throw new NotSupportedException(); 307 | public override long Seek(long _, SeekOrigin _1) => throw new NotSupportedException(); 308 | public override void SetLength(long _) => throw new NotSupportedException(); 309 | public override void Write(byte[] _, int _1, int _2) => throw new NotSupportedException(); 310 | } 311 | } 312 | "); 313 | 314 | [Fact] 315 | public async Task TestGeneratingCallOnDisposeCSharp9() 316 | => await RunTest(@"// 317 | namespace MyApp 318 | { 319 | using global::System; 320 | 321 | internal partial class TestDisposable : IDisposable 322 | { 323 | private bool isDisposed; 324 | 325 | /// 326 | /// Cleans up the resources used by . 327 | /// 328 | public void Dispose() 329 | { 330 | this.Dispose(true); 331 | GC.SuppressFinalize(this); 332 | } 333 | 334 | private void Dispose(bool disposing) 335 | { 336 | if (!this.isDisposed && disposing) 337 | { 338 | this.TestCallThisOnDispose(); 339 | this.testDispose?.Dispose(); 340 | this.testDispose = null; 341 | this.testsetnull = null; 342 | this.isDisposed = true; 343 | } 344 | } 345 | 346 | internal void ThrowIfDisposed() 347 | { 348 | if (this.isDisposed) 349 | { 350 | throw new ObjectDisposedException(nameof(TestDisposable)); 351 | } 352 | } 353 | } 354 | } 355 | ", @"namespace MyApp 356 | { 357 | using System; 358 | using IDisposableGenerator; 359 | 360 | [GenerateDispose(false)] 361 | internal partial class TestDisposable 362 | { 363 | [DisposeField(false)] 364 | private IDisposable testDispose; 365 | 366 | [NullOnDispose] 367 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 368 | 369 | [CallOnDispose] 370 | private void TestCallThisOnDispose() 371 | { 372 | // Intentionally left this empty (cannot throw exceptions 373 | // here as this is called inside of Dispose(bool)). 374 | } 375 | } 376 | } 377 | "); 378 | 379 | [Fact] 380 | public async Task TestAttributeOnDisposableMemberFromBCLCSharp9() 381 | => await RunTest(@"// 382 | namespace MyApp 383 | { 384 | using global::System; 385 | 386 | internal partial class TestDisposable : IDisposable 387 | { 388 | private bool isDisposed; 389 | 390 | /// 391 | /// Cleans up the resources used by . 392 | /// 393 | public void Dispose() 394 | { 395 | this.Dispose(true); 396 | GC.SuppressFinalize(this); 397 | } 398 | 399 | private void Dispose(bool disposing) 400 | { 401 | if (!this.isDisposed && disposing) 402 | { 403 | this.test = null; 404 | this.isDisposed = true; 405 | } 406 | } 407 | 408 | internal void ThrowIfDisposed() 409 | { 410 | if (this.isDisposed) 411 | { 412 | throw new ObjectDisposedException(nameof(TestDisposable)); 413 | } 414 | } 415 | } 416 | } 417 | ", @"namespace MyApp 418 | { 419 | using System; 420 | using System.ComponentModel.DataAnnotations; 421 | using IDisposableGenerator; 422 | 423 | [GenerateDispose(false)] 424 | internal partial class TestDisposable 425 | { 426 | [NullOnDispose] 427 | [StringLength(50)] 428 | public string? test { get; set; } = ""stuff here.""; 429 | } 430 | } 431 | "); 432 | 433 | [Fact] 434 | public async Task TestWithoutThrowIfDisposedCSharp9() 435 | { 436 | const string generatedSource = """ 437 | // 438 | namespace MyApp 439 | { 440 | using global::System; 441 | 442 | internal partial class TestDisposable : IDisposable 443 | { 444 | private bool isDisposed; 445 | 446 | /// 447 | /// Cleans up the resources used by . 448 | /// 449 | public void Dispose() 450 | { 451 | this.Dispose(true); 452 | GC.SuppressFinalize(this); 453 | } 454 | 455 | private void Dispose(bool disposing) 456 | { 457 | if (!this.isDisposed && disposing) 458 | { 459 | this.test = null; 460 | this.isDisposed = true; 461 | } 462 | } 463 | } 464 | } 465 | 466 | """; 467 | 468 | const string testSource = """ 469 | namespace MyApp 470 | { 471 | using System; 472 | using System.ComponentModel.DataAnnotations; 473 | using IDisposableGenerator; 474 | 475 | [GenerateDispose(false)] 476 | [WithoutThrowIfDisposed] 477 | internal partial class TestDisposable 478 | { 479 | [NullOnDispose] 480 | [StringLength(50)] 481 | public string? test { get; set; } = "stuff here."; 482 | } 483 | } 484 | 485 | """; 486 | 487 | await RunTest(generatedSource, testSource, LanguageVersion.CSharp9); 488 | } 489 | 490 | [Fact] 491 | public async Task TestGeneratingDisposableWithReadonlyFieldsCSharp9() 492 | { 493 | const string generatedSource = """ 494 | // 495 | namespace Test 496 | { 497 | using global::System; 498 | 499 | public partial class LogWriter : IDisposable 500 | { 501 | private bool isDisposed; 502 | 503 | /// 504 | /// Cleans up the resources used by . 505 | /// 506 | public void Dispose() 507 | { 508 | this.Dispose(true); 509 | GC.SuppressFinalize(this); 510 | } 511 | 512 | private void Dispose(bool disposing) 513 | { 514 | if (!this.isDisposed && disposing) 515 | { 516 | this._streamWriter?.Dispose(); 517 | this.isDisposed = true; 518 | } 519 | } 520 | 521 | internal void ThrowIfDisposed() 522 | { 523 | if (this.isDisposed) 524 | { 525 | throw new ObjectDisposedException(nameof(LogWriter)); 526 | } 527 | } 528 | } 529 | } 530 | 531 | """; 532 | 533 | const string testSource = """ 534 | using System; 535 | using System.IO; 536 | using IDisposableGenerator; 537 | 538 | namespace Test 539 | { 540 | [GenerateDispose(false)] 541 | public partial class LogWriter 542 | { 543 | [DisposeField(false)] 544 | private readonly StreamWriter _streamWriter; 545 | 546 | public LogWriter(string path) 547 | { 548 | _streamWriter = new StreamWriter(path); 549 | } 550 | 551 | public void WriteLine(string text) => _streamWriter.WriteLine(text.ToUpper()); 552 | } 553 | } 554 | """; 555 | 556 | await RunTest(generatedSource, testSource, LanguageVersion.CSharp9); 557 | } 558 | 559 | [Fact] 560 | public async Task TestGeneratingDisposableWithoutNamespaceCSharp9() 561 | { 562 | const string generatedSource = """ 563 | // 564 | using global::System; 565 | 566 | public partial class LogWriter : IDisposable 567 | { 568 | private bool isDisposed; 569 | 570 | /// 571 | /// Cleans up the resources used by . 572 | /// 573 | public void Dispose() 574 | { 575 | this.Dispose(true); 576 | GC.SuppressFinalize(this); 577 | } 578 | 579 | private void Dispose(bool disposing) 580 | { 581 | if (!this.isDisposed && disposing) 582 | { 583 | this._streamWriter?.Dispose(); 584 | this.isDisposed = true; 585 | } 586 | } 587 | 588 | internal void ThrowIfDisposed() 589 | { 590 | if (this.isDisposed) 591 | { 592 | throw new ObjectDisposedException(nameof(LogWriter)); 593 | } 594 | } 595 | } 596 | 597 | """; 598 | 599 | const string testSource = """ 600 | using System; 601 | using System.IO; 602 | using IDisposableGenerator; 603 | 604 | [GenerateDispose(false)] 605 | public partial class LogWriter 606 | { 607 | [DisposeField(false)] 608 | private readonly StreamWriter _streamWriter; 609 | 610 | public LogWriter(string path) 611 | { 612 | _streamWriter = new StreamWriter(path); 613 | } 614 | 615 | public void WriteLine(string text) => _streamWriter.WriteLine(text.ToUpper()); 616 | } 617 | """; 618 | 619 | await RunTest(generatedSource, testSource, LanguageVersion.CSharp9); 620 | } 621 | } 622 | -------------------------------------------------------------------------------- /tests/IDisposableGeneratorTests.VisualBasic.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public partial class IDisposableGeneratorTests 4 | { 5 | [Fact] 6 | public async Task TestGeneratingPublicDisposableNotOwnsVisualBasic() 7 | => await RunTest(@"' 8 | Imports System 9 | 10 | Namespace MyApp 11 | 12 | Public Partial Class TestDisposable 13 | Implements IDisposable 14 | 15 | Private isDisposed As Boolean 16 | 17 | ''' 18 | ''' Cleans up the resources used by . 19 | ''' 20 | Public Sub Dispose() Implements IDisposable.Dispose 21 | Me.Dispose(True) 22 | GC.SuppressFinalize(Me) 23 | End Sub 24 | 25 | Private Sub Dispose(ByVal disposing As Boolean) 26 | If Not Me.isDisposed AndAlso disposing Then 27 | Me.testDispose?.Dispose() 28 | Me.testDispose = Nothing 29 | Me.testsetnull = Nothing 30 | Me.isDisposed = True 31 | End If 32 | End Sub 33 | 34 | Friend Sub ThrowIfDisposed() 35 | If Me.isDisposed Then 36 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 37 | End If 38 | End Sub 39 | End Class 40 | End Namespace 41 | ", @"Imports System 42 | Imports IDisposableGenerator 43 | 44 | Namespace MyApp 45 | 46 | 47 | Public Partial Class TestDisposable 48 | 49 | Private testDispose As IDisposable 50 | 51 | 52 | Private testsetnull As Char() = { ""t""c, ""e""c, ""s""c, ""t""c, ""i""c, ""n""c, ""g""c } 53 | End Class 54 | End Namespace 55 | ", null); 56 | 57 | [Fact] 58 | public async Task TestGeneratingDisposableNotOwnsVisualBasic() 59 | => await RunTest(@"' 60 | Imports System 61 | 62 | Namespace MyApp 63 | 64 | Friend Partial Class TestDisposable 65 | Implements IDisposable 66 | 67 | Private isDisposed As Boolean 68 | 69 | ''' 70 | ''' Cleans up the resources used by . 71 | ''' 72 | Public Sub Dispose() Implements IDisposable.Dispose 73 | Me.Dispose(True) 74 | GC.SuppressFinalize(Me) 75 | End Sub 76 | 77 | Private Sub Dispose(ByVal disposing As Boolean) 78 | If Not Me.isDisposed AndAlso disposing Then 79 | Me.testDispose?.Dispose() 80 | Me.testDispose = Nothing 81 | Me.testsetnull = Nothing 82 | Me.isDisposed = True 83 | End If 84 | End Sub 85 | 86 | Friend Sub ThrowIfDisposed() 87 | If Me.isDisposed Then 88 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 89 | End If 90 | End Sub 91 | End Class 92 | End Namespace 93 | ", @"Imports System 94 | Imports IDisposableGenerator 95 | 96 | Namespace MyApp 97 | 98 | 99 | Friend Partial Class TestDisposable 100 | 101 | Private testDispose As IDisposable 102 | 103 | 104 | Private testsetnull As Char() = { ""t""c, ""e""c, ""s""c, ""t""c, ""i""c, ""n""c, ""g""c } 105 | End Class 106 | End Namespace 107 | ", null); 108 | 109 | [Fact] 110 | public async Task TestGeneratingDisposableOwnsVisualBasic() 111 | => await RunTest(@"' 112 | Imports System 113 | 114 | Namespace MyApp 115 | 116 | Friend Partial Class TestDisposable 117 | Implements IDisposable 118 | 119 | Private isDisposed As Boolean 120 | 121 | Friend Property IsOwned As Boolean 122 | 123 | ''' 124 | ''' Cleans up the resources used by . 125 | ''' 126 | Public Sub Dispose() Implements IDisposable.Dispose 127 | Me.Dispose(True) 128 | GC.SuppressFinalize(Me) 129 | End Sub 130 | 131 | Private Sub Dispose(ByVal disposing As Boolean) 132 | If Not Me.isDisposed AndAlso disposing Then 133 | If Me.IsOwned Then 134 | Me.testDispose?.Dispose() 135 | Me.testDispose = Nothing 136 | End If 137 | Me.testsetnull = Nothing 138 | Me.isDisposed = True 139 | End If 140 | End Sub 141 | 142 | Friend Sub ThrowIfDisposed() 143 | If Me.isDisposed Then 144 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 145 | End If 146 | End Sub 147 | End Class 148 | End Namespace 149 | ", @"Imports System 150 | Imports IDisposableGenerator 151 | 152 | Namespace MyApp 153 | 154 | 155 | Friend Partial Class TestDisposable 156 | 157 | Private testDispose As IDisposable 158 | 159 | 160 | Private testsetnull As Char() = { ""t""c, ""e""c, ""s""c, ""t""c, ""i""c, ""n""c, ""g""c } 161 | End Class 162 | End Namespace 163 | ", null); 164 | 165 | [Fact] 166 | public async Task TestGeneratingStreamNotOwnsVisualBasic() 167 | => await RunTest(@"' 168 | Imports System 169 | 170 | Namespace MyApp 171 | 172 | Friend Partial Class TestDisposable 173 | Private isDisposed As Boolean 174 | 175 | ''' 176 | Protected Overrides Sub Dispose(ByVal disposing As Boolean) 177 | If Not Me.isDisposed AndAlso disposing Then 178 | Me.testDispose?.Dispose() 179 | Me.testDispose = Nothing 180 | Me.testsetnull = Nothing 181 | Me.isDisposed = True 182 | End If 183 | 184 | ' On Streams call MyBase.Dispose(disposing)!!! 185 | MyBase.Dispose(disposing) 186 | End Sub 187 | 188 | Friend Sub ThrowIfDisposed() 189 | If Me.isDisposed Then 190 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 191 | End If 192 | End Sub 193 | End Class 194 | End Namespace 195 | ", @"Imports System 196 | Imports System.IO 197 | Imports IDisposableGenerator 198 | 199 | Namespace MyApp 200 | 201 | 202 | Friend Partial Class TestDisposable 203 | Inherits Stream 204 | 205 | 206 | Private testDispose As IDisposable 207 | 208 | 209 | Private testsetnull As Char() = { ""t""c, ""e""c, ""s""c, ""t""c, ""i""c, ""n""c, ""g""c } 210 | 211 | Public Overrides ReadOnly Property CanRead As Boolean 212 | Get 213 | Throw New NotSupportedException() 214 | End Get 215 | End Property 216 | Public Overrides ReadOnly Property CanSeek As Boolean 217 | Get 218 | Throw New NotSupportedException() 219 | End Get 220 | End Property 221 | Public Overrides ReadOnly Property CanWrite As Boolean 222 | Get 223 | Throw New NotSupportedException() 224 | End Get 225 | End Property 226 | Public Overrides Sub Flush() 227 | Throw New NotSupportedException() 228 | End Sub 229 | Public Overrides ReadOnly Property Length As Long 230 | Get 231 | Throw New NotSupportedException() 232 | End Get 233 | End Property 234 | Public Overrides Property Position As Long 235 | Get 236 | Throw New NotSupportedException() 237 | End Get 238 | Set(ByVal value As Long) 239 | Throw New NotSupportedException() 240 | End Set 241 | End Property 242 | Public Overrides Function Read(ByVal __ As Byte(), ByVal _1 As Integer, ByVal _2 As Integer) As Integer 243 | Throw New NotSupportedException() 244 | End Function 245 | Public Overrides Function Seek(ByVal __ As Long, ByVal _1 As SeekOrigin) As Long 246 | Throw New NotSupportedException() 247 | End Function 248 | Public Overrides Sub SetLength(ByVal __ As Long) 249 | Throw New NotSupportedException() 250 | End Sub 251 | Public Overrides Sub Write(ByVal __ As Byte(), ByVal _1 As Integer, ByVal _2 As Integer) 252 | Throw New NotSupportedException() 253 | End Sub 254 | End Class 255 | End Namespace 256 | ", null); 257 | 258 | [Fact] 259 | public async Task TestGeneratingStreamOwnsVisualBasic() 260 | => await RunTest(@"' 261 | Imports System 262 | 263 | Namespace MyApp 264 | 265 | Friend Partial Class TestDisposable 266 | Private isDisposed As Boolean 267 | 268 | Friend ReadOnly Property KeepOpen As Boolean 269 | 270 | ''' 271 | Protected Overrides Sub Dispose(ByVal disposing As Boolean) 272 | If Not Me.isDisposed AndAlso disposing Then 273 | If Not Me.KeepOpen Then 274 | Me.testDispose?.Dispose() 275 | Me.testDispose = Nothing 276 | End If 277 | Me.testsetnull = Nothing 278 | Me.isDisposed = True 279 | End If 280 | 281 | ' On Streams call MyBase.Dispose(disposing)!!! 282 | MyBase.Dispose(disposing) 283 | End Sub 284 | 285 | Friend Sub ThrowIfDisposed() 286 | If Me.isDisposed Then 287 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 288 | End If 289 | End Sub 290 | End Class 291 | End Namespace 292 | ", @"Imports System 293 | Imports System.IO 294 | Imports IDisposableGenerator 295 | 296 | Namespace MyApp 297 | 298 | 299 | Friend Partial Class TestDisposable 300 | Inherits Stream 301 | 302 | 303 | Private testDispose As IDisposable 304 | 305 | 306 | Private testsetnull As Char() = { ""t""c, ""e""c, ""s""c, ""t""c, ""i""c, ""n""c, ""g""c } 307 | 308 | Public Overrides ReadOnly Property CanRead As Boolean 309 | Get 310 | Throw New NotSupportedException() 311 | End Get 312 | End Property 313 | Public Overrides ReadOnly Property CanSeek As Boolean 314 | Get 315 | Throw New NotSupportedException() 316 | End Get 317 | End Property 318 | Public Overrides ReadOnly Property CanWrite As Boolean 319 | Get 320 | Throw New NotSupportedException() 321 | End Get 322 | End Property 323 | Public Overrides Sub Flush() 324 | Throw New NotSupportedException() 325 | End Sub 326 | Public Overrides ReadOnly Property Length As Long 327 | Get 328 | Throw New NotSupportedException() 329 | End Get 330 | End Property 331 | Public Overrides Property Position As Long 332 | Get 333 | Throw New NotSupportedException() 334 | End Get 335 | Set(ByVal value As Long) 336 | Throw New NotSupportedException() 337 | End Set 338 | End Property 339 | Public Overrides Function Read(ByVal __ As Byte(), ByVal _1 As Integer, ByVal _2 As Integer) As Integer 340 | Throw New NotSupportedException() 341 | End Function 342 | Public Overrides Function Seek(ByVal __ As Long, ByVal _1 As SeekOrigin) As Long 343 | Throw New NotSupportedException() 344 | End Function 345 | Public Overrides Sub SetLength(ByVal __ As Long) 346 | Throw New NotSupportedException() 347 | End Sub 348 | Public Overrides Sub Write(ByVal __ As Byte(), ByVal _1 As Integer, ByVal _2 As Integer) 349 | Throw New NotSupportedException() 350 | End Sub 351 | End Class 352 | End Namespace 353 | ", null); 354 | 355 | [Fact] 356 | public async Task TestGeneratingCallOnDisposeVisualBasic() 357 | => await RunTest(@"' 358 | Imports System 359 | 360 | Namespace MyApp 361 | 362 | Friend Partial Class TestDisposable 363 | Implements IDisposable 364 | 365 | Private isDisposed As Boolean 366 | 367 | ''' 368 | ''' Cleans up the resources used by . 369 | ''' 370 | Public Sub Dispose() Implements IDisposable.Dispose 371 | Me.Dispose(True) 372 | GC.SuppressFinalize(Me) 373 | End Sub 374 | 375 | Private Sub Dispose(ByVal disposing As Boolean) 376 | If Not Me.isDisposed AndAlso disposing Then 377 | Me.TestCallThisOnDispose() 378 | Me.testDispose?.Dispose() 379 | Me.testDispose = Nothing 380 | Me.testsetnull = Nothing 381 | Me.isDisposed = True 382 | End If 383 | End Sub 384 | 385 | Friend Sub ThrowIfDisposed() 386 | If Me.isDisposed Then 387 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 388 | End If 389 | End Sub 390 | End Class 391 | End Namespace 392 | ", @"Imports System 393 | Imports IDisposableGenerator 394 | 395 | Namespace MyApp 396 | 397 | Friend Partial Class TestDisposable 398 | 399 | Private testDispose As IDisposable 400 | 401 | 402 | Private testsetnull As Char() = { ""t""c, ""e""c, ""s""c, ""t""c, ""i""c, ""n""c, ""g""c } 403 | 404 | 405 | Private Sub TestCallThisOnDispose() 406 | ' Intentionally left this empty (cannot throw exceptions 407 | ' here as this is called inside of Dispose(Boolean)). 408 | End Sub 409 | End Class 410 | End Namespace 411 | ", null); 412 | 413 | [Fact] 414 | public async Task TestAttributeOnDisposableMemberFromBCLVisualBasic() 415 | => await RunTest(@"' 416 | Imports System 417 | 418 | Namespace MyApp 419 | 420 | Friend Partial Class TestDisposable 421 | Implements IDisposable 422 | 423 | Private isDisposed As Boolean 424 | 425 | ''' 426 | ''' Cleans up the resources used by . 427 | ''' 428 | Public Sub Dispose() Implements IDisposable.Dispose 429 | Me.Dispose(True) 430 | GC.SuppressFinalize(Me) 431 | End Sub 432 | 433 | Private Sub Dispose(ByVal disposing As Boolean) 434 | If Not Me.isDisposed AndAlso disposing Then 435 | Me.test = Nothing 436 | Me.isDisposed = True 437 | End If 438 | End Sub 439 | 440 | Friend Sub ThrowIfDisposed() 441 | If Me.isDisposed Then 442 | Throw New ObjectDisposedException(NameOf(TestDisposable)) 443 | End If 444 | End Sub 445 | End Class 446 | End Namespace 447 | ", @"Imports System 448 | Imports System.ComponentModel.DataAnnotations 449 | Imports IDisposableGenerator 450 | 451 | Namespace MyApp 452 | 453 | 454 | Friend Partial Class TestDisposable 455 | 456 | 457 | Public Property test As String = ""stuff here."" 458 | End Class 459 | End Namespace 460 | ", null); 461 | 462 | [Fact] 463 | public async Task TestWithoutThrowIfDisposedVisualBasic() 464 | { 465 | const string generatedSource = """ 466 | ' 467 | Imports System 468 | 469 | Namespace MyApp 470 | 471 | Friend Partial Class TestDisposable 472 | Implements IDisposable 473 | 474 | Private isDisposed As Boolean 475 | 476 | ''' 477 | ''' Cleans up the resources used by . 478 | ''' 479 | Public Sub Dispose() Implements IDisposable.Dispose 480 | Me.Dispose(True) 481 | GC.SuppressFinalize(Me) 482 | End Sub 483 | 484 | Private Sub Dispose(ByVal disposing As Boolean) 485 | If Not Me.isDisposed AndAlso disposing Then 486 | Me.test = Nothing 487 | Me.isDisposed = True 488 | End If 489 | End Sub 490 | End Class 491 | End Namespace 492 | 493 | """; 494 | 495 | const string testSource = """ 496 | Imports System 497 | Imports System.ComponentModel.DataAnnotations 498 | Imports IDisposableGenerator 499 | 500 | Namespace MyApp 501 | 502 | 503 | Friend Partial Class TestDisposable 504 | 505 | 506 | Public Property test As String = "stuff here." 507 | End Class 508 | End Namespace 509 | 510 | """; 511 | 512 | await RunTest(generatedSource, testSource, null); 513 | } 514 | 515 | [Fact] 516 | public async Task TestGeneratingDisposableWithReadonlyFieldsVisualBasic() 517 | { 518 | const string generatedSource = """ 519 | ' 520 | Imports System 521 | 522 | Namespace Test 523 | 524 | Public Partial Class LogWriter 525 | Implements IDisposable 526 | 527 | Private isDisposed As Boolean 528 | 529 | ''' 530 | ''' Cleans up the resources used by . 531 | ''' 532 | Public Sub Dispose() Implements IDisposable.Dispose 533 | Me.Dispose(True) 534 | GC.SuppressFinalize(Me) 535 | End Sub 536 | 537 | Private Sub Dispose(ByVal disposing As Boolean) 538 | If Not Me.isDisposed AndAlso disposing Then 539 | Me._streamWriter?.Dispose() 540 | Me.isDisposed = True 541 | End If 542 | End Sub 543 | 544 | Friend Sub ThrowIfDisposed() 545 | If Me.isDisposed Then 546 | Throw New ObjectDisposedException(NameOf(LogWriter)) 547 | End If 548 | End Sub 549 | End Class 550 | End Namespace 551 | 552 | """; 553 | 554 | const string testSource = """ 555 | Imports System 556 | Imports System.IO 557 | Imports IDisposableGenerator 558 | 559 | Namespace Test 560 | 561 | Public Partial Class LogWriter 562 | 563 | Private ReadOnly _streamWriter As StreamWriter 564 | 565 | Public Sub New(ByVal path As String) 566 | _streamWriter = New StreamWriter(path) 567 | End Sub 568 | 569 | Public Sub WriteLine(ByVal text As String) 570 | _streamWriter.WriteLine(text.ToUpper()) 571 | End Sub 572 | End Class 573 | End Namespace 574 | """; 575 | 576 | await RunTest(generatedSource, testSource, null); 577 | } 578 | 579 | [Fact] 580 | public async Task TestGeneratingDisposableWithoutNamespaceVisualBasic() 581 | { 582 | const string generatedSource = """ 583 | ' 584 | Imports System 585 | 586 | Public Partial Class LogWriter 587 | Implements IDisposable 588 | 589 | Private isDisposed As Boolean 590 | 591 | ''' 592 | ''' Cleans up the resources used by . 593 | ''' 594 | Public Sub Dispose() Implements IDisposable.Dispose 595 | Me.Dispose(True) 596 | GC.SuppressFinalize(Me) 597 | End Sub 598 | 599 | Private Sub Dispose(ByVal disposing As Boolean) 600 | If Not Me.isDisposed AndAlso disposing Then 601 | Me._streamWriter?.Dispose() 602 | Me.isDisposed = True 603 | End If 604 | End Sub 605 | 606 | Friend Sub ThrowIfDisposed() 607 | If Me.isDisposed Then 608 | Throw New ObjectDisposedException(NameOf(LogWriter)) 609 | End If 610 | End Sub 611 | End Class 612 | 613 | """; 614 | 615 | const string testSource = """ 616 | Imports System 617 | Imports System.IO 618 | Imports IDisposableGenerator 619 | 620 | 621 | Public Partial Class LogWriter 622 | 623 | Private ReadOnly _streamWriter As StreamWriter 624 | 625 | Public Sub New(ByVal path As String) 626 | _streamWriter = New StreamWriter(path) 627 | End Sub 628 | 629 | Public Sub WriteLine(ByVal text As String) 630 | _streamWriter.WriteLine(text.ToUpper()) 631 | End Sub 632 | End Class 633 | """; 634 | 635 | await RunTest(generatedSource, testSource, null); 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /tests/IDisposableGeneratorTests.CSharp10.cs: -------------------------------------------------------------------------------- 1 | namespace IDisposableGenerator.Tests; 2 | 3 | public partial class IDisposableGeneratorTests 4 | { 5 | [Fact] 6 | public async Task TestGeneratingPublicDisposableNotOwnsCSharp10() 7 | => await RunTest(@"// 8 | namespace MyApp; 9 | 10 | public partial class TestDisposable : IDisposable 11 | { 12 | private bool isDisposed; 13 | 14 | /// 15 | /// Cleans up the resources used by . 16 | /// 17 | public void Dispose() 18 | { 19 | this.Dispose(true); 20 | GC.SuppressFinalize(this); 21 | } 22 | 23 | private void Dispose(bool disposing) 24 | { 25 | if (!this.isDisposed && disposing) 26 | { 27 | this.testDispose?.Dispose(); 28 | this.testDispose = null; 29 | this.testsetnull = null; 30 | this.isDisposed = true; 31 | } 32 | } 33 | 34 | internal void ThrowIfDisposed() 35 | { 36 | if (this.isDisposed) 37 | { 38 | throw new ObjectDisposedException(nameof(TestDisposable)); 39 | } 40 | } 41 | } 42 | ", @"global using System; 43 | global using IDisposableGenerator; 44 | 45 | namespace MyApp; 46 | 47 | [GenerateDispose(false)] 48 | public partial class TestDisposable 49 | { 50 | [DisposeField(false)] 51 | private IDisposable testDispose; 52 | 53 | [NullOnDispose] 54 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 55 | } 56 | ", LanguageVersion.CSharp10); 57 | 58 | [Fact] 59 | public async Task TestGeneratingDisposableNotOwnsCSharp10() 60 | => await RunTest(@"// 61 | namespace MyApp; 62 | 63 | internal partial class TestDisposable : IDisposable 64 | { 65 | private bool isDisposed; 66 | 67 | /// 68 | /// Cleans up the resources used by . 69 | /// 70 | public void Dispose() 71 | { 72 | this.Dispose(true); 73 | GC.SuppressFinalize(this); 74 | } 75 | 76 | private void Dispose(bool disposing) 77 | { 78 | if (!this.isDisposed && disposing) 79 | { 80 | this.testDispose?.Dispose(); 81 | this.testDispose = null; 82 | this.testsetnull = null; 83 | this.isDisposed = true; 84 | } 85 | } 86 | 87 | internal void ThrowIfDisposed() 88 | { 89 | if (this.isDisposed) 90 | { 91 | throw new ObjectDisposedException(nameof(TestDisposable)); 92 | } 93 | } 94 | } 95 | ", @"global using System; 96 | global using IDisposableGenerator; 97 | 98 | namespace MyApp; 99 | 100 | [GenerateDispose(false)] 101 | internal partial class TestDisposable 102 | { 103 | [DisposeField(false)] 104 | private IDisposable testDispose; 105 | 106 | [NullOnDispose] 107 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 108 | } 109 | ", LanguageVersion.CSharp10); 110 | 111 | [Fact] 112 | public async Task TestGeneratingDisposableOwnsCSharp10() 113 | => await RunTest(@"// 114 | namespace MyApp; 115 | 116 | internal partial class TestDisposable : IDisposable 117 | { 118 | private bool isDisposed; 119 | 120 | internal bool IsOwned { get; set; } 121 | 122 | /// 123 | /// Cleans up the resources used by . 124 | /// 125 | public void Dispose() 126 | { 127 | this.Dispose(true); 128 | GC.SuppressFinalize(this); 129 | } 130 | 131 | private void Dispose(bool disposing) 132 | { 133 | if (!this.isDisposed && disposing) 134 | { 135 | if (this.IsOwned) 136 | { 137 | this.testDispose?.Dispose(); 138 | this.testDispose = null; 139 | } 140 | this.testsetnull = null; 141 | this.isDisposed = true; 142 | } 143 | } 144 | 145 | internal void ThrowIfDisposed() 146 | { 147 | if (this.isDisposed) 148 | { 149 | throw new ObjectDisposedException(nameof(TestDisposable)); 150 | } 151 | } 152 | } 153 | ", @"global using System; 154 | global using IDisposableGenerator; 155 | 156 | namespace MyApp; 157 | 158 | [GenerateDispose(false)] 159 | internal partial class TestDisposable 160 | { 161 | [DisposeField(true)] 162 | private IDisposable testDispose; 163 | 164 | [NullOnDispose] 165 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 166 | } 167 | ", LanguageVersion.CSharp10); 168 | 169 | [Fact] 170 | public async Task TestGeneratingStreamNotOwnsCSharp10() 171 | => await RunTest(@"// 172 | namespace MyApp; 173 | 174 | internal partial class TestDisposable 175 | { 176 | private bool isDisposed; 177 | 178 | /// 179 | protected override void Dispose(bool disposing) 180 | { 181 | if (!this.isDisposed && disposing) 182 | { 183 | this.testDispose?.Dispose(); 184 | this.testDispose = null; 185 | this.testsetnull = null; 186 | this.isDisposed = true; 187 | } 188 | 189 | // On Streams call base.Dispose(disposing)!!! 190 | base.Dispose(disposing); 191 | } 192 | 193 | internal void ThrowIfDisposed() 194 | { 195 | if (this.isDisposed) 196 | { 197 | throw new ObjectDisposedException(nameof(TestDisposable)); 198 | } 199 | } 200 | } 201 | ", @"global using System; 202 | global using System.IO; 203 | global using IDisposableGenerator; 204 | 205 | namespace MyApp; 206 | 207 | [GenerateDispose(true)] 208 | internal partial class TestDisposable : Stream 209 | { 210 | [DisposeField(false)] 211 | private IDisposable testDispose; 212 | 213 | [NullOnDispose] 214 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 215 | 216 | public override bool CanRead { get => throw new NotSupportedException(); } 217 | public override bool CanSeek { get => throw new NotSupportedException(); } 218 | public override bool CanWrite { get => throw new NotSupportedException(); } 219 | public override void Flush() => throw new NotSupportedException(); 220 | public override long Length { get => throw new NotSupportedException(); } 221 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 222 | public override int Read(byte[] _, int _1, int _2) => throw new NotSupportedException(); 223 | public override long Seek(long _, SeekOrigin _1) => throw new NotSupportedException(); 224 | public override void SetLength(long _) => throw new NotSupportedException(); 225 | public override void Write(byte[] _, int _1, int _2) => throw new NotSupportedException(); 226 | } 227 | ", LanguageVersion.CSharp10); 228 | 229 | [Fact] 230 | public async Task TestGeneratingStreamOwnsCSharp10() 231 | => await RunTest(@"// 232 | namespace MyApp; 233 | 234 | internal partial class TestDisposable 235 | { 236 | private bool isDisposed; 237 | 238 | internal bool KeepOpen { get; } 239 | 240 | /// 241 | protected override void Dispose(bool disposing) 242 | { 243 | if (!this.isDisposed && disposing) 244 | { 245 | if (!this.KeepOpen) 246 | { 247 | this.testDispose?.Dispose(); 248 | this.testDispose = null; 249 | } 250 | this.testsetnull = null; 251 | this.isDisposed = true; 252 | } 253 | 254 | // On Streams call base.Dispose(disposing)!!! 255 | base.Dispose(disposing); 256 | } 257 | 258 | internal void ThrowIfDisposed() 259 | { 260 | if (this.isDisposed) 261 | { 262 | throw new ObjectDisposedException(nameof(TestDisposable)); 263 | } 264 | } 265 | } 266 | ", @"global using System; 267 | global using System.IO; 268 | global using IDisposableGenerator; 269 | 270 | namespace MyApp; 271 | 272 | [GenerateDispose(true)] 273 | internal partial class TestDisposable : Stream 274 | { 275 | [DisposeField(true)] 276 | private IDisposable testDispose; 277 | 278 | [NullOnDispose] 279 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 280 | 281 | public override bool CanRead { get => throw new NotSupportedException(); } 282 | public override bool CanSeek { get => throw new NotSupportedException(); } 283 | public override bool CanWrite { get => throw new NotSupportedException(); } 284 | public override void Flush() => throw new NotSupportedException(); 285 | public override long Length { get => throw new NotSupportedException(); } 286 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 287 | public override int Read(byte[] _, int _1, int _2) => throw new NotSupportedException(); 288 | public override long Seek(long _, SeekOrigin _1) => throw new NotSupportedException(); 289 | public override void SetLength(long _) => throw new NotSupportedException(); 290 | public override void Write(byte[] _, int _1, int _2) => throw new NotSupportedException(); 291 | } 292 | ", LanguageVersion.CSharp10); 293 | 294 | [Fact] 295 | public async Task TestGeneratingCallOnDisposeCSharp10() 296 | => await RunTest(@"// 297 | namespace MyApp; 298 | 299 | internal partial class TestDisposable : IDisposable 300 | { 301 | private bool isDisposed; 302 | 303 | /// 304 | /// Cleans up the resources used by . 305 | /// 306 | public void Dispose() 307 | { 308 | this.Dispose(true); 309 | GC.SuppressFinalize(this); 310 | } 311 | 312 | private void Dispose(bool disposing) 313 | { 314 | if (!this.isDisposed && disposing) 315 | { 316 | this.TestCallThisOnDispose(); 317 | this.testDispose?.Dispose(); 318 | this.testDispose = null; 319 | this.testsetnull = null; 320 | this.isDisposed = true; 321 | } 322 | } 323 | 324 | internal void ThrowIfDisposed() 325 | { 326 | if (this.isDisposed) 327 | { 328 | throw new ObjectDisposedException(nameof(TestDisposable)); 329 | } 330 | } 331 | } 332 | ", @"global using System; 333 | global using IDisposableGenerator; 334 | 335 | namespace MyApp; 336 | 337 | [GenerateDispose(false)] 338 | internal partial class TestDisposable 339 | { 340 | [DisposeField(false)] 341 | private IDisposable testDispose; 342 | 343 | [NullOnDispose] 344 | char[] testsetnull = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; 345 | 346 | [CallOnDispose] 347 | private void TestCallThisOnDispose() 348 | { 349 | // Intentionally left this empty (cannot throw exceptions 350 | // here as this is called inside of Dispose(bool)). 351 | } 352 | } 353 | ", LanguageVersion.CSharp10); 354 | 355 | [Fact] 356 | public async Task TestAttributeOnDisposableMemberFromBCLCSharp10() 357 | => await RunTest(@"// 358 | namespace MyApp; 359 | 360 | internal partial class TestDisposable : IDisposable 361 | { 362 | private bool isDisposed; 363 | 364 | /// 365 | /// Cleans up the resources used by . 366 | /// 367 | public void Dispose() 368 | { 369 | this.Dispose(true); 370 | GC.SuppressFinalize(this); 371 | } 372 | 373 | private void Dispose(bool disposing) 374 | { 375 | if (!this.isDisposed && disposing) 376 | { 377 | this.test = null; 378 | this.isDisposed = true; 379 | } 380 | } 381 | 382 | internal void ThrowIfDisposed() 383 | { 384 | if (this.isDisposed) 385 | { 386 | throw new ObjectDisposedException(nameof(TestDisposable)); 387 | } 388 | } 389 | } 390 | ", @"global using System; 391 | global using System.ComponentModel.DataAnnotations; 392 | global using IDisposableGenerator; 393 | 394 | namespace MyApp; 395 | 396 | [GenerateDispose(false)] 397 | internal partial class TestDisposable 398 | { 399 | [NullOnDispose] 400 | [StringLength(50)] 401 | public string? test { get; set; } = ""stuff here.""; 402 | } 403 | ", LanguageVersion.CSharp10); 404 | 405 | [Fact] 406 | public async Task TestGeneratingDisposableWithMultipleNamespacesCSharp10() 407 | { 408 | var testSources = new List 409 | { 410 | @"namespace MyApp.AnotherNamespace; 411 | 412 | [GenerateDispose(false)] 413 | internal partial class AnotherDisposable 414 | { 415 | } 416 | ", 417 | }; 418 | var generatedSources = new Dictionary 419 | { 420 | {"Disposables.0.g.cs", @"// 421 | namespace MyApp; 422 | 423 | internal partial class TestDisposable : IDisposable 424 | { 425 | private bool isDisposed; 426 | 427 | /// 428 | /// Cleans up the resources used by . 429 | /// 430 | public void Dispose() 431 | { 432 | this.Dispose(true); 433 | GC.SuppressFinalize(this); 434 | } 435 | 436 | private void Dispose(bool disposing) 437 | { 438 | if (!this.isDisposed && disposing) 439 | { 440 | this.test = null; 441 | this.isDisposed = true; 442 | } 443 | } 444 | 445 | internal void ThrowIfDisposed() 446 | { 447 | if (this.isDisposed) 448 | { 449 | throw new ObjectDisposedException(nameof(TestDisposable)); 450 | } 451 | } 452 | } 453 | "}, 454 | {"Disposables.1.g.cs", @"// 455 | namespace MyApp.AnotherNamespace; 456 | 457 | internal partial class AnotherDisposable : IDisposable 458 | { 459 | private bool isDisposed; 460 | 461 | /// 462 | /// Cleans up the resources used by . 463 | /// 464 | public void Dispose() 465 | { 466 | this.Dispose(true); 467 | GC.SuppressFinalize(this); 468 | } 469 | 470 | private void Dispose(bool disposing) 471 | { 472 | if (!this.isDisposed && disposing) 473 | { 474 | this.isDisposed = true; 475 | } 476 | } 477 | 478 | internal void ThrowIfDisposed() 479 | { 480 | if (this.isDisposed) 481 | { 482 | throw new ObjectDisposedException(nameof(AnotherDisposable)); 483 | } 484 | } 485 | } 486 | "} 487 | }; 488 | 489 | await RunTest(@"// 490 | namespace MyApp; 491 | 492 | internal partial class TestDisposable : IDisposable 493 | { 494 | private bool isDisposed; 495 | 496 | /// 497 | /// Cleans up the resources used by . 498 | /// 499 | public void Dispose() => this.Dispose(true); 500 | 501 | private void Dispose(bool disposing) 502 | { 503 | if (!this.isDisposed && disposing) 504 | { 505 | this.test = null; 506 | this.isDisposed = true; 507 | } 508 | } 509 | 510 | internal void ThrowIfDisposed() 511 | { 512 | if (this.isDisposed) 513 | { 514 | throw new ObjectDisposedException(nameof(TestDisposable)); 515 | } 516 | } 517 | } 518 | ", @"global using System; 519 | global using System.ComponentModel.DataAnnotations; 520 | global using IDisposableGenerator; 521 | 522 | namespace MyApp; 523 | 524 | [GenerateDispose(false)] 525 | internal partial class TestDisposable 526 | { 527 | [NullOnDispose] 528 | public string? test { get; set; } = ""stuff here.""; 529 | } 530 | ", LanguageVersion.CSharp10, testSources, generatedSources); 531 | } 532 | 533 | [Fact] 534 | public async Task TestWithoutThrowIfDisposedCSharp10() 535 | { 536 | const string generatedSource = """ 537 | // 538 | namespace MyApp; 539 | 540 | internal partial class TestDisposable : IDisposable 541 | { 542 | private bool isDisposed; 543 | 544 | /// 545 | /// Cleans up the resources used by . 546 | /// 547 | public void Dispose() 548 | { 549 | this.Dispose(true); 550 | GC.SuppressFinalize(this); 551 | } 552 | 553 | private void Dispose(bool disposing) 554 | { 555 | if (!this.isDisposed && disposing) 556 | { 557 | this.test = null; 558 | this.isDisposed = true; 559 | } 560 | } 561 | } 562 | 563 | """; 564 | 565 | const string testSource = """ 566 | global using System; 567 | global using System.ComponentModel.DataAnnotations; 568 | global using IDisposableGenerator; 569 | 570 | namespace MyApp; 571 | 572 | [GenerateDispose(false)] 573 | [WithoutThrowIfDisposed] 574 | internal partial class TestDisposable 575 | { 576 | [NullOnDispose] 577 | public string? test { get; set; } = "stuff here."; 578 | } 579 | 580 | """; 581 | 582 | await RunTest(generatedSource, testSource, LanguageVersion.CSharp10); 583 | } 584 | 585 | [Fact] 586 | public async Task TestGeneratingDisposableWithReadonlyFieldsCSharp10() 587 | { 588 | const string generatedSource = """ 589 | // 590 | namespace Test; 591 | 592 | public partial class LogWriter : IDisposable 593 | { 594 | private bool isDisposed; 595 | 596 | /// 597 | /// Cleans up the resources used by . 598 | /// 599 | public void Dispose() 600 | { 601 | this.Dispose(true); 602 | GC.SuppressFinalize(this); 603 | } 604 | 605 | private void Dispose(bool disposing) 606 | { 607 | if (!this.isDisposed && disposing) 608 | { 609 | this._streamWriter?.Dispose(); 610 | this.isDisposed = true; 611 | } 612 | } 613 | 614 | internal void ThrowIfDisposed() 615 | { 616 | if (this.isDisposed) 617 | { 618 | throw new ObjectDisposedException(nameof(LogWriter)); 619 | } 620 | } 621 | } 622 | 623 | """; 624 | 625 | const string testSource = """ 626 | global using System; 627 | global using System.IO; 628 | global using IDisposableGenerator; 629 | 630 | namespace Test; 631 | 632 | [GenerateDispose(false)] 633 | public partial class LogWriter 634 | { 635 | [DisposeField(false)] 636 | private readonly StreamWriter _streamWriter; 637 | 638 | public LogWriter(string path) 639 | { 640 | _streamWriter = new StreamWriter(path); 641 | } 642 | 643 | public void WriteLine(string text) => _streamWriter.WriteLine(text.ToUpper()); 644 | } 645 | """; 646 | 647 | await RunTest(generatedSource, testSource, LanguageVersion.CSharp10); 648 | } 649 | 650 | [Fact] 651 | public async Task TestGeneratingDisposableWithoutNamespaceCSharp10() 652 | { 653 | const string generatedSource = """ 654 | // 655 | 656 | public partial class LogWriter : IDisposable 657 | { 658 | private bool isDisposed; 659 | 660 | /// 661 | /// Cleans up the resources used by . 662 | /// 663 | public void Dispose() 664 | { 665 | this.Dispose(true); 666 | GC.SuppressFinalize(this); 667 | } 668 | 669 | private void Dispose(bool disposing) 670 | { 671 | if (!this.isDisposed && disposing) 672 | { 673 | this._streamWriter?.Dispose(); 674 | this.isDisposed = true; 675 | } 676 | } 677 | 678 | internal void ThrowIfDisposed() 679 | { 680 | if (this.isDisposed) 681 | { 682 | throw new ObjectDisposedException(nameof(LogWriter)); 683 | } 684 | } 685 | } 686 | 687 | """; 688 | 689 | const string testSource = """ 690 | global using System; 691 | global using System.IO; 692 | global using IDisposableGenerator; 693 | 694 | [GenerateDispose(false)] 695 | public partial class LogWriter 696 | { 697 | [DisposeField(false)] 698 | private readonly StreamWriter _streamWriter; 699 | 700 | public LogWriter(string path) 701 | { 702 | _streamWriter = new StreamWriter(path); 703 | } 704 | 705 | public void WriteLine(string text) => _streamWriter.WriteLine(text.ToUpper()); 706 | } 707 | """; 708 | 709 | await RunTest(generatedSource, testSource, LanguageVersion.CSharp10); 710 | } 711 | } 712 | --------------------------------------------------------------------------------