├── 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 | [](https://www.codacy.com/gh/Elskom/IDisposableGenerator/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Elskom/IDisposableGenerator&utm_campaign=Badge_Grade)
24 | [](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 | [](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 |
--------------------------------------------------------------------------------