Skip to content

Commit 04badef

Browse files
Create Copilot instructions for ReactiveUI - Windows-only build, AOT patterns with DynamicallyAccessedMembersAttribute, and code style guidelines (#4115)
This PR creates comprehensive Copilot instructions for the ReactiveUI repository to help developers understand the Windows-only build requirements, AOT-friendly development patterns emphasizing `DynamicallyAccessedMembersAttribute`, and strict code style compliance. ## What's Added Created `.github/copilot-instructions.md` with detailed guidance covering: ### 🏗️ Build Environment - **Windows-only requirements**: Clearly documented that the repository requires Windows for building due to Windows-specific target frameworks (`net8.0-windows10.0.17763.0`, `net9.0-windows10.0.17763.0`) - **.NET 9.0 SDK requirement**: Required for AOT tests and full build capabilities - **Proper build commands**: Step-by-step instructions for `dotnet restore`, `dotnet build`, and `dotnet test` using `src/ReactiveUI.sln` ### 🎯 AOT-Friendly Development Patterns - **Preferred approach**: Use `DynamicallyAccessedMembersAttribute` to inform the AOT compiler about required members rather than just suppressing warnings - **Specific member types**: Examples showing `DynamicallyAccessedMemberTypes.PublicProperties`, `PublicParameterlessConstructor`, and `All` usage - **Real codebase examples**: Concrete patterns from ReactiveUI source code (ReactiveUIBuilder.cs, ExpressionRewriter.cs, DependencyResolverMixins.cs) - **Fallback approach**: Use `UnconditionalSuppressMessage` only when `DynamicallyAccessedMembersAttribute` isn't sufficient - **ReactiveCommand patterns**: Examples of async commands that work well with AOT compilation ### 🎨 Code Style & Formatting Guidelines - **EditorConfig compliance**: References the extensive `.editorconfig` file with 200+ StyleCop analyzer rules - **StyleCop enforcement**: Error-level enforcement of coding standards - **Official style guide**: Links to [ReactiveUI's code style guide](https://www.reactiveui.net/contribute/software-style-guide/code-style.html) - **Build enforcement**: Instructions for using `-warnaserror` to catch style violations ### 📚 Reference Materials - Links to official ReactiveUI documentation and samples - In-repository examples pointing to `src/ReactiveUI.AOTTests/` and other test projects - Key project structure overview for navigation - ViewModel creation patterns and reactive programming examples ## Why This Matters ReactiveUI has extensive AOT support and Windows-specific build requirements, plus strict code style enforcement. The instructions emphasize the preferred use of `DynamicallyAccessedMembersAttribute` over suppression attributes because it: - Informs the AOT trimmer what members to preserve rather than just hiding warnings - Provides better AOT compatibility by ensuring needed members aren't trimmed - Is more surgical and specific than broad warning suppression - Documents the specific reflection requirements for maintainability These instructions will help Copilot provide accurate guidance when working with ReactiveUI's functional reactive programming patterns, MVVM architecture, and AOT compilation requirements. Fixes #4114. <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: glennawatson <[email protected]>
1 parent 30e2b60 commit 04badef

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed

.github/copilot-instructions.md

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
# Copilot Instructions for ReactiveUI
2+
3+
You are working on the ReactiveUI repository - a composable, cross-platform model-view-viewmodel framework for all .NET platforms inspired by functional reactive programming.
4+
5+
## 🏗️ Build Environment Requirements
6+
7+
**IMPORTANT**: This repository requires Windows for building. Linux/macOS are not supported.
8+
9+
### Required Tools
10+
- Windows 10/11 with Visual Studio 2019+
11+
- PowerShell or Command Prompt
12+
- .NET 9.0 SDK (required for AOT tests) or .NET 8.0 SDK minimum
13+
- Windows-specific target frameworks: `net8.0-windows10.0.17763.0`, `net9.0-windows10.0.17763.0`
14+
15+
### Solution Files
16+
- Main solution: `src/ReactiveUI.sln` (repository root/src directory)
17+
- Integration tests: `integrationtests/` directory contains platform-specific solutions
18+
19+
## 🛠️ Build & Test Commands
20+
21+
**Run these commands in Windows PowerShell or CMD from the repository root:**
22+
23+
```powershell
24+
# Check .NET installation (requires .NET 9.0 SDK for full build including AOT tests)
25+
dotnet --info
26+
27+
# Restore packages (may fail on non-Windows due to Windows-specific target frameworks)
28+
dotnet restore src/ReactiveUI.sln
29+
30+
# Build the solution (requires Windows for platform-specific targets)
31+
dotnet build src/ReactiveUI.sln -c Release -warnaserror
32+
33+
# Run tests (includes AOT tests that require .NET 9.0)
34+
dotnet test src/ReactiveUI.sln -c Release --no-build
35+
```
36+
37+
**Note**: The repository contains Windows-specific target frameworks (`net8.0-windows`, `net9.0-windows`) and AOT tests that require .NET 9.0. Building on Linux/macOS will fail due to these platform dependencies.
38+
39+
**For non-Windows environments**: If working in this repository on Linux/macOS (such as in GitHub Codespaces), focus on documentation changes, code analysis, and understanding patterns rather than attempting to build. The build failure is expected and normal.
40+
41+
If any step fails due to Windows-specific tooling requirements, create a draft PR describing the minimal change needed.
42+
43+
## 🎯 AOT-Friendly Development Patterns
44+
45+
ReactiveUI supports Ahead-of-Time (AOT) compilation. Follow these patterns:
46+
47+
### ✅ Preferred AOT-Friendly Patterns
48+
49+
1. **String-based property observation** (AOT-safe when using nameof):
50+
```csharp
51+
// Use ObservableForProperty with nameof - AOT-safe
52+
obj.ObservableForProperty<TestReactiveObject, string?>(
53+
nameof(TestReactiveObject.TestProperty),
54+
beforeChange: false,
55+
skipInitial: false)
56+
.Select(x => x.Value)
57+
.Subscribe(HandlePropertyChange);
58+
59+
// Method that needs to access properties via reflection
60+
#if NET6_0_OR_GREATER
61+
private static PropertyInfo? GetPropertyInfo(
62+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
63+
Type type, string propertyName)
64+
#else
65+
private static PropertyInfo? GetPropertyInfo(Type type, string propertyName)
66+
#endif
67+
{
68+
return type.GetProperty(propertyName);
69+
}
70+
```
71+
72+
2. **ReactiveCommand creation**:
73+
```csharp
74+
// Preferred: Async command with explicit typing
75+
public ReactiveCommand<Unit, Unit> SubmitCommand { get; }
76+
77+
public ViewModel()
78+
{
79+
SubmitCommand = ReactiveCommand.CreateFromTask(ExecuteSubmit);
80+
}
81+
82+
private async Task ExecuteSubmit(CancellationToken cancellationToken)
83+
{
84+
// Implementation
85+
}
86+
```
87+
88+
3. **Observable property helpers**:
89+
```csharp
90+
private readonly ObservableAsPropertyHelper<string> _computedValue;
91+
92+
public string ComputedValue => _computedValue.Value;
93+
94+
// In constructor - when using strongly-typed expressions, no AOT attributes needed
95+
public ViewModel()
96+
{
97+
_computedValue = this.WhenAnyValue(x => x.InputValue)
98+
.Select(x => $"Computed: {x}")
99+
.ToProperty(this, nameof(ComputedValue));
100+
}
101+
102+
// For methods that register view models with dependency injection
103+
#if NET6_0_OR_GREATER
104+
public void RegisterViewModel<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TViewModel>()
105+
#else
106+
public void RegisterViewModel<TViewModel>()
107+
#endif
108+
{
109+
// Registration logic that uses reflection on TViewModel
110+
}
111+
```
112+
113+
4. **Property change notification**:
114+
```csharp
115+
private string? _myProperty;
116+
117+
public string? MyProperty
118+
{
119+
get => _myProperty;
120+
set => this.RaiseAndSetIfChanged(ref _myProperty, value);
121+
}
122+
```
123+
124+
### ⚠️ AOT Considerations
125+
126+
**Preferred Approach**: Use `DynamicallyAccessedMembersAttribute` to inform the AOT compiler about required members:
127+
128+
```csharp
129+
// For methods that access type constructors
130+
private static object CreateInstance(
131+
#if NET6_0_OR_GREATER
132+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
133+
#endif
134+
Type type)
135+
{
136+
return Activator.CreateInstance(type);
137+
}
138+
139+
// For methods that access properties via reflection
140+
private static PropertyInfo? GetProperty(
141+
#if NET6_0_OR_GREATER
142+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
143+
#endif
144+
Type type, string propertyName)
145+
{
146+
return type.GetProperty(propertyName);
147+
}
148+
149+
// For methods that need all members (like view model registration)
150+
public void RegisterViewModel<
151+
#if NET6_0_OR_GREATER
152+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
153+
#endif
154+
TViewModel>()
155+
{
156+
// Registration logic
157+
}
158+
```
159+
160+
**Fallback Approach**: When `DynamicallyAccessedMembersAttribute` isn't sufficient, use suppression attributes:
161+
```csharp
162+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Description of why this is safe")]
163+
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Description of why this is safe")]
164+
public void SetupObservation()
165+
{
166+
// Reflection-based code that can't be made AOT-safe with DynamicallyAccessedMembersAttribute
167+
}
168+
```
169+
170+
**Best Practices**:
171+
- Prefer `DynamicallyAccessedMembersAttribute` over `UnconditionalSuppressMessage` when possible
172+
- Use specific `DynamicallyAccessedMemberTypes` values rather than `All` when you know what's needed
173+
- Prefer strongly-typed expressions over string-based property names when possible
174+
- Use `nameof()` for compile-time property name checking
175+
176+
**ReactiveUI Codebase Examples**:
177+
```csharp
178+
// From ReactiveUIBuilder.cs - View model registration
179+
public IReactiveUIBuilder RegisterSingletonViewModel<
180+
#if NET6_0_OR_GREATER
181+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
182+
#endif
183+
TViewModel>()
184+
185+
// From ExpressionRewriter.cs - Property access
186+
private static PropertyInfo? GetItemProperty(
187+
#if NET6_0_OR_GREATER
188+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
189+
#endif
190+
Type type)
191+
192+
// From DependencyResolverMixins.cs - Constructor access
193+
private static Func<object> TypeFactory(
194+
#if NET6_0_OR_GREATER
195+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
196+
#endif
197+
TypeInfo typeInfo)
198+
```
199+
200+
## 📚 Reference Materials
201+
202+
### Official Samples & Documentation
203+
Use these as references for patterns and implementation examples:
204+
205+
- **ReactiveUI Documentation**: https://reactiveui.net/docs/
206+
- **Official Samples Repository**: Various platform examples (WPF, Avalonia, Uno, etc.)
207+
- **Getting Started Guide**: https://reactiveui.net/docs/getting-started/
208+
- **ViewModels Documentation**: https://reactiveui.net/docs/handbook/view-models/
209+
210+
### In-Repository Examples
211+
- `src/ReactiveUI.AOTTests/` - AOT compatibility test examples showing proper attribute usage
212+
- `src/ReactiveUI.Tests/` - Comprehensive test patterns and API usage examples
213+
- `integrationtests/` - Platform-specific integration examples
214+
- `src/ReactiveUI.Builder.Tests/` - Builder pattern examples for dependency injection
215+
216+
### Key Project Structure
217+
- `src/ReactiveUI/` - Core ReactiveUI library
218+
- `src/ReactiveUI.WPF/` - WPF-specific extensions
219+
- `src/ReactiveUI.WinUI/` - WinUI-specific extensions
220+
- `src/ReactiveUI.Maui/` - MAUI-specific extensions
221+
- `src/ReactiveUI.Testing/` - Testing utilities
222+
223+
## 🎨 Good Development Suggestions
224+
225+
### Creating ViewModels
226+
```csharp
227+
public class SampleViewModel : ReactiveObject
228+
{
229+
private string? _name;
230+
private readonly ObservableAsPropertyHelper<bool> _isValid;
231+
232+
public SampleViewModel()
233+
{
234+
// Setup validation
235+
_isValid = this.WhenAnyValue(x => x.Name)
236+
.Select(name => !string.IsNullOrWhiteSpace(name))
237+
.ToProperty(this, nameof(IsValid));
238+
239+
// Setup commands
240+
SubmitCommand = ReactiveCommand.CreateFromTask(
241+
ExecuteSubmit,
242+
this.WhenAnyValue(x => x.IsValid));
243+
}
244+
245+
public string? Name
246+
{
247+
get => _name;
248+
set => this.RaiseAndSetIfChanged(ref _name, value);
249+
}
250+
251+
public bool IsValid => _isValid.Value;
252+
253+
public ReactiveCommand<Unit, Unit> SubmitCommand { get; }
254+
255+
private async Task ExecuteSubmit(CancellationToken cancellationToken)
256+
{
257+
// Submit logic here
258+
}
259+
}
260+
```
261+
262+
### Using WhenAnyValue / WhenAny
263+
```csharp
264+
// Combine multiple properties
265+
this.WhenAnyValue(
266+
x => x.FirstName,
267+
x => x.LastName,
268+
(first, last) => $"{first} {last}")
269+
.Subscribe(fullName => { /* Handle full name */ });
270+
271+
// React to property changes
272+
this.WhenAnyValue(x => x.IsLoading)
273+
.Where(isLoading => !isLoading)
274+
.Subscribe(_ => { /* Handle loaded state */ });
275+
276+
// String-based observation (AOT-friendly with nameof)
277+
obj.ObservableForProperty<MyClass, string>(
278+
nameof(MyClass.PropertyName),
279+
beforeChange: false,
280+
skipInitial: false)
281+
.Select(x => x.Value)
282+
.Subscribe(HandleChange);
283+
```
284+
285+
### ReactiveUI Builder Pattern
286+
```csharp
287+
// Platform-specific builder configuration
288+
public static class MauiProgram
289+
{
290+
public static MauiApp CreateMauiApp()
291+
{
292+
var builder = MauiApp.CreateBuilder();
293+
294+
builder
295+
.UseMauiApp<App>()
296+
.UseReactiveUI() // Extension for MAUI
297+
.ConfigureFonts(fonts =>
298+
{
299+
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
300+
});
301+
302+
return builder.Build();
303+
}
304+
}
305+
306+
// WinUI builder configuration
307+
public void ConfigureServices(IServiceCollection services)
308+
{
309+
services.AddReactiveUI(builder =>
310+
{
311+
builder.WithWinUI(); // Platform-specific setup
312+
});
313+
}
314+
```
315+
316+
### ObservableAsPropertyHelper
317+
```csharp
318+
// Computed property that reacts to changes
319+
private readonly ObservableAsPropertyHelper<decimal> _total;
320+
321+
public decimal Total => _total.Value;
322+
323+
// In constructor
324+
_total = this.WhenAnyValue(
325+
x => x.Quantity,
326+
x => x.Price,
327+
(qty, price) => qty * price)
328+
.ToProperty(this, nameof(Total));
329+
```
330+
331+
## 🎨 Code Style & Formatting
332+
333+
ReactiveUI enforces strict code style and formatting guidelines:
334+
335+
### EditorConfig & StyleCop Compliance
336+
- **EditorConfig**: The repository has a comprehensive `.editorconfig` file with detailed formatting rules
337+
- **StyleCop Analyzers**: Extensive StyleCop analyzer rules are configured with error-level enforcement
338+
- **Code Analysis**: Over 200 analyzer rules are configured for code quality and consistency
339+
340+
### Key Style Requirements
341+
- **Indentation**: 4 spaces (configured in `.editorconfig`)
342+
- **Braces**: Allman style (opening braces on new lines)
343+
- **Field naming**: Private fields use `_camelCase` prefix, static fields use `s_` prefix
344+
- **Var usage**: Prefer `var` when type is apparent
345+
- **Documentation**: All public APIs require XML documentation
346+
- **Null checking**: Use modern C# null-conditional operators
347+
348+
### Official Style Guide
349+
Follow the comprehensive ReactiveUI style guide:
350+
- **Code Style Guide**: https://www.reactiveui.net/contribute/software-style-guide/code-style.html
351+
- **Commit Message Convention**: https://reactiveui.net/contribute/software-style-guide/commit-message-convention
352+
353+
### Build with Style Enforcement
354+
```powershell
355+
# Build with warnings as errors (includes StyleCop violations)
356+
dotnet build src/ReactiveUI.sln -c Release -warnaserror
357+
```
358+
359+
**Important**: Style violations will cause build failures. Use an IDE with EditorConfig support (Visual Studio, VS Code, Rider) to automatically format code according to project standards.
360+
361+
## 📋 Testing Guidelines
362+
363+
- Always write unit tests for new features or bug fixes
364+
- Use `ReactiveUI.Testing` package for testing reactive code
365+
- Follow existing test patterns in `src/ReactiveUI.Tests/`
366+
- For AOT scenarios, reference patterns in `src/ReactiveUI.AOTTests/`
367+
368+
## 🚫 What to Avoid
369+
370+
- **Reflection-heavy patterns** without proper AOT suppression
371+
- **Expression trees** in hot paths without caching
372+
- **Platform-specific code** in the core ReactiveUI library
373+
- **Breaking changes** to public APIs without proper versioning
374+
375+
## 🔄 Common Tasks
376+
377+
### Adding a new feature:
378+
1. Create failing tests first
379+
2. Implement minimal functionality
380+
3. Ensure AOT compatibility
381+
4. Update documentation if needed
382+
5. Add XML documentation to public APIs
383+
384+
### Fixing bugs:
385+
1. Create reproduction test
386+
2. Fix with minimal changes
387+
3. Verify AOT compatibility
388+
4. Ensure no regression in existing tests
389+
390+
Remember: ReactiveUI emphasizes functional reactive programming patterns, immutability where possible, and clean separation of concerns through the MVVM pattern. When in doubt, prefer reactive streams over imperative code, and always consider the AOT implications of your changes.

0 commit comments

Comments
 (0)