Skip to content

Commit e746a92

Browse files
mycroesMichael CroesChrisPulman
authored
Declare command properties as notnull (#46) (#47)
* Declare command properties as notnull (#46) * fix(ReactiveCommand): Normalize whitespace for backing field * Remove InitializeCommands from Readme and examples --------- Co-authored-by: Michael Croes <[email protected]> Co-authored-by: Chris Pulman <[email protected]>
1 parent 369d9e5 commit e746a92

File tree

5 files changed

+83
-109
lines changed

5 files changed

+83
-109
lines changed

README.md

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,6 @@ using ReactiveUI.SourceGenerators;
176176

177177
public partial class MyReactiveClass
178178
{
179-
public MyReactiveClass()
180-
{
181-
InitializeCommands();
182-
}
183-
184179
[ReactiveCommand]
185180
private void Execute() { }
186181
}
@@ -192,11 +187,6 @@ using ReactiveUI.SourceGenerators;
192187

193188
public partial class MyReactiveClass
194189
{
195-
public MyReactiveClass()
196-
{
197-
InitializeCommands();
198-
}
199-
200190
[ReactiveCommand]
201191
private void Execute(string parameter) { }
202192
}
@@ -208,11 +198,6 @@ using ReactiveUI.SourceGenerators;
208198

209199
public partial class MyReactiveClass
210200
{
211-
public MyReactiveClass()
212-
{
213-
InitializeCommands();
214-
}
215-
216201
[ReactiveCommand]
217202
private string Execute(string parameter) => parameter;
218203
}
@@ -224,11 +209,6 @@ using ReactiveUI.SourceGenerators;
224209

225210
public partial class MyReactiveClass
226211
{
227-
public MyReactiveClass()
228-
{
229-
InitializeCommands();
230-
}
231-
232212
[ReactiveCommand]
233213
private async Task<string> Execute(string parameter) => await Task.FromResult(parameter);
234214
}
@@ -240,11 +220,6 @@ using ReactiveUI.SourceGenerators;
240220

241221
public partial class MyReactiveClass
242222
{
243-
public MyReactiveClass()
244-
{
245-
InitializeCommands();
246-
}
247-
248223
[ReactiveCommand]
249224
private IObservable<string> Execute(string parameter) => Observable.Return(parameter);
250225
}
@@ -256,11 +231,6 @@ using ReactiveUI.SourceGenerators;
256231

257232
public partial class MyReactiveClass
258233
{
259-
public MyReactiveClass()
260-
{
261-
InitializeCommands();
262-
}
263-
264234
[ReactiveCommand]
265235
private async Task Execute(CancellationToken token) => await Task.Delay(1000, token);
266236
}
@@ -272,11 +242,6 @@ using ReactiveUI.SourceGenerators;
272242

273243
public partial class MyReactiveClass
274244
{
275-
public MyReactiveClass()
276-
{
277-
InitializeCommands();
278-
}
279-
280245
[ReactiveCommand]
281246
private async Task<string> Execute(string parameter, CancellationToken token)
282247
{
@@ -302,7 +267,6 @@ public partial class MyReactiveClass
302267

303268
public MyReactiveClass()
304269
{
305-
InitializeCommands();
306270
_canExecute = this.WhenAnyValue(x => x.MyProperty1, x => x.MyProperty2, (x, y) => !string.IsNullOrEmpty(x) && !string.IsNullOrEmpty(y));
307271
}
308272

@@ -327,7 +291,6 @@ public partial class MyReactiveClass
327291

328292
public MyReactiveClass()
329293
{
330-
InitializeCommands();
331294
_canExecute = this.WhenAnyValue(x => x.MyProperty1, x => x.MyProperty2, (x, y) => !string.IsNullOrEmpty(x) && !string.IsNullOrEmpty(y));
332295
}
333296

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public partial class TestViewModel : ReactiveObject
3333
/// </summary>
3434
public TestViewModel()
3535
{
36-
InitializeCommands();
3736
InitializeOAPH();
3837

3938
Console.Out.WriteLine(Test1Command);

src/ReactiveUI.SourceGenerators/Helpers/AttributeDefinitions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal static class AttributeDefinitions
1111
{
1212
public const string GeneratedCode = "global::System.CodeDom.Compiler.GeneratedCode";
1313
public const string ExcludeFromCodeCoverage = "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage";
14+
public const string Obsolete = "global::System.Obsolete";
1415
public const string ReactiveObjectAttribute = """
1516
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
1617
// Licensed to the .NET Foundation under one or more agreements.

src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs

Lines changed: 80 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -30,37 +30,14 @@ public partial class ReactiveCommandGenerator
3030
private const string Create = ".Create";
3131
private const string CreateO = ".CreateFromObservable";
3232
private const string CreateT = ".CreateFromTask";
33+
private const string ObsoleteReason = "Commands are initialized automatically. Method will be removed in future version.";
3334

3435
/// <summary>
3536
/// A container for all the logic for <see cref="ReactiveCommandGenerator"/>.
3637
/// </summary>
3738
internal static class Execute
3839
{
39-
internal static MethodDeclarationSyntax GetCommandInitiliser(CommandInfo[] commandExtensionInfos)
40-
{
41-
using var commandInitilisers = ImmutableArrayBuilder<StatementSyntax>.Rent();
42-
43-
// Add the command initializations
44-
foreach (var commandExtensionInfo in commandExtensionInfos)
45-
{
46-
var commandName = GetGeneratedCommandName(commandExtensionInfo.MethodName);
47-
var outputType = commandExtensionInfo.GetOutputTypeText();
48-
var inputType = commandExtensionInfo.GetInputTypeText();
49-
if (commandExtensionInfo.ArgumentType == null)
50-
{
51-
commandInitilisers.Add(GenerateBasicCommand(commandExtensionInfo, commandName));
52-
}
53-
else if (commandExtensionInfo.ArgumentType != null && commandExtensionInfo.IsReturnTypeVoid)
54-
{
55-
commandInitilisers.Add(GenerateInCommand(commandExtensionInfo, commandName, inputType));
56-
}
57-
else if (commandExtensionInfo.ArgumentType != null && !commandExtensionInfo.IsReturnTypeVoid)
58-
{
59-
commandInitilisers.Add(GenerateInOutCommand(commandExtensionInfo, commandName, outputType, inputType));
60-
}
61-
}
62-
63-
return MethodDeclaration(
40+
internal static MethodDeclarationSyntax GetCommandInitiliser() => MethodDeclaration(
6441
PredefinedType(Token(SyntaxKind.VoidKeyword)),
6542
Identifier("InitializeCommands"))
6643
.AddAttributeLists(
@@ -69,59 +46,45 @@ internal static MethodDeclarationSyntax GetCommandInitiliser(CommandInfo[] comma
6946
.AddArgumentListArguments(
7047
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ReactiveGenerator).FullName))),
7148
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ReactiveGenerator).Assembly.GetName().Version.ToString())))))),
72-
AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverage)))))
49+
AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverage)))),
50+
AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.Obsolete))
51+
.AddArgumentListArguments(
52+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(ObsoleteReason)))))))
7353
.WithModifiers(TokenList(Token(SyntaxKind.ProtectedKeyword)))
74-
.WithBody(Block(commandInitilisers.ToImmutable()));
54+
.WithBody(Block());
7555

76-
static StatementSyntax GenerateBasicCommand(CommandInfo commandExtensionInfo, string commandName)
77-
{
78-
var commandType = commandExtensionInfo.IsObservable ? CreateO : commandExtensionInfo.IsTask ? CreateT : Create;
79-
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
80-
{
81-
return ParseStatement($"{commandName} = {RxCmd}{commandType}({commandExtensionInfo.MethodName});");
82-
}
56+
internal static MemberDeclarationSyntax[] GetCommandProperty(CommandInfo commandExtensionInfo)
57+
{
58+
var outputType = commandExtensionInfo.GetOutputTypeText();
59+
var inputType = commandExtensionInfo.GetInputTypeText();
60+
var commandName = GetGeneratedCommandName(commandExtensionInfo.MethodName);
61+
var fieldName = GetGeneratedFieldName(commandName);
8362

84-
return ParseStatement($"{commandName} = {RxCmd}{commandType}({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});");
63+
ExpressionSyntax initializer;
64+
if (commandExtensionInfo.ArgumentType == null)
65+
{
66+
initializer = GenerateBasicCommand(commandExtensionInfo, fieldName);
8567
}
86-
87-
static StatementSyntax GenerateInOutCommand(CommandInfo commandExtensionInfo, string commandName, string outputType, string inputType)
68+
else if (commandExtensionInfo.ArgumentType != null && commandExtensionInfo.IsReturnTypeVoid)
8869
{
89-
var commandType = commandExtensionInfo.IsObservable ? CreateO : commandExtensionInfo.IsTask ? CreateT : Create;
90-
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
91-
{
92-
return ParseStatement($"{commandName} = {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName});");
93-
}
94-
95-
return ParseStatement($"{commandName} = {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});");
70+
initializer = GenerateInCommand(commandExtensionInfo, fieldName, inputType);
9671
}
97-
98-
static StatementSyntax GenerateInCommand(CommandInfo commandExtensionInfo, string commandName, string inputType)
72+
else if (commandExtensionInfo.ArgumentType != null && !commandExtensionInfo.IsReturnTypeVoid)
9973
{
100-
var commandType = commandExtensionInfo.IsTask ? CreateT : Create;
101-
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
102-
{
103-
return ParseStatement($"{commandName} = {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName});");
104-
}
105-
106-
return ParseStatement($"{commandName} = {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});");
74+
initializer = GenerateInOutCommand(commandExtensionInfo, fieldName, outputType, inputType);
75+
}
76+
else
77+
{
78+
return [];
10779
}
108-
}
109-
110-
internal static MemberDeclarationSyntax GetCommandProperty(CommandInfo commandExtensionInfo)
111-
{
112-
var outputType = commandExtensionInfo.GetOutputTypeText();
113-
var inputType = commandExtensionInfo.GetInputTypeText();
114-
var commandName = GetGeneratedCommandName(commandExtensionInfo.MethodName);
11580

11681
// Prepare any forwarded property attributes
11782
var forwardedPropertyAttributes =
11883
commandExtensionInfo.ForwardedPropertyAttributes
11984
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
12085
.ToImmutableArray();
12186

122-
var commandDeclaration = PropertyDeclaration(
123-
NullableType(
124-
QualifiedName(
87+
var qualifiedName = QualifiedName(
12588
IdentifierName(ReactiveUI),
12689
GenericName(
12790
Identifier(ReactiveCommand))
@@ -133,18 +96,63 @@ internal static MemberDeclarationSyntax GetCommandProperty(CommandInfo commandEx
13396
IdentifierName(inputType),
13497
Token(SyntaxKind.CommaToken),
13598
IdentifierName(outputType)
136-
}))))),
99+
}))));
100+
101+
var fieldDeclaration = FieldDeclaration(
102+
VariableDeclaration(NullableType(qualifiedName)))
103+
.AddDeclarationVariables(VariableDeclarator(fieldName))
104+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
105+
Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
106+
.AddArgumentListArguments(
107+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ReactiveCommandGenerator).FullName))),
108+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ReactiveCommandGenerator).Assembly.GetName().Version.ToString())))))))
109+
.AddModifiers(
110+
Token(SyntaxKind.PrivateKeyword))
111+
.NormalizeWhitespace();
112+
113+
var commandDeclaration = PropertyDeclaration(
114+
qualifiedName,
137115
Identifier(commandName))
138116
.AddModifiers(Token(SyntaxKind.PublicKeyword))
139117
.AddAccessorListAccessors(
140118
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
141-
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
142-
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
143-
.WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword)))
144-
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)))
119+
.WithExpressionBody(ArrowExpressionClause(initializer)))
145120
.AddAttributeLists([.. forwardedPropertyAttributes])
146121
.NormalizeWhitespace();
147-
return commandDeclaration;
122+
return [fieldDeclaration, commandDeclaration];
123+
124+
static ExpressionSyntax GenerateBasicCommand(CommandInfo commandExtensionInfo, string fieldName)
125+
{
126+
var commandType = commandExtensionInfo.IsObservable ? CreateO : commandExtensionInfo.IsTask ? CreateT : Create;
127+
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
128+
{
129+
return ParseExpression($"{fieldName} ??= {RxCmd}{commandType}({commandExtensionInfo.MethodName});");
130+
}
131+
132+
return ParseExpression($"{fieldName} ??= {RxCmd}{commandType}({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});");
133+
}
134+
135+
static ExpressionSyntax GenerateInOutCommand(CommandInfo commandExtensionInfo, string fieldName, string outputType, string inputType)
136+
{
137+
var commandType = commandExtensionInfo.IsObservable ? CreateO : commandExtensionInfo.IsTask ? CreateT : Create;
138+
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
139+
{
140+
return ParseExpression($"{fieldName} ??= {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName});");
141+
}
142+
143+
return ParseExpression($"{fieldName} ??= {RxCmd}{commandType}<{inputType}, {outputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});");
144+
}
145+
146+
static ExpressionSyntax GenerateInCommand(CommandInfo commandExtensionInfo, string fieldName, string inputType)
147+
{
148+
var commandType = commandExtensionInfo.IsTask ? CreateT : Create;
149+
if (string.IsNullOrEmpty(commandExtensionInfo.CanExecuteObservableName))
150+
{
151+
return ParseExpression($"{fieldName} ??= {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName});");
152+
}
153+
154+
return ParseExpression($"{fieldName} ??= {RxCmd}{commandType}<{inputType}>({commandExtensionInfo.MethodName}, {commandExtensionInfo.CanExecuteObservableName}{(commandExtensionInfo.CanExecuteTypeInfo == CanExecuteTypeInfo.MethodObservable ? "()" : string.Empty)});");
155+
}
148156
}
149157

150158
internal static bool IsTaskReturnType(ITypeSymbol? typeSymbol)
@@ -458,5 +466,8 @@ internal static string GetGeneratedCommandName(string methodName)
458466

459467
return $"{char.ToUpper(commandName[0], CultureInfo.InvariantCulture)}{commandName.Substring(1)}Command";
460468
}
469+
470+
internal static string GetGeneratedFieldName(string generatedCommandName) =>
471+
$"_{char.ToLower(generatedCommandName[0], CultureInfo.InvariantCulture)}{generatedCommandName.Substring(1)}";
461472
}
462473
}

src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
139139
// Generate all member declarations for the current type
140140
var propertyDeclarations =
141141
commandInfos
142-
.Select(Execute.GetCommandProperty)
142+
.SelectMany(Execute.GetCommandProperty)
143143
.ToList();
144144

145-
var c = Execute.GetCommandInitiliser(commandInfos);
145+
var c = Execute.GetCommandInitiliser();
146146
propertyDeclarations.Add(c);
147147
var memberDeclarations = propertyDeclarations.ToImmutableArray();
148148

0 commit comments

Comments
 (0)