Imported from wieslawsoltes/XamlToCSharpGenerator (
AGENTS.md). Install upstream withnpx skills add wieslawsoltes/XamlToCSharpGenerator. Copyright stays with the author.
Engineering Guide (AGENTS)
This document defines the architectural and coding rules for the project. It is authoritative for all new code and refactors.
1) Core principles (non-negotiable)
SOLID (strict)
- Single Responsibility: every class has exactly one reason to change.
- Open/Closed: extend behavior via composition and interfaces; avoid modifying stable code paths when adding features.
- Liskov Substitution: derived types must be safely substitutable without altering expected behavior or contract.
- Interface Segregation: prefer small, focused interfaces; avoid "god" interfaces.
- Dependency Inversion: depend on abstractions; wire concrete types in the composition root only.
MVVM (strict)
- Views are passive. No UI logic in code-behind beyond
InitializeComponent(). - All inputs are routed to ViewModels via bindings, commands, and behaviors.
- ViewModels are UI-framework agnostic and unit-testable.
- Models and services contain business logic and data access; ViewModels orchestrate them via DI.
- Prefer composition in ViewModels/services/code over inheritance wherever possible,
except where framework base types are required (e.g.,
ReactiveObjectfor ViewModels).
2) Architecture
Layering
- UI (Avalonia Views + XAML): visual composition only.
- Presentation (ViewModels): state, commands, reactive composition.
- Domain/Services: business logic, parsing, validation, domain rules.
- Infrastructure: file system, persistence, external integrations.
Boundaries
- UI depends on Presentation; Presentation depends on Domain; Infrastructure is depended on by Domain or Presentation via interfaces.
- No reference from Domain to UI or Avalonia types.
3) Avalonia UI best practices (aligned with Avalonia codebase)
Reference: https://github.com/AvaloniaUI/Avalonia
Views and styling
- Use XAML for layout and visuals; avoid creating controls in code.
- Define styles and resources in dedicated resource dictionaries and merge them
in
App.axamlto keep styling consistent and maintainable. - Prefer
StaticResourcefor immutable resources andDynamicResourcewhen runtime updates are required.
Data binding
- Use compiled bindings only (no reflection bindings) with explicit
x:DataTypeon all binding scopes (views, DataTemplates, control themes, and resources). - Keep bindings one-way unless user input must update the ViewModel.
- Use
DataTemplatesor aViewLocator(custom, non-reflection) for view lookup.
Custom controls
- Use
StyledPropertyonly for values that must participate in styling. - Prefer
DirectPropertyfor non-styled properties to avoid extra overhead. - For best UI/UX, prefer custom control creation or re-templating using control themes instead of CRUD-style UI.
4) ReactiveUI (required)
Reference: https://github.com/reactiveui/ReactiveUI
ViewModel base
- All ViewModels inherit from
ReactiveObject. - Use
ReactiveCommandfor commands; never use event handlers in code-behind. - Use
WhenAnyValue,ObservableAsPropertyHelper, andInteraction<TIn,TOut>to model state, derived values, and dialogs. - Use
ReactiveUI.SourceGeneratorsfor INPC/ReactiveObject boilerplate where applicable. https://github.com/reactiveui/ReactiveUI.SourceGenerators
Navigation (ReactiveUI routing)
- Use
IScreenwith a singleRoutingStateas the navigation root. - All navigable ViewModels implement
IRoutableViewModel. - Views host navigation via
RoutedViewHost. - Use route segments that are stable, explicit, and testable.
Avalonia integration
- Use
ReactiveUI.Avalonia(latest) and do not useAvalonia.ReactiveUIdirectly. - If a third-party dependency requires
Avalonia.ReactiveUI(e.g., Dock integration), isolate it to the docking layer and do not reference it from app UI code. https://github.com/reactiveui/ReactiveUI.Avalonia
5) Input and interaction via Xaml.Behaviors (required)
Reference: https://github.com/wieslawsoltes/Xaml.Behaviors
- All UI input and events are handled via behaviors/triggers.
- Prefer source-generator-based behaviors/actions (no reflection) wherever available.
- Use trigger behaviors (property, data, loaded/unloaded, routed event) for lifecycles and state transitions.
- Code-behind must not contain event handlers or direct ViewModel calls.
6) Docking layout with Dock for Avalonia (required)
Reference: https://github.com/wieslawsoltes/Dock
- Use Dock.Model.* to represent the docking layout state.
- Use Dock.Avalonia for the view layer and Dock.Model.ReactiveUI for MVVM integration.
- Persist layout state to user settings and restore on startup.
- Keep layout logic in ViewModels; Views only render the Dock model.
7) Text editing with AvaloniaEdit (required)
Reference: https://github.com/AvaloniaUI/AvaloniaEdit
- Use AvaloniaEdit
TextEditorfor all code/text editing surfaces. - Enable syntax highlighting using TextMate grammars/themes.
- Keep editor configuration in ViewModels (options, text, selection) and bind to the view.
8) Data presentation with ProDataGrid (required)
Reference: https://github.com/wieslawsoltes/ProDataGrid
- Use ProDataGrid
DataGridfor all tabular data, tree views, and list displays. - Always use the ProDataGrid model approach with code-based column bindings and fast paths.
- Always enable full filtering, searching, and sorting support.
9) Dependency Injection (Microsoft.Extensions.DependencyInjection)
- Configure services in a single composition root (App startup).
- Use
AddSingleton,AddScoped,AddTransientcorrectly:- Singleton: thread-safe, shared, expensive-to-create services.
- Scoped: per-document or per-operation services created within explicit scopes.
- Transient: stateless lightweight services.
- Never resolve scoped services from singletons without creating a scope.
- Do not dispose services resolved from the container manually.
10) Performance (required)
- Prefer allocation-free APIs:
Span<T>,ReadOnlySpan<T>,Memory<T>,ValueTask,ArrayPool<T>, andSystem.Buffers. - Use SIMD (
System.Numerics.Vector<T>or hardware intrinsics) where it provides measurable wins and keeps code maintainable. - Avoid LINQ in hot paths; use loops and pre-sized collections.
- Minimize boxing, virtual dispatch in tight loops, and avoid unnecessary allocations in render/update loops.
- Profile before and after optimizations; document expected gains.
11) Reflection, source generation, and AOT compatibility (required)
These rules are non-negotiable for production compiler/runtime paths.
Reflection usage policy (strict)
- No reflection in source-generator emitted code. This is mandatory.
- No reflection in runtime helpers executed by emitted code (loader, markup-extension runtime, binding/runtime assignment helpers, hot-reload apply paths).
- Forbidden in emitted/runtime paths:
Type.GetType,GetProperty/GetField/GetMethod,PropertyInfo.SetValue,MethodInfo.Invoke,Activator.CreateInstance,dynamic, runtime expression compilation, runtime IL emit, or any equivalent late-bound invocation. - Implement behavior through compile-time semantic binding and strongly typed emitted calls, delegates, and registries.
- Any change introducing reflection into emitted/runtime paths must be rejected in review.
AOT and trimming policy (strict)
- Generated code and runtime support code must be fully NativeAOT/trimming compatible.
- Do not depend on dynamic code generation or runtime assembly scanning/loading in emitted/runtime paths.
- Do not add production-path dependencies on APIs requiring dynamic code or unreferenced member preservation unless the call site is fully compile-time guarded away from AOT targets.
- Compiler parity work must preserve this contract: feature completeness cannot be achieved by falling back to reflection.
Allowed scope for reflection
- Reflection is allowed only in tests, diagnostics tooling, or development-only utilities that are outside production runtime and emitted-code execution paths.
12) Testing and validation
References:
-
All production code must be covered by unit tests; xUnit is required for unit testing.
-
UI tests must use Avalonia Headless (xUnit integration) and follow the headless testing guidance and helpers for input simulation.
-
Unit-test ViewModels and Domain services.
-
Use integration tests for parsing, IO, and docking layout persistence.
-
UI tests should validate navigation flows, docking, and editor behaviors.
13) Code conventions
- No code-behind event handlers.
- Avoid static state (except truly immutable constants).
- Prefer explicit types where clarity is improved; avoid
varin public APIs. - All public APIs must be documented and unit-tested.
14) Version updates (required)
Version bumps must keep the .NET/package metadata and VS Code extension metadata in sync.
Version source of truth
.NETpackage versioning is defined inDirectory.Build.propsviaVersionPrefixandVersionSuffix.- The VS Code extension version is defined in:
tools/vscode/axsg-language-server/package.jsontools/vscode/axsg-language-server/package-lock.json
How to bump to the next alpha version
- Increment
VersionSuffixinDirectory.Build.props(for examplealpha.23->alpha.24). - Update
tools/vscode/axsg-language-server/package.jsonto the matching semver prerelease (for example0.1.0-alpha.24). - Update the root
versionfields intools/vscode/axsg-language-server/package-lock.jsonto the same value. - Verify there are no stale references to the previous prerelease in the authoritative version files before committing.
Rules
- Keep the repository on a single prerelease version; do not leave
.NETand VS Code extension versions diverged. - Use a dedicated commit for version bumps when practical.
- Do not treat README examples like
0.1.0-localas release-version sources.
15) Fluent icons path data (required)
References:
-
Use Microsoft Fluent System Icons for all
PathIcon/IconPathDatageometry. -
Do not hand-draw or invent SVG path strings for product UI icons.
-
Prefer
regularvariants for standard UI chrome. -
Prefer
20size source icons for toolbar usage; render using the existing control size in XAML. -
Keep icon path data in named constants (for example
OpenFolderIconPath) and reuse it.
How to get Fluent icon path data
- Pick an icon name from the Fluent icon catalog (for example
folder_open_20_regular). - Download the SVG from npm/unpkg, for example:
https://unpkg.com/@fluentui/svg-icons@<version>/icons/<icon-name>.svg - Copy the value of the SVG
<path d="..."/>attribute exactly. - Use that value directly in
PathIcon.Dataor extensionIconPathData. - If an icon contains multiple
<path>elements, choose a Fluent icon variant with a single path forPathIcon.Data, or compose a geometry only when needed.
CLI workflow (example)
npm view @fluentui/svg-icons version
npm pack @fluentui/svg-icons@<version>
tar -xzf fluentui-svg-icons-<version>.tgz
cat package/icons/folder_open_20_regular.svg
Then copy the d attribute from the <path> element into code.
16) SourceGen XAML compiler parity guardrails (required)
These rules prevent regressions in the source-generator compiler pipeline.
Parser and semantic model rules
- Preserve owner-qualified property tokens exactly as authored in XAML (
Owner.Property). Do not strip owner prefixes in the parser. - Keep design-time-only members (
Design.*, ignored design namespaces) out of runtime semantic binding and runtime code emission. - Keep diagnostics location-accurate (file, line, column) and deterministic.
Property element binding order (strict)
- Property-element binding must follow this order:
- Skip design-only members.
- Handle explicit child-attachment aliases (
Content,Children,Items). - Bind object values.
- Resolve attached-property elements from owner-qualified tokens.
- Handle dictionary-merge semantics.
- Handle collection-add semantics.
- Handle Avalonia-property assignment.
- Fall back to CLR settable-property assignment.
- Never run scalar CLR setter cardinality checks before collection/add and Avalonia-property checks, otherwise valid multi-item collection property elements regress.
Style and ControlTheme setter resolution
- For setter
Propertytokens, resolve owner-qualified attached-property tokens first. - If attached Avalonia property resolution succeeds, do not emit missing CLR-property
diagnostics (
AXSG0301/AXSG0303) for that setter. - Use resolved Avalonia property metadata for value type conversion and duplicate setter detection.
Binding emission safety rules
- If a value is binding-like (
Binding,MultiBinding, compiled/runtime binding expression), emit Avalonia binding assignment via indexer descriptor path, not CLR casts. - Do not emit direct typed CLR assignment for binding-like values (
(bool)binding,(string)binding, etc.). - Treat both inline markup-extension bindings and object-element binding nodes as binding-like.
Warning policy and parity intent
- Treat warning reduction as semantic parity work, not blanket suppression.
- Preserve strict diagnostics where parity requires explicit author intent
(
x:DataTypefor compiled bindings, template validation, etc.). - Do not downgrade warnings to hide unsupported behavior; either implement behavior or keep the warning actionable.
17) Hot reload and incremental generator reliability (required)
Incremental generator stability
- Generated hint names must be unique and stable per logical XAML input.
- Repeated runs and partial rebuilds must not emit duplicate hint names.
- AdditionalFiles discovery must avoid duplicate logical inputs and editor backup files.
Hot reload error resilience
- Hot reload path must be tolerant to transient invalid XAML while editing.
- On parse/semantic failure during hot reload, keep last known good generated output; do not apply partial invalid graph updates.
- Resume normal hot reload application automatically once XAML becomes valid again.
Hot reload apply/revert semantics
- Runtime hot reload must track and clean up removable graph artifacts (styles, resources, templates, merged dictionaries, theme entries) when they are removed from XAML.
- Applying an edit and then removing it must converge to the same runtime state as the original baseline.
- For app-specific side effects outside generated graph, use explicit hot-reload handler policies rather than implicit best-effort cleanup.
Minimal-diff live editing (strict)
- Hot design and related editing tools must compute and propagate minimal text-diff metadata (replace start offset, removed length, inserted length) for each source update.
- Source persistence paths must support no-op diff detection and avoid rewriting source files when there is no effective text change.
- Minimal-diff behavior must be deterministic and test-covered for replace/insert/delete cases.
Mandatory hot reload and hot design coverage
- Any new parser/binder/emitter feature is incomplete unless hot reload and hot design behavior is explicitly implemented and verified.
- For each new feature, define live-edit semantics for:
- apply while editing,
- revert/removal convergence to baseline,
- failure handling with last-known-good behavior when applicable.
- Add tests that exercise both compile-time emission and runtime live-edit behavior for the feature.
18) XAML compiler and semantic-binder implementation standard (required)
These rules are mandatory for parser, semantic model, transforms, binder, emitter, and runtime contracts. They apply to all new features and all refactors.
Normative behavior baseline
- Language semantics must follow the XAML standard and preserve behavioral parity with mature compile-time XAML compilers.
- Framework-specific behavior (for example Avalonia property systems, selectors, templates) must be implemented as framework adapters over a framework-neutral semantic core.
- Never implement feature parity using string-shape hacks, reflection, or runtime guesswork.
Mandatory anti-hack rules (pattern -> required implementation)
- Markup extension detection:
- Forbidden: lexical head-token heuristics on raw text.
- Required: parser-first structured markup parsing, then semantic dispatch by parsed extension name/type.
- Value classification:
- Forbidden: inferring semantic kind from emitted C# expression text.
- Required: typed conversion results carrying explicit value kind and runtime requirements.
- Template/style/control-theme classification:
- Forbidden: suffix checks such as
EndsWith("Template"). - Required: symbol-based or node-kind-based classification.
- Forbidden: suffix checks such as
- Static resource resolver usage:
- Forbidden: deciding resolver requirements by scanning generated expression strings.
- Required: explicit semantic flags propagated from conversion/binding results.
- Template validation:
- Forbidden: reparsing
RawXamlin binder validation paths. - Required: validate from parsed document-model nodes with source line/column from nodes.
- Forbidden: reparsing
- Property-element and setter resolution:
- Forbidden: early missing-property diagnostics before attached/Avalonia-property resolution.
- Required: resolve owner-qualified/attached/Avalonia property metadata first, then emit missing-property diagnostics only when all typed resolution paths fail.
- Fallback conversion policy:
- Forbidden: untracked implicit fallback that changes behavior silently.
- Required: policy-driven fallback (
strictvscompatibility) with explicit diagnostics where applicable and deterministic emission behavior.
- Type resolution:
- Forbidden: unordered probing and unstable candidate selection.
- Required: deterministic ordered resolution with ambiguity diagnostics and explicit compatibility switches for legacy fallback behavior.
Root-cause-first fix policy (strict)
- Do not ship heuristic patches or one-off hacks to "make it work".
- Every bug fix must start with root-cause analysis at the correct semantic layer (parser, binder, transform, emitter, or runtime contract).
- Implement fixes using established XAML compiler patterns and standard XAML semantics, not ad-hoc behavior guesses tied to specific samples.
- If behavior differs from mature XAML compilers, align semantics through typed models and deterministic rules, not special-case string or control-name checks.
- Any compatibility fallback must be explicit, documented, deterministic, and covered by diagnostics/tests; silent heuristic fallback is forbidden.
Semantic pipeline contracts
- Pipeline stages must stay explicit and ordered:
- Parse to immutable model.
- Semantic bind to typed symbols/contracts.
- Run transforms on typed model.
- Emit deterministic C# from typed model only.
- Every stage must be deterministic for identical inputs (including diagnostics ordering).
- Binder output models must be sufficient for emission without reparsing raw XAML text.
- Any framework-specific feature must be represented in typed semantic models first, then projected by framework emitter/runtime adapters.
Reusability and framework-neutral design
- Keep core semantic abstractions free of Avalonia runtime types whenever possible.
- Isolate framework-specific constructs in adapter layers (
*.Avalonia), not in core parser or core semantic model. - When adding a new XAML feature, define:
- framework-neutral semantic representation in Core,
- framework adapter mapping in Avalonia layer,
- runtime contract only if strictly required.
- Do not introduce Avalonia-only assumptions into generic parsing/tokenization logic.
Parser infrastructure reuse (strict)
- All mini-language parsing/tokenization must use the shared parser infrastructure project
(
XamlToCSharpGenerator.MiniLanguageParsing) instead of ad-hoc binder/runtime parsing. - Add new parsers/tokenizers there first (framework-neutral), then consume them from framework adapters and runtime tooling.
- Live-edit tooling (hot reload/hot design) must reuse the same parser infrastructure whenever structural parsing is required, so behavior stays aligned across compile-time and runtime paths.
Diagnostics quality requirements
- Diagnostics must be actionable, deterministic, and source-accurate.
- Ambiguity diagnostics must list candidates and deterministic selection result.
- Strict-mode diagnostics must not be downgraded to hide unsupported semantics.
- Compatibility-mode behavior must be explicit and test-covered.
Test gates (mandatory for each semantic change)
- Add/adjust unit tests for:
- parser output shape,
- binder semantic resolution,
- emitted C# contract,
- diagnostics IDs/messages/locations.
- Add differential tests for representative framework cases (templates, resources, bindings, setters, includes) against expected runtime behavior.
- Add guard tests preventing regression to prohibited patterns (raw-XAML reparse in binder, suffix heuristics, expression-text scanning for semantic decisions).
