├── ValueChangedGenerator
├── ValueChangedGenerator
│ ├── ValueChangedGenerator.Vsix
│ │ ├── Screenshot.png
│ │ ├── source.extension.vsixmanifest
│ │ └── ValueChangedGenerator.Vsix.csproj
│ ├── ValueChangedGenerator
│ │ ├── AnalyzerReleases.Shipped.md
│ │ ├── AnalyzerReleases.Unshipped.md
│ │ ├── ValueChangedGenerator.csproj
│ │ ├── SyntaxExtensions.cs
│ │ ├── ValueChangedGeneratorAnalyzer.cs
│ │ ├── RecordDefinition.cs
│ │ ├── Resources.Designer.cs
│ │ ├── Resources.resx
│ │ └── ValueChangedGeneratorCodeFixProvider.cs
│ ├── ValueChangedGenerator.SourceGenerator
│ │ ├── StringExtensions.cs
│ │ ├── RoslynExtensions.cs
│ │ ├── ValueChangedGenerator.SourceGenerator.csproj
│ │ ├── SourceGenerator.cs
│ │ └── Generator.cs
│ ├── ValueChangedGenerator.Package
│ │ ├── tools
│ │ │ ├── install.ps1
│ │ │ └── uninstall.ps1
│ │ └── ValueChangedGenerator.Package.csproj
│ └── ValueChangedGenerator.Test
│ │ ├── ValueChangedGenerator.Test.csproj
│ │ └── ValueChangedGeneratorUnitTests.cs
├── NuGet.Config
└── ValueChangedGenerator.sln
├── LICENSE
├── README.md
└── .gitignore
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Vsix/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ufcpp/ValueChangedGenerator/HEAD/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Vsix/Screenshot.png
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/AnalyzerReleases.Shipped.md:
--------------------------------------------------------------------------------
1 | ; Shipped analyzer releases
2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3 |
4 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.SourceGenerator/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace ValueChangedGenerator
2 | {
3 | internal static class StringExtensions
4 | {
5 | public static string? NullIfEmpty(this string? value) => string.IsNullOrEmpty(value) ? null : value;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/AnalyzerReleases.Unshipped.md:
--------------------------------------------------------------------------------
1 | ; Unshipped analyzer release
2 | ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3 |
4 | ### New Rules
5 | Rule ID | Category | Severity | Notes
6 | --------|----------|----------|-------
7 | ValueChangedGenerator | Naming | Info | ValueChangedGeneratorAnalyzer
--------------------------------------------------------------------------------
/ValueChangedGenerator/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Nobuyuki Iwanaga
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 |
23 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.SourceGenerator/RoslynExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Microsoft.CodeAnalysis;
3 |
4 | namespace ValueChangedGenerator
5 | {
6 | internal static class RoslynExtensions
7 | {
8 | // Code from: https://github.com/YairHalberstadt/stronginject/blob/779a38e7e74b92c87c86ded5d1fef55744d34a83/StrongInject/Generator/RoslynExtensions.cs#L166
9 | public static string FullName(this INamespaceSymbol @namespace) => @namespace.ToDisplayString(new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces));
10 |
11 | // Code from: https://github.com/YairHalberstadt/stronginject/blob/779a38e7e74b92c87c86ded5d1fef55744d34a83/StrongInject/Generator/RoslynExtensions.cs#L69
12 | public static IEnumerable GetContainingTypesAndThis(this INamedTypeSymbol? namedType)
13 | {
14 | var current = namedType;
15 | while (current != null)
16 | {
17 | yield return current;
18 | current = current.ContainingType;
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | preview
7 | enable
8 |
9 |
10 | *$(MSBuildProjectFullPath)*
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/SyntaxExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Microsoft.CodeAnalysis;
3 | using Microsoft.CodeAnalysis.CSharp;
4 | using Microsoft.CodeAnalysis.CSharp.Syntax;
5 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
6 |
7 | namespace ValueChangedGenerator
8 | {
9 | static class SyntaxExtensions
10 | {
11 | private static readonly SyntaxToken PartialToken = Token(SyntaxKind.PartialKeyword);
12 |
13 | public static ClassDeclarationSyntax AddPartialModifier(this ClassDeclarationSyntax typeDecl)
14 | {
15 | if (typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) return typeDecl;
16 | return typeDecl.AddModifiers(new[] { PartialToken });
17 | }
18 |
19 | public static ClassDeclarationSyntax GetPartialTypeDelaration(this ClassDeclarationSyntax typeDecl)
20 | => CSharpSyntaxTree.ParseText($@"
21 | partial class {GetGenericTypeName(typeDecl)}
22 | {{
23 | }}
24 | ").GetRoot().ChildNodes().OfType().First();
25 |
26 | private static string GetGenericTypeName(TypeDeclarationSyntax typeDecl)
27 | {
28 | if (typeDecl.TypeParameterList == null)
29 | {
30 | return typeDecl.Identifier.Text;
31 | }
32 |
33 | return typeDecl.Identifier.Text + "<" +
34 | string.Join(", ", typeDecl.TypeParameterList.Parameters.Select(p => p.Identifier.Text)) +
35 | ">";
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Vsix/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ValueChangedGenerator
6 | Roslyn Code Fix provider for generating PropertyChanged from inner struct members.
7 | Screenshot.png
8 | analyzers,Code Fix,Code Generator,INotifyPropertyChanged,PropertyChanged
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.SourceGenerator/ValueChangedGenerator.SourceGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | preview
6 | enable
7 | ValueChangedGenerator
8 |
9 | ValueChangedGenerator.SourceGenerator
10 | Nobuyuki Iwanaga
11 | MIT
12 | https://github.com/ufcpp/ValueChangedGenerator
13 | git
14 | false
15 | C# (Roslyn) Source Generator for generating PropertyChanged from inner struct members.
16 |
17 | false
18 | $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Package/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | if($project.Object.SupportsPackageDependencyResolution)
4 | {
5 | if($project.Object.SupportsPackageDependencyResolution())
6 | {
7 | # Do not install analyzers via install.ps1, instead let the project system handle it.
8 | return
9 | }
10 | }
11 |
12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
13 |
14 | foreach($analyzersPath in $analyzersPaths)
15 | {
16 | if (Test-Path $analyzersPath)
17 | {
18 | # Install the language agnostic analyzers.
19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
20 | {
21 | if($project.Object.AnalyzerReferences)
22 | {
23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
24 | }
25 | }
26 | }
27 | }
28 |
29 | # $project.Type gives the language name like (C# or VB.NET)
30 | $languageFolder = ""
31 | if($project.Type -eq "C#")
32 | {
33 | $languageFolder = "cs"
34 | }
35 | if($project.Type -eq "VB.NET")
36 | {
37 | $languageFolder = "vb"
38 | }
39 | if($languageFolder -eq "")
40 | {
41 | return
42 | }
43 |
44 | foreach($analyzersPath in $analyzersPaths)
45 | {
46 | # Install language specific analyzers.
47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder
48 | if (Test-Path $languageAnalyzersPath)
49 | {
50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
51 | {
52 | if($project.Object.AnalyzerReferences)
53 | {
54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Test/ValueChangedGenerator.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | false
6 | preview
7 | enable
8 | NU1701
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 | all
25 |
26 |
27 | runtime; build; native; contentfiles; analyzers; buildtransitive
28 | all
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Package/ValueChangedGenerator.Package.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | true
7 | true
8 |
9 |
10 |
11 | ValueChangedGenerator
12 | 1.0.0.0
13 | Nobuyuki Iwanaga
14 | https://github.com/ufcpp/ValueChangedGenerator/blob/master/LICENSE
15 | https://github.com/ufcpp/ValueChangedGenerator/
16 | https://github.com/ufcpp/ValueChangedGenerator/
17 | false
18 | Roslyn Code Fix provider for generating PropertyChanged from inner struct members.
19 | renew with the latest Roslyn SDK.
20 | Nobuyuki Iwanaga
21 | ValueChangedGenerator, analyzers
22 | true
23 |
24 | $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Package/tools/uninstall.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 |
3 | if($project.Object.SupportsPackageDependencyResolution)
4 | {
5 | if($project.Object.SupportsPackageDependencyResolution())
6 | {
7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
8 | return
9 | }
10 | }
11 |
12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
13 |
14 | foreach($analyzersPath in $analyzersPaths)
15 | {
16 | # Uninstall the language agnostic analyzers.
17 | if (Test-Path $analyzersPath)
18 | {
19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
20 | {
21 | if($project.Object.AnalyzerReferences)
22 | {
23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
24 | }
25 | }
26 | }
27 | }
28 |
29 | # $project.Type gives the language name like (C# or VB.NET)
30 | $languageFolder = ""
31 | if($project.Type -eq "C#")
32 | {
33 | $languageFolder = "cs"
34 | }
35 | if($project.Type -eq "VB.NET")
36 | {
37 | $languageFolder = "vb"
38 | }
39 | if($languageFolder -eq "")
40 | {
41 | return
42 | }
43 |
44 | foreach($analyzersPath in $analyzersPaths)
45 | {
46 | # Uninstall language specific analyzers.
47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder
48 | if (Test-Path $languageAnalyzersPath)
49 | {
50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
51 | {
52 | if($project.Object.AnalyzerReferences)
53 | {
54 | try
55 | {
56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
57 | }
58 | catch
59 | {
60 |
61 | }
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Vsix/ValueChangedGenerator.Vsix.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | net6.0
7 | ValueChangedGenerator.Vsix
8 | ValueChangedGenerator.Vsix
9 |
10 |
11 |
12 | false
13 | false
14 | false
15 | false
16 | false
17 | false
18 | Roslyn
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Program
27 | $(DevEnvDir)devenv.exe
28 | /rootsuffix $(VSSDKTargetPlatformRegRootSuffix)
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Always
42 | true
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ValueChangedGenerator
2 |
3 | A Roslyn Code Fix provider for generating PropertyChanged from inner struct members.
4 |
5 | This inlcudes VSIX and NuGet packages of an analyzer created by using .NET Compiler Platform (Roslyn).
6 |
7 | Code Fix version (C# 6.0)
8 |
9 | - VSIX: https://visualstudiogallery.msdn.microsoft.com/bf5dbce2-b73d-4f5f-8e1c-0e1de386b7ce
10 | - NuGet: https://www.nuget.org/packages/ValueChangedGenerator/
11 |
12 | Source Generator version (C# 9.0)
13 |
14 | - NuGet: https://www.nuget.org/packages/ValueChangedGenerator.SourceGenerator/
15 |
16 | ## Usage
17 |
18 | - Insert an inner struct named `NotifyRecord` into a class that you want to generete its properties.
19 | - Use 'Quick Action' (Lightbulb) to fix the code
20 |
21 | 
22 |
23 | ## Sample
24 |
25 | original source:
26 |
27 | ```cs
28 | class Point : BindableBase
29 | {
30 | struct NotifyRecord
31 | {
32 | public int X;
33 | public int Y;
34 | public int Z => X * Y;
35 |
36 | ///
37 | /// Name.
38 | ///
39 | public string Name;
40 | }
41 | }
42 | ```
43 |
44 | fixed result of the original source (`partial` modifier added):
45 |
46 | ```cs
47 | partial class Point : BindableBase
48 | {
49 | struct NotifyRecord
50 | {
51 | public int X;
52 | public int Y;
53 | public int Z => X * Y;
54 |
55 | ///
56 | /// Name.
57 | ///
58 | public string Name;
59 | }
60 | }
61 | ```
62 |
63 | the generetated source code:
64 |
65 | ```cs
66 | using System.ComponentModel;
67 |
68 | partial class Point
69 | {
70 | private NotifyRecord _value;
71 |
72 | public int X { get { return _value.X; } set { SetProperty(ref _value.X, value, XProperty); OnPropertyChanged(ZProperty); } }
73 | private static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X));
74 | public int Y { get { return _value.Y; } set { SetProperty(ref _value.Y, value, YProperty); OnPropertyChanged(ZProperty); } }
75 | private static readonly PropertyChangedEventArgs YProperty = new PropertyChangedEventArgs(nameof(Y));
76 |
77 | ///
78 | /// Name.
79 | ///
80 | public string Name { get { return _value.Name; } set { SetProperty(ref _value.Name, value, NameProperty); } }
81 | private static readonly PropertyChangedEventArgs NameProperty = new PropertyChangedEventArgs(nameof(Name));
82 | public int Z => _value.Z;
83 | private static readonly PropertyChangedEventArgs ZProperty = new PropertyChangedEventArgs(nameof(Z));
84 | }
85 | ```
86 |
87 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGeneratorAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Linq;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Diagnostics;
7 |
8 | namespace ValueChangedGenerator
9 | {
10 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
11 | public class ValueChangedGeneratorAnalyzer : DiagnosticAnalyzer
12 | {
13 | public const string DiagnosticId = "ValueChangedGenerator";
14 |
15 | // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
16 | // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
17 | private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
18 | private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
19 | private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
20 | private const string Category = "Naming";
21 |
22 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, isEnabledByDefault: true, description: Description);
23 |
24 | public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
25 |
26 | public override void Initialize(AnalysisContext context)
27 | {
28 | context.EnableConcurrentExecution();
29 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
30 | context.RegisterSyntaxNodeAction(AnalyzerSyntax, SyntaxKind.StructDeclaration);
31 | }
32 |
33 | private void AnalyzerSyntax(SyntaxNodeAnalysisContext context)
34 | {
35 | if (context.Node is not StructDeclarationSyntax s) return;
36 |
37 | var parent = s.FirstAncestorOrSelf();
38 |
39 | if (parent is null) return;
40 |
41 | var name = s.Identifier.Text;
42 | if (name != "NotifyRecord") return;
43 |
44 | if (!parent.ChildNodes().Any(n => n == s))
45 | return;
46 |
47 | var diagnostic = Diagnostic.Create(Rule, s.GetLocation(), parent.Identifier.Text);
48 | context.ReportDiagnostic(diagnostic);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/RecordDefinition.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.CSharp.Syntax;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace ValueChangedGenerator
8 | {
9 | public class RecordDefinition
10 | {
11 | public IReadOnlyList Properties { get; }
12 |
13 | public IReadOnlyList DependentProperties { get; }
14 |
15 | public RecordDefinition(StructDeclarationSyntax decl)
16 | {
17 | Properties = SimpleProperty.New(decl).ToArray();
18 | DependentProperties = DependentProperty.New(decl, Properties).ToArray();
19 | }
20 | }
21 |
22 | public class SimpleProperty
23 | {
24 | public TypeSyntax Type { get; }
25 | public string Name { get; }
26 | public SyntaxTriviaList LeadingTrivia { get; }
27 | public SyntaxTriviaList TrailingTrivia { get; }
28 |
29 | public IEnumerable Dependents => _dependents;
30 | private readonly List _dependents = new List();
31 |
32 | public SimpleProperty(FieldDeclarationSyntax d)
33 | {
34 | Type = d.Declaration.Type;
35 | Name = d.Declaration.Variables[0].Identifier.Text;
36 | LeadingTrivia = d.GetLeadingTrivia();
37 | TrailingTrivia = d.GetTrailingTrivia();
38 | }
39 |
40 | public static IEnumerable New(StructDeclarationSyntax decl)
41 | => decl.Members.OfType().Select(d => new SimpleProperty(d));
42 |
43 | internal void AddDependent(DependentProperty dp) => _dependents.Add(dp);
44 | }
45 |
46 | public class DependentProperty
47 | {
48 | public TypeSyntax Type { get; }
49 | public string Name { get; }
50 | public SyntaxTriviaList LeadingTrivia { get; }
51 | public SyntaxTriviaList TrailingTrivia { get; }
52 | public IEnumerable DependsOn { get; }
53 |
54 | public DependentProperty(PropertyDeclarationSyntax d, IEnumerable simpleProperties)
55 | {
56 | Type = d.Type;
57 | Name = d.Identifier.Text;
58 | LeadingTrivia = d.GetLeadingTrivia();
59 | TrailingTrivia = d.GetTrailingTrivia();
60 | DependsOn = GetDependsOn(d, simpleProperties).ToArray();
61 | }
62 |
63 | public static IEnumerable New(StructDeclarationSyntax decl, IEnumerable simpleProperties)
64 | => decl.Members.OfType().Select(d => new DependentProperty(d, simpleProperties));
65 |
66 | private IEnumerable GetDependsOn(PropertyDeclarationSyntax property, IEnumerable simpleProperties)
67 | {
68 | foreach (var p in property
69 | .DescendantNodes(x => !x.IsKind(SyntaxKind.IdentifierName))
70 | .OfType())
71 | {
72 | var name = p.Identifier.Text;
73 | var sp = simpleProperties.FirstOrDefault(x => x.Name == name);
74 |
75 | if (sp != null)
76 | {
77 | sp.AddDependent(this);
78 | yield return sp.Name;
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.SourceGenerator/SourceGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text;
4 | using Microsoft.CodeAnalysis;
5 | using Microsoft.CodeAnalysis.CSharp;
6 | using Microsoft.CodeAnalysis.CSharp.Syntax;
7 |
8 | namespace ValueChangedGenerator
9 | {
10 | [Generator]
11 | public class SourceGenerator : ISourceGenerator
12 | {
13 | private sealed class SyntaxReceiver : ISyntaxReceiver
14 | {
15 | public List CandidateStructs { get; } = new();
16 |
17 | public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
18 | {
19 | if (syntaxNode is not StructDeclarationSyntax structDeclarationSyntax)
20 | return;
21 |
22 | var parent = structDeclarationSyntax.FirstAncestorOrSelf();
23 | if (parent is null) return;
24 |
25 | var name = structDeclarationSyntax.Identifier.Text;
26 | if (name != "NotifyRecord") return;
27 |
28 | if (!parent.ChildNodes().Any(n => n == structDeclarationSyntax))
29 | return;
30 |
31 | CandidateStructs.Add(structDeclarationSyntax);
32 | }
33 | }
34 |
35 | public void Initialize(GeneratorInitializationContext context)
36 | {
37 | context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
38 | }
39 |
40 | public void Execute(GeneratorExecutionContext context)
41 | {
42 | if (context.SyntaxReceiver is not SyntaxReceiver receiver)
43 | return;
44 |
45 | foreach (var candidateStruct in receiver.CandidateStructs)
46 | {
47 | if (candidateStruct.FirstAncestorOrSelf() is ClassDeclarationSyntax typeDecl)
48 | {
49 | // Code from: https://github.com/YairHalberstadt/stronginject/blob/779a38e7e74b92c87c86ded5d1fef55744d34a83/StrongInject/Generator/SourceGenerator.cs#L87
50 | static string GenerateHintName(INamedTypeSymbol container)
51 | {
52 | var stringBuilder = new StringBuilder();
53 | stringBuilder.Append(container.ContainingNamespace.FullName());
54 | foreach (var type in container.GetContainingTypesAndThis().Reverse())
55 | {
56 | stringBuilder.Append(".");
57 | stringBuilder.Append(type.Name);
58 | if (type.TypeParameters.Length > 0)
59 | {
60 | stringBuilder.Append("_");
61 | stringBuilder.Append(type.TypeParameters.Length);
62 | }
63 | }
64 | stringBuilder.Append(".g.cs");
65 | return stringBuilder.ToString();
66 | }
67 | var model = context.Compilation.GetSemanticModel(typeDecl.SyntaxTree);
68 | if (model.GetDeclaredSymbol(typeDecl) is INamedTypeSymbol type)
69 | {
70 | var generator = new Generator();
71 | context.AddSource(GenerateHintName(type), generator.GeneratePartialDeclaration(type, typeDecl).ToFullString());
72 | }
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33209.295
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValueChangedGenerator", "ValueChangedGenerator\ValueChangedGenerator\ValueChangedGenerator.csproj", "{5915CA94-F5A8-49BA-98FD-09B455C26E44}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValueChangedGenerator.Package", "ValueChangedGenerator\ValueChangedGenerator.Package\ValueChangedGenerator.Package.csproj", "{81019264-DDD8-4CA5-96DE-2573A68F6666}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValueChangedGenerator.Vsix", "ValueChangedGenerator\ValueChangedGenerator.Vsix\ValueChangedGenerator.Vsix.csproj", "{2ABA407E-D049-49F6-8744-040A54A30E2D}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValueChangedGenerator.SourceGenerator", "ValueChangedGenerator\ValueChangedGenerator.SourceGenerator\ValueChangedGenerator.SourceGenerator.csproj", "{E4326175-9A2C-4A76-94D6-0F6DF4787F2B}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValueChangedGenerator.Test", "ValueChangedGenerator\ValueChangedGenerator.Test\ValueChangedGenerator.Test.csproj", "{6C27E7BC-F8EE-4253-9AA1-F839582DFA27}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {5915CA94-F5A8-49BA-98FD-09B455C26E44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {5915CA94-F5A8-49BA-98FD-09B455C26E44}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {5915CA94-F5A8-49BA-98FD-09B455C26E44}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {5915CA94-F5A8-49BA-98FD-09B455C26E44}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {81019264-DDD8-4CA5-96DE-2573A68F6666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {81019264-DDD8-4CA5-96DE-2573A68F6666}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {81019264-DDD8-4CA5-96DE-2573A68F6666}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {81019264-DDD8-4CA5-96DE-2573A68F6666}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {2ABA407E-D049-49F6-8744-040A54A30E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {2ABA407E-D049-49F6-8744-040A54A30E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {2ABA407E-D049-49F6-8744-040A54A30E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {2ABA407E-D049-49F6-8744-040A54A30E2D}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {E4326175-9A2C-4A76-94D6-0F6DF4787F2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {E4326175-9A2C-4A76-94D6-0F6DF4787F2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {E4326175-9A2C-4A76-94D6-0F6DF4787F2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {E4326175-9A2C-4A76-94D6-0F6DF4787F2B}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {6C27E7BC-F8EE-4253-9AA1-F839582DFA27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {6C27E7BC-F8EE-4253-9AA1-F839582DFA27}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {6C27E7BC-F8EE-4253-9AA1-F839582DFA27}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {6C27E7BC-F8EE-4253-9AA1-F839582DFA27}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {14EE4099-3038-4951-BA5A-D603D41AF7D9}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ValueChangedGenerator {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ValueChangedGenerator.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to PropertyChanged can be generated..
65 | ///
66 | internal static string AnalyzerDescription {
67 | get {
68 | return ResourceManager.GetString("AnalyzerDescription", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Generates PropertyChanged for '{0}'.
74 | ///
75 | internal static string AnalyzerMessageFormat {
76 | get {
77 | return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to PropertyChanged can be generated.
83 | ///
84 | internal static string AnalyzerTitle {
85 | get {
86 | return ResourceManager.GetString("AnalyzerTitle", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to Generate properties.
92 | ///
93 | internal static string CodeFixTitle {
94 | get {
95 | return ResourceManager.GetString("CodeFixTitle", resourceCulture);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.SourceGenerator/Generator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
7 |
8 | namespace ValueChangedGenerator
9 | {
10 | public class Generator
11 | {
12 | public CompilationUnitSyntax GeneratePartialDeclaration(INamedTypeSymbol container, ClassDeclarationSyntax classDecl)
13 | {
14 | var strDecl = (StructDeclarationSyntax)classDecl.ChildNodes().First(x => x is StructDeclarationSyntax);
15 |
16 | var def = new RecordDefinition(strDecl);
17 | var generatedNodes = GetGeneratedNodes(def).ToArray();
18 |
19 | var newClassDecl = container.GetContainingTypesAndThis()
20 | .Select((type, i) => i == 0
21 | ? ClassDeclaration(type.Name).GetPartialTypeDelaration().AddMembers(generatedNodes)
22 | : ClassDeclaration(type.Name).GetPartialTypeDelaration())
23 | .Aggregate((a, b) => b.AddMembers(a));
24 |
25 | var ns = container.ContainingNamespace.FullName().NullIfEmpty();
26 |
27 | MemberDeclarationSyntax topDecl;
28 | if (ns != null)
29 | {
30 | topDecl = NamespaceDeclaration(IdentifierName(ns))
31 | .AddMembers(newClassDecl);
32 | }
33 | else
34 | {
35 | topDecl = newClassDecl;
36 | }
37 |
38 | var root = (CompilationUnitSyntax)classDecl.SyntaxTree.GetRoot();
39 |
40 | return CompilationUnit().AddUsings(WithComponentModel(root.Usings))
41 | .AddMembers(topDecl)
42 | .WithTrailingTrivia(CarriageReturnLineFeed)
43 | .NormalizeWhitespace();
44 | }
45 |
46 | private UsingDirectiveSyntax[] WithComponentModel(IEnumerable usings)
47 | {
48 | const string SystemComponentModel = "System.ComponentModel";
49 |
50 | if (usings.Any(x => x.Name?.WithoutTrivia().GetText().ToString() == SystemComponentModel))
51 | return usings.ToArray();
52 |
53 | return usings.Concat(new[] { UsingDirective(IdentifierName("System.ComponentModel")) }).ToArray();
54 | }
55 |
56 | private IEnumerable GetGeneratedNodes(RecordDefinition def)
57 | {
58 | yield return CSharpSyntaxTree.ParseText(
59 | @" private NotifyRecord _value;
60 | ")
61 | .GetRoot().ChildNodes()
62 | .OfType()
63 | .First()
64 | .WithTrailingTrivia(CarriageReturnLineFeed, CarriageReturnLineFeed);
65 |
66 | foreach (var p in def.Properties)
67 | foreach (var s in WithTrivia(GetGeneratedMember(p), p.LeadingTrivia, p.TrailingTrivia))
68 | yield return s;
69 |
70 | foreach (var p in def.DependentProperties)
71 | foreach (var s in WithTrivia(GetGeneratedMember(p), p.LeadingTrivia, p.TrailingTrivia))
72 | yield return s;
73 | }
74 |
75 | private IEnumerable WithTrivia(IEnumerable members, SyntaxTriviaList leadingTrivia, SyntaxTriviaList trailingTrivia)
76 | {
77 | var array = members.ToArray();
78 |
79 | if (array.Length == 0) yield break;
80 |
81 | if (array.Length == 1)
82 | {
83 | yield return array[0]
84 | .WithLeadingTrivia(leadingTrivia)
85 | .WithTrailingTrivia(trailingTrivia);
86 |
87 | yield break;
88 | }
89 |
90 | yield return array[0].WithLeadingTrivia(leadingTrivia);
91 |
92 | for (int i = 1; i < array.Length - 1; i++)
93 | yield return array[i];
94 |
95 | yield return array[array.Length - 1].WithTrailingTrivia(trailingTrivia);
96 | }
97 |
98 | private string NameOf(SimpleProperty p) => NameOf(p.Name);
99 | private string NameOf(DependentProperty p) => NameOf(p.Name);
100 | private string NameOf(string identifier) => $"nameof({identifier})";
101 |
102 | private IEnumerable GetGeneratedMember(SimpleProperty p)
103 | {
104 | var dependentChanged = string.Join("", p.Dependents.Select(d => $" OnPropertyChanged({d.Name}Property);"));
105 | var source = string.Format($@" public {{1}} {{0}} {{{{ get {{{{ return _value.{{0}}; }}}} set {{{{ SetProperty(ref _value.{{0}}, value, {{0}}Property); {{2}} }}}} }}}}
106 | private static readonly PropertyChangedEventArgs {{0}}Property = new PropertyChangedEventArgs(" + NameOf(p) + ");",
107 | p.Name, p.Type.WithoutTrivia().GetText().ToString(), dependentChanged);
108 |
109 | var generatedNodes = CSharpSyntaxTree.ParseText(source)
110 | .GetRoot().ChildNodes()
111 | .OfType()
112 | .ToArray();
113 |
114 | return generatedNodes;
115 | }
116 |
117 | private IEnumerable GetGeneratedMember(DependentProperty p)
118 | {
119 | var source = string.Format(@" public {1} {0} => _value.{0};
120 | private static readonly PropertyChangedEventArgs {0}Property = new PropertyChangedEventArgs(" + NameOf(p) + @");
121 | ",
122 | p.Name, p.Type.WithoutTrivia().GetText().ToString());
123 |
124 | var generatedNodes = CSharpSyntaxTree.ParseText(source)
125 | .GetRoot().ChildNodes()
126 | .OfType()
127 | .ToArray();
128 |
129 | return generatedNodes;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/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 | PropertyChanged can be generated.
122 | An optional longer localizable description of the diagnostic.
123 |
124 |
125 | Generates PropertyChanged for '{0}'
126 | The format-able message the diagnostic displays.
127 |
128 |
129 | PropertyChanged can be generated
130 | The title of the diagnostic.
131 |
132 |
133 | Generate properties
134 | The title of the code fix.
135 |
136 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGeneratorCodeFixProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.Composition;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using Microsoft.CodeAnalysis;
9 | using Microsoft.CodeAnalysis.CodeFixes;
10 | using Microsoft.CodeAnalysis.CodeActions;
11 | using Microsoft.CodeAnalysis.CSharp;
12 | using Microsoft.CodeAnalysis.CSharp.Syntax;
13 | using Microsoft.CodeAnalysis.Rename;
14 | using Microsoft.CodeAnalysis.Text;
15 | using Microsoft.CodeAnalysis.Formatting;
16 | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
17 |
18 | namespace ValueChangedGenerator
19 | {
20 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ValueChangedGeneratorCodeFixProvider)), Shared]
21 | public class ValueChangedGeneratorCodeFixProvider : CodeFixProvider
22 | {
23 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ValueChangedGeneratorAnalyzer.DiagnosticId);
24 |
25 | public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
26 |
27 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
28 | {
29 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
30 |
31 | if (root is null) return;
32 |
33 | // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
34 | var diagnostic = context.Diagnostics.First();
35 | var diagnosticSpan = diagnostic.Location.SourceSpan;
36 |
37 | // Find the type declaration identified by the diagnostic.
38 | if (root.FindToken(diagnosticSpan.Start).Parent is not { } parent) return;
39 | var declaration = parent.AncestorsAndSelf().OfType().First();
40 |
41 | // Register a code action that will invoke the fix.
42 | context.RegisterCodeFix(
43 | CodeAction.Create(
44 | title: Resources.CodeFixTitle,
45 | createChangedSolution: c => GenerateValueChanged(context.Document, declaration, c),
46 | equivalenceKey: nameof(Resources.CodeFixTitle)),
47 | diagnostic);
48 | }
49 |
50 | private async Task GenerateValueChanged(Document document, ClassDeclarationSyntax classDecl, CancellationToken cancellationToken)
51 | {
52 | document = await AddPartialModifier(document, classDecl, cancellationToken);
53 | document = await AddNewDocument(document, classDecl, cancellationToken);
54 | return document.Project.Solution;
55 | }
56 |
57 | private static async Task AddPartialModifier(Document document, ClassDeclarationSyntax typeDecl, CancellationToken cancellationToken)
58 | {
59 | var newTypeDecl = typeDecl.AddPartialModifier();
60 |
61 | if (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) is not CompilationUnitSyntax root) return document;
62 |
63 | var newRoolt = root.ReplaceNode(typeDecl, newTypeDecl)
64 | .WithAdditionalAnnotations(Formatter.Annotation);
65 |
66 | document = document.WithSyntaxRoot(newRoolt);
67 | return document;
68 | }
69 |
70 | private static async Task AddNewDocument(Document document, ClassDeclarationSyntax typeDecl, CancellationToken cancellationToken)
71 | {
72 | var newRoot = await GeneratePartialDeclaration(document, typeDecl, cancellationToken);
73 |
74 | var name = typeDecl.Identifier.Text;
75 | var generatedName = name + ".ValueChanged.cs";
76 |
77 | var project = document.Project;
78 |
79 | var existed = project.Documents.FirstOrDefault(d => d.Name == generatedName);
80 | if (existed != null) return existed.WithSyntaxRoot(newRoot);
81 | else return project.AddDocument(generatedName, newRoot, document.Folders);
82 | }
83 |
84 | private static async Task GeneratePartialDeclaration(Document document, ClassDeclarationSyntax classDecl, CancellationToken cancellationToken)
85 | {
86 | var strDecl = (StructDeclarationSyntax)classDecl.ChildNodes().First(x => x is StructDeclarationSyntax);
87 |
88 | var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
89 |
90 | if (semanticModel is null) return CompilationUnit();
91 |
92 | var ti = semanticModel.GetTypeInfo(strDecl);
93 |
94 | var def = new RecordDefinition(strDecl);
95 | var generatedNodes = GetGeneratedNodes(def).ToArray();
96 |
97 | var newClassDecl = classDecl.GetPartialTypeDelaration()
98 | .AddMembers(generatedNodes)
99 | .WithAdditionalAnnotations(Formatter.Annotation);
100 |
101 | var ns = classDecl.FirstAncestorOrSelf()?.Name.WithoutTrivia().GetText().ToString();
102 |
103 | MemberDeclarationSyntax topDecl;
104 | if (ns != null)
105 | {
106 | topDecl = NamespaceDeclaration(IdentifierName(ns))
107 | .AddMembers(newClassDecl)
108 | .WithAdditionalAnnotations(Formatter.Annotation);
109 | }
110 | else
111 | {
112 | topDecl = newClassDecl;
113 | }
114 |
115 | if (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) is not CompilationUnitSyntax root) return CompilationUnit();
116 |
117 | return CompilationUnit().AddUsings(WithComponentModel(root.Usings))
118 | .AddMembers(topDecl)
119 | .WithTrailingTrivia(CarriageReturnLineFeed)
120 | .WithAdditionalAnnotations(Formatter.Annotation);
121 | }
122 |
123 | private static UsingDirectiveSyntax[] WithComponentModel(IEnumerable usings)
124 | {
125 | const string SystemComponentModel = "System.ComponentModel";
126 |
127 | if (usings.Any(x => x.Name.WithoutTrivia().GetText().ToString() == SystemComponentModel))
128 | return usings.ToArray();
129 |
130 | return usings.Concat(new[] { UsingDirective(IdentifierName("System.ComponentModel")) }).ToArray();
131 | }
132 |
133 | private static IEnumerable GetGeneratedNodes(RecordDefinition def)
134 | {
135 | yield return CSharpSyntaxTree.ParseText(
136 | @" private NotifyRecord _value;
137 | ")
138 | .GetRoot().ChildNodes()
139 | .OfType()
140 | .First()
141 | .WithTrailingTrivia(CarriageReturnLineFeed, CarriageReturnLineFeed)
142 | .WithAdditionalAnnotations(Formatter.Annotation)
143 | ;
144 |
145 | foreach (var p in def.Properties)
146 | foreach (var s in WithTrivia(GetGeneratedMember(p), p.LeadingTrivia, p.TrailingTrivia))
147 | yield return s;
148 |
149 | foreach (var p in def.DependentProperties)
150 | foreach (var s in WithTrivia(GetGeneratedMember(p), p.LeadingTrivia, p.TrailingTrivia))
151 | yield return s;
152 | }
153 |
154 | private static IEnumerable WithTrivia(IEnumerable members, SyntaxTriviaList leadingTrivia, SyntaxTriviaList trailingTrivia)
155 | {
156 | var array = members.ToArray();
157 |
158 | if (array.Length == 0) yield break;
159 |
160 | if (array.Length == 1)
161 | {
162 | yield return array[0]
163 | .WithLeadingTrivia(leadingTrivia)
164 | .WithTrailingTrivia(trailingTrivia);
165 |
166 | yield break;
167 | }
168 |
169 | yield return array[0].WithLeadingTrivia(leadingTrivia);
170 |
171 | for (int i = 1; i < array.Length - 1; i++)
172 | yield return array[i];
173 |
174 | yield return array[array.Length - 1].WithTrailingTrivia(trailingTrivia);
175 | }
176 |
177 | private static string NameOf(SimpleProperty p) => NameOf(p.Name);
178 | private static string NameOf(DependentProperty p) => NameOf(p.Name);
179 | private static string NameOf(string identifier) => $"nameof({identifier})";
180 |
181 | private static IEnumerable GetGeneratedMember(SimpleProperty p)
182 | {
183 | var dependentChanged = string.Join("", p.Dependents.Select(d => $" OnPropertyChanged({d.Name}Property);"));
184 | var source = string.Format(@" public {1} {0} {{ get {{ return _value.{0}; }} set {{ SetProperty(ref _value.{0}, value, {0}Property); {2} }} }}
185 | private static readonly PropertyChangedEventArgs {0}Property = new PropertyChangedEventArgs(" + NameOf(p) + ");",
186 | p.Name, p.Type.WithoutTrivia().GetText().ToString(), dependentChanged);
187 |
188 | var generatedNodes = CSharpSyntaxTree.ParseText(source)
189 | .GetRoot().ChildNodes()
190 | .OfType()
191 | .ToArray();
192 |
193 | return generatedNodes;
194 | }
195 |
196 | private static IEnumerable GetGeneratedMember(DependentProperty p)
197 | {
198 | var source = string.Format(@" public {1} {0} => _value.{0};
199 | private static readonly PropertyChangedEventArgs {0}Property = new PropertyChangedEventArgs(" + NameOf(p) + @");
200 | ",
201 | p.Name, p.Type.WithoutTrivia().GetText().ToString());
202 |
203 | var generatedNodes = CSharpSyntaxTree.ParseText(source)
204 | .GetRoot().ChildNodes()
205 | .OfType()
206 | .ToArray();
207 |
208 | return generatedNodes;
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/ValueChangedGenerator/ValueChangedGenerator/ValueChangedGenerator.Test/ValueChangedGeneratorUnitTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Microsoft.CodeAnalysis;
3 | using Microsoft.CodeAnalysis.CSharp;
4 | using Microsoft.CodeAnalysis.CSharp.Syntax;
5 | using Xunit;
6 |
7 | namespace ValueChangedGenerator.Test
8 | {
9 | public class ValueChangedGeneratorUnitTest
10 | {
11 | [Fact]
12 | public void GeneratePartialDeclaration()
13 | {
14 | var expected = @"using System.ComponentModel;
15 |
16 | partial class Point
17 | {
18 | private NotifyRecord _value;
19 | public int X
20 | {
21 | get
22 | {
23 | return _value.X;
24 | }
25 |
26 | set
27 | {
28 | SetProperty(ref _value.X, value, XProperty);
29 | OnPropertyChanged(ZProperty);
30 | }
31 | }
32 |
33 | private static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X));
34 | public int Y
35 | {
36 | get
37 | {
38 | return _value.Y;
39 | }
40 |
41 | set
42 | {
43 | SetProperty(ref _value.Y, value, YProperty);
44 | OnPropertyChanged(ZProperty);
45 | }
46 | }
47 |
48 | private static readonly PropertyChangedEventArgs YProperty = new PropertyChangedEventArgs(nameof(Y));
49 | ///
50 | /// Name.
51 | ///
52 | public string Name
53 | {
54 | get
55 | {
56 | return _value.Name;
57 | }
58 |
59 | set
60 | {
61 | SetProperty(ref _value.Name, value, NameProperty);
62 | }
63 | }
64 |
65 | private static readonly PropertyChangedEventArgs NameProperty = new PropertyChangedEventArgs(nameof(Name));
66 | public int Z => _value.Z;
67 |
68 | private static readonly PropertyChangedEventArgs ZProperty = new PropertyChangedEventArgs(nameof(Z));
69 | }";
70 | var text = @"
71 | partial class Point : BindableBase
72 | {
73 | struct NotifyRecord
74 | {
75 | public int X;
76 | public int Y;
77 | public int Z => X * Y;
78 |
79 | ///
80 | /// Name.
81 | ///
82 | public string Name;
83 | }
84 | }";
85 | var compilation = CSharpCompilation.Create(assemblyName: "test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
86 | .AddSyntaxTrees(CSharpSyntaxTree.ParseText(text));
87 | var generator = new Generator();
88 | var syntaxTrees = compilation.SyntaxTrees;
89 | Assert.Single(syntaxTrees);
90 | var classDecl = (ClassDeclarationSyntax)syntaxTrees.First().GetCompilationUnitRoot().DescendantNodes().First(x => x is ClassDeclarationSyntax);
91 | var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
92 | var container = model.GetDeclaredSymbol(classDecl);
93 | Assert.NotNull(container);
94 | Assert.Equal(expected, actual: generator.GeneratePartialDeclaration(container!, classDecl).ToFullString());
95 | }
96 |
97 | [Fact]
98 | public void GeneratePartialDeclaration_NestedPartialClasses()
99 | {
100 | var expected = @"using System.ComponentModel;
101 |
102 | partial class C1
103 | {
104 | partial class C2
105 | {
106 | partial class C3
107 | {
108 | private NotifyRecord _value;
109 | public int X
110 | {
111 | get
112 | {
113 | return _value.X;
114 | }
115 |
116 | set
117 | {
118 | SetProperty(ref _value.X, value, XProperty);
119 | OnPropertyChanged(ZProperty);
120 | }
121 | }
122 |
123 | private static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X));
124 | public int Y
125 | {
126 | get
127 | {
128 | return _value.Y;
129 | }
130 |
131 | set
132 | {
133 | SetProperty(ref _value.Y, value, YProperty);
134 | OnPropertyChanged(ZProperty);
135 | }
136 | }
137 |
138 | private static readonly PropertyChangedEventArgs YProperty = new PropertyChangedEventArgs(nameof(Y));
139 | ///
140 | /// Name.
141 | ///
142 | public string Name
143 | {
144 | get
145 | {
146 | return _value.Name;
147 | }
148 |
149 | set
150 | {
151 | SetProperty(ref _value.Name, value, NameProperty);
152 | }
153 | }
154 |
155 | private static readonly PropertyChangedEventArgs NameProperty = new PropertyChangedEventArgs(nameof(Name));
156 | public int Z => _value.Z;
157 |
158 | private static readonly PropertyChangedEventArgs ZProperty = new PropertyChangedEventArgs(nameof(Z));
159 | }
160 | }
161 | }";
162 | var text = @"
163 | public partial class C1 : BindableBase
164 | {
165 | struct NotifyRecord
166 | {
167 | public int X;
168 | public int Y;
169 | public int Z => X * Y;
170 |
171 | ///
172 | /// Name.
173 | ///
174 | public string Name;
175 | }
176 |
177 | public partial class C2 : BindableBase
178 | {
179 | struct NotifyRecord
180 | {
181 | public int X;
182 | public int Y;
183 | public int Z => X * Y;
184 |
185 | ///
186 | /// Name.
187 | ///
188 | public string Name;
189 | }
190 |
191 | public partial class C3 : BindableBase
192 | {
193 | struct NotifyRecord
194 | {
195 | public int X;
196 | public int Y;
197 | public int Z => X * Y;
198 |
199 | ///
200 | /// Name.
201 | ///
202 | public string Name;
203 | }
204 | }
205 | }
206 | }";
207 | var compilation = CSharpCompilation.Create(assemblyName: "test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
208 | .AddSyntaxTrees(CSharpSyntaxTree.ParseText(text));
209 | var generator = new Generator();
210 | var syntaxTrees = compilation.SyntaxTrees;
211 | Assert.Single(syntaxTrees);
212 | var classDecl = (ClassDeclarationSyntax)syntaxTrees.First().GetCompilationUnitRoot()
213 | .DescendantNodes().First(x => x is ClassDeclarationSyntax)
214 | .DescendantNodes().First(x => x is ClassDeclarationSyntax)
215 | .DescendantNodes().First(x => x is ClassDeclarationSyntax);
216 | var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
217 | var container = model.GetDeclaredSymbol(classDecl);
218 | Assert.NotNull(container);
219 | Assert.Equal(expected, actual: generator.GeneratePartialDeclaration(container!, classDecl).ToFullString());
220 | }
221 |
222 | [Fact]
223 | public void GeneratePartialDeclaration_NestedNamespaces()
224 | {
225 | var expected = @"using System.ComponentModel;
226 |
227 | namespace N1.N2.N3
228 | {
229 | partial class C1
230 | {
231 | partial class C2
232 | {
233 | partial class C3
234 | {
235 | private NotifyRecord _value;
236 | public int X
237 | {
238 | get
239 | {
240 | return _value.X;
241 | }
242 |
243 | set
244 | {
245 | SetProperty(ref _value.X, value, XProperty);
246 | OnPropertyChanged(ZProperty);
247 | }
248 | }
249 |
250 | private static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X));
251 | public int Y
252 | {
253 | get
254 | {
255 | return _value.Y;
256 | }
257 |
258 | set
259 | {
260 | SetProperty(ref _value.Y, value, YProperty);
261 | OnPropertyChanged(ZProperty);
262 | }
263 | }
264 |
265 | private static readonly PropertyChangedEventArgs YProperty = new PropertyChangedEventArgs(nameof(Y));
266 | ///
267 | /// Name.
268 | ///
269 | public string Name
270 | {
271 | get
272 | {
273 | return _value.Name;
274 | }
275 |
276 | set
277 | {
278 | SetProperty(ref _value.Name, value, NameProperty);
279 | }
280 | }
281 |
282 | private static readonly PropertyChangedEventArgs NameProperty = new PropertyChangedEventArgs(nameof(Name));
283 | public int Z => _value.Z;
284 |
285 | private static readonly PropertyChangedEventArgs ZProperty = new PropertyChangedEventArgs(nameof(Z));
286 | }
287 | }
288 | }
289 | }";
290 | var text = @"
291 | namespace N1
292 | {
293 | namespace N2
294 | {
295 | namespace N3
296 | {
297 | public partial class C1 : BindableBase
298 | {
299 | struct NotifyRecord
300 | {
301 | public int X;
302 | public int Y;
303 | public int Z => X * Y;
304 |
305 | ///
306 | /// Name.
307 | ///
308 | public string Name;
309 | }
310 |
311 | public partial class C2 : BindableBase
312 | {
313 | struct NotifyRecord
314 | {
315 | public int X;
316 | public int Y;
317 | public int Z => X * Y;
318 |
319 | ///
320 | /// Name.
321 | ///
322 | public string Name;
323 | }
324 |
325 | public partial class C3 : BindableBase
326 | {
327 | struct NotifyRecord
328 | {
329 | public int X;
330 | public int Y;
331 | public int Z => X * Y;
332 |
333 | ///
334 | /// Name.
335 | ///
336 | public string Name;
337 | }
338 | }
339 | }
340 | }
341 | }
342 | }
343 | }";
344 | var compilation = CSharpCompilation.Create(assemblyName: "test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
345 | .AddSyntaxTrees(CSharpSyntaxTree.ParseText(text));
346 | var generator = new Generator();
347 | var syntaxTrees = compilation.SyntaxTrees;
348 | Assert.Single(syntaxTrees);
349 | var classDecl = (ClassDeclarationSyntax)syntaxTrees.First().GetCompilationUnitRoot()
350 | .DescendantNodes().First(x => x is ClassDeclarationSyntax)
351 | .DescendantNodes().First(x => x is ClassDeclarationSyntax)
352 | .DescendantNodes().First(x => x is ClassDeclarationSyntax);
353 | var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
354 | var container = model.GetDeclaredSymbol(classDecl);
355 | Assert.NotNull(container);
356 | Assert.Equal(expected, actual: generator.GeneratePartialDeclaration(container!, classDecl).ToFullString());
357 | }
358 |
359 | [Fact]
360 | public void GenerateUsings()
361 | {
362 | var expected = @"using System;
363 | using System.Linq;
364 | using System.ComponentModel;
365 |
366 | partial class Point
367 | {
368 | private NotifyRecord _value;
369 | public int X
370 | {
371 | get
372 | {
373 | return _value.X;
374 | }
375 |
376 | set
377 | {
378 | SetProperty(ref _value.X, value, XProperty);
379 | }
380 | }
381 |
382 | private static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X));
383 | }";
384 | var text = @"using System;
385 | using System.Linq;
386 |
387 | partial class Point : BindableBase
388 | {
389 | struct NotifyRecord
390 | {
391 | public int X;
392 | }
393 | }";
394 | var compilation = CSharpCompilation.Create(assemblyName: "test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
395 | .AddSyntaxTrees(CSharpSyntaxTree.ParseText(text));
396 | var generator = new Generator();
397 | var syntaxTrees = compilation.SyntaxTrees;
398 | Assert.Single(syntaxTrees);
399 | var classDecl = (ClassDeclarationSyntax)syntaxTrees.First().GetCompilationUnitRoot().DescendantNodes().First(x => x is ClassDeclarationSyntax);
400 | var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
401 | var container = model.GetDeclaredSymbol(classDecl);
402 | Assert.NotNull(container);
403 | Assert.Equal(expected, actual: generator.GeneratePartialDeclaration(container!, classDecl).ToFullString());
404 | }
405 |
406 | [Fact]
407 | public void GenerateFileScopedNamespace()
408 | {
409 | var expected = @"using System;
410 | using System.ComponentModel;
411 |
412 | namespace N
413 | {
414 | partial class Point
415 | {
416 | private NotifyRecord _value;
417 | public int X
418 | {
419 | get
420 | {
421 | return _value.X;
422 | }
423 |
424 | set
425 | {
426 | SetProperty(ref _value.X, value, XProperty);
427 | }
428 | }
429 |
430 | private static readonly PropertyChangedEventArgs XProperty = new PropertyChangedEventArgs(nameof(X));
431 | }
432 | }";
433 | var text = @"using System;
434 | namespace N;
435 |
436 | partial class Point : BindableBase
437 | {
438 | struct NotifyRecord
439 | {
440 | public int X;
441 | }
442 | }";
443 | var compilation = CSharpCompilation.Create(assemblyName: "test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
444 | .AddSyntaxTrees(CSharpSyntaxTree.ParseText(text));
445 | var generator = new Generator();
446 | var syntaxTrees = compilation.SyntaxTrees;
447 | Assert.Single(syntaxTrees);
448 | var classDecl = (ClassDeclarationSyntax)syntaxTrees.First().GetCompilationUnitRoot().DescendantNodes().First(x => x is ClassDeclarationSyntax);
449 | var model = compilation.GetSemanticModel(classDecl.SyntaxTree);
450 | var container = model.GetDeclaredSymbol(classDecl);
451 | Assert.NotNull(container);
452 | Assert.Equal(expected, actual: generator.GeneratePartialDeclaration(container!, classDecl).ToFullString());
453 | }
454 | }
455 | }
456 |
--------------------------------------------------------------------------------