You're reading the Rask v1.0.0 guides.View source
All guides

Rask diagnostics (RASK001–RASK071, RASKVAL001–RASKVAL002)

Every Rask diagnostic, what triggers it, and how to fix it. Errors block the build; warnings don't but flag a real problem; the hidden ones are informational, surfaced only as an IDE suggestion.

These come from the Rask source generator and analyzers (Rask.Generators). The generated chain surface doesn't exist until a build runs, so if an ID below isn't recognised by your IDE yet, build once.

Some diagnostics ship an IDE quick-fix (the lightbulb / Ctrl+.):

ID What the lightbulb does
RASK001 adds the required modifier
RASK014 rewrites new Widget() into the bare entry Widget
RASK023 appends .Alt("") to the chain (or Alt: "" on a factory call)
RASK026 deletes the redundant StateHasChanged() statement
RASK027 removes the OnXAsync argument, keeping the sync one
RASK067 swaps ASP.NET's [Route] for Rask's own
CS0108 adds new to a member that hides a builder entry

These are delivered by Rask.Generators.CodeFixes, packed alongside the analyzers in the Rask.Server / Rask.Wasm packages — no extra reference needed.

A fix is offered only when the rewrite means exactly what you wrote. RASK014's is withheld when the construction has arguments or an object initializer: a chain sets each property by name in its own step, so moving positional constructor arguments across would compile and mean something else — and an object initializer is only legal after new. In those cases the error stands with its message, which already spells out the chain to write.

Every RASK diagnostic reports under the single category Rask, so one .editorconfig line covers the family:

dotnet_analyzer_diagnostic.category-Rask.severity = warning
ID Severity Summary
RASK001 Hidden Property is treated as a required factory parameter
RASK002 Warning required property cannot be honored by the chain
RASK003 Error Malformed route template
RASK004 Error Route segment has no matching property
RASK005 Error Property type does not match the route constraint
RASK006 Error [QueryParam] applied to a path-segment property
RASK007 Error [ParentRoute] cycle
RASK008 Error [RouteParam] without a matching path segment
RASK009 Error [RouteParam] on a non-routed class
RASK010 Error [QueryParam] on a non-routed class
RASK011 Error Route/query param type must implement IParsable<T>
RASK012 Error Multiple [NotFound] components
RASK013 Error [NotFound] cannot be combined with [Route]
RASK014 Error Components must be built through a chain
RASK015 Error Orphan scoped-CSS file
RASK016 Error Ambiguous scoped-CSS match
RASK017 Error Orphan scoped-TS file
RASK018 Error Ambiguous scoped-TS match
RASK019 Error <head> is a framework-managed slot
RASK020 Warning Scoped-TS simple-name collision
RASK021 Warning Root component must not render the page shell
RASK022 Warning List item is missing a Key
RASK023 Warning Img is missing Alt text
RASK024 Warning UseAuthentication() must precede UseRask()
RASK025 Warning InputType conflicts with the bound Input<T> value type
RASK026 Warning Redundant StateHasChanged in a Rask callback
RASK027 Error Both the sync and async handler are set for one event
RASK028 Error Ambiguous request handler (more than one handler for a query/command)
RASK029 Warning Handler cannot be registered (open generic, no public constructor, or unnameable)
RASK031 Warning Two pages resolve to the same route
RASK032 Retired — native chrome cannot sit inside an HTML tree
RASK033 Warning Hardcoded path for internal navigation instead of the generated route URL
RASK034 Retired — the BsDataGrid it analysed went with Rask.Bootstrap
RASK035 Warning Background job or outbox event type cannot be registered
RASK036 Warning A builder-entry host must be partial
RASK037 Warning using alias is hidden by a builder entry
RASK038 Error Builder chain does not set a required property
RASK039 Warning Builder chain is split across statements, so its required properties can't be checked
RASK040 Warning Two components share a simple name, so neither can have a builder entry
RASK041 Warning The builder surface's shared pending-bit budget is exhausted
RASK042 Retired — delegate-typed property cannot receive a builder setter
RASK043 Warning A component name is used in a type that has no builder entries
RASK044 Warning Builder chain sets the same property twice
RASK045 Warning Component built by a chain is assigned to afterwards
RASK046 Warning Key must open a component's chain
RASK047 retired — routes are [Route] attribute arguments, constant by construction
RASK048 Retired — HTML cannot sit inside a native screen
RASK049 Retired — a NativeWebView set a Url and took children
RASK050 Retired — a native head was named on a web-only host
RASK051 Error Translation catalog is malformed
RASK052 Warning Translation catalog disagrees with the neutral catalog
RASK053 Error Remote message has no wire encoding
RASK054 Info Page cannot run in the browser
RASK055 Error Scoped JavaScript is no longer supported
RASK056 Error External component must be partial
RASK057 Error External component prop has no wire encoding
RASK058 Error External component name collision
RASK059 Error Module override must be a constant string
RASK060 Warning AddRask is called twice on the same service collection
RASK061 Error Blazor island must be partial
RASK062 Error An island takes no children
RASK064 Error Blazor island name collision
RASK066 Warning Hosted Blazor component's parameters cannot be verified
RASK067 Error Endpoint shape has no wire encoding
RASK068 Warning Endpoint has no generated client method
RASK069 Error Two endpoints claim one client method
RASK070 Warning Endpoint's response type is not statically known
RASK071 Error ASP.NET route attribute on a Rask component
RASKVAL001 Error Two validators for the same model
RASKVAL002 Warning Validator cannot be constructed automatically

RASK001

Property is treated as a required chain step · Hidden

A non-nullable reference-type property with no initializer becomes a required step — one the chain must take before it produces a component at all. This is informational: the generator already enforces it through the chain's type, but the property isn't marked required at the language level.


public sealed partial class Badge : Component
{
    public string Label { get; set; }   // RASK001 suggestion: add `required`
}

Fix (optional): add required for language-level enforcement (quick-fix available — the IDE lightbulb inserts it), or make the property nullable (string? Label) if it should be optional. HTML-attribute props are intentionally declared nullable to stay ergonomic. See what becomes a step.

RASK002

required property cannot be honored by the chain · Warning

A property is marked required, but the chain can't set it. This fires in exactly one shape: the component has both a dependency-injected constructor and a parameterless constructor, and the required property carries a member initializer. The entry then builds the component with new C(), but an initializer-carrying property is not one the steps can set, so nothing ever assigns it and the consumer build fails with CS9035.

A DI constructor with no parameterless constructor is fine: the entry builds the component with ActivatorUtilities.CreateInstance (which runs your DI constructor, so injected services are set) and the steps assign afterwards — so a required property with no member initializer is honored. RASK002 does not fire in that case.

Fix: remove the member initializer so the required property becomes a plain factory parameter, or remove required. Framework services (RouteState, Navigator, HttpClient, IJSRuntime) should come through the constructor, never as settable properties.

RASK003

Malformed route template · Error

A [Route("...")] template can't be parsed (unbalanced braces, empty segment, illegal constraint).

Fix: correct the template, e.g. [Route("/users/{id:int}")].

RASK004

Route segment has no matching property · Error

A {segment} in the route has no public settable property to bind to.

Fix: add a matching property and annotate it: [RouteParam] public int Id { get; set; }.

RASK005

Property type does not match the route constraint · Error

The route constraint (e.g. {id:int}) is incompatible with the bound property's CLR type.

Fix: align the types — {id:int}int Id, {slug}string Slug.

RASK006

[QueryParam] applied to a path-segment property · Error

A property is bound by a {path} segment and marked [QueryParam]. A value can't come from both.

Fix: use [RouteParam] for path segments; reserve [QueryParam] for query-string values.

RASK007

[ParentRoute] cycle · Error

[ParentRoute(typeof(...))] links form a cycle, so no root can be established.

Fix: break the cycle so the parent chain terminates at a top-level route.

RASK008

[RouteParam] without a matching path segment · Error

A property is [RouteParam] but no {segment} in the template (or an ancestor's, via [ParentRoute]) matches its name.

Fix: add the segment to the template, or rename the property/segment to match.

RASK009

[RouteParam] on a non-routed class · Error

[RouteParam] sits on a class that isn't a valid route target (no [Route], not reachable via [ParentRoute]).

Fix: add [Route] to the class, or remove the attribute.

RASK010

[QueryParam] on a non-routed class · Error

As RASK009, for [QueryParam]. Query binding only applies to routed pages.

Fix: add [Route], or remove [QueryParam].

RASK011

Route/query param type must implement IParsable<T> · Error

A [RouteParam]/[QueryParam] property's type can't be parsed from a URL string. Bound types must be string or implement System.IParsable<T> (every built-in numeric, Guid, DateTime, bool, enums via custom parsing, etc. qualify).

Fix: use a parsable type, or accept the value as string and convert inside the page.

RASK012

Multiple [NotFound] components · Error

More than one [NotFound] catch-all page exists in the assembly; only one is allowed.

Fix: keep a single [NotFound] page and remove the duplicate.

RASK013

[NotFound] cannot be combined with [Route] · Error

A class carries both [NotFound] and [Route]. [NotFound] is the catch-all and matches no specific path.

Fix: remove [Route] from the not-found page.

RASK014

Components must be built through a chain · Error

new SomeComponent() was used outside Rask.Core. Components are built by naming them and chaining onto them, which is what routes the first step through GetOrCreate — the identity the runtime reconciles across renders — and what wires keys, children, and DI consistently. new skips all of it, and can also produce a component whose required properties were never set.


// ✗ new Div
// ✓ Div.Class("panel")[ ... ]

Fix: name it and chain onto it — Counter.Start(3), or Counter alone when it needs nothing. In test files that deliberately construct components directly, opt out per file with #pragma warning disable RASK014.

RASK015

Orphan scoped-CSS file · Error

A {Name}.css sibling has no matching Component subclass named {Name} in the same folder, so it can't be scoped to anything.

Fix: rename the file to match its component, move it next to the right component, or exclude it with <RaskScopedCssAutoInclude>false</RaskScopedCssAutoInclude> if it's a global stylesheet. bin/, obj/, node_modules/ and wwwroot/ are already excluded, so a global stylesheet under wwwroot/ — the placement js-interop.md recommends — never trips this.

RASK016

Ambiguous scoped-CSS match · Error

A {Name}.css file matches more than one component class named {Name} (e.g. two Card types in different namespaces but the same folder).

Fix: disambiguate by moving one component/file so each .css has exactly one match.

RASK017

Orphan scoped-TS file · Error

As RASK015, for a {Name}.ts sibling with no matching component.

Fix: rename/move the file, or opt the file out of auto-inclusion with <RaskScopedTsAutoInclude>false</RaskScopedTsAutoInclude>.

RASK018

Ambiguous scoped-TS match · Error

As RASK016, for {Name}.ts matching multiple component classes.

Fix: disambiguate so each .ts file maps to one component.

RASK019

<head> is a framework-managed slot · Error

Children were passed to Head(). Rask collects, dedupes, and splices head content itself, so the <head> element doesn't take children.

Fix: override protected override Component? Head => ... on any component and return your Title/Meta/Link/Script — a single tag, or a collection expression like Head => [Title(...)["..."], Meta(...)]. See the head guide.

RASK020

Scoped-TS simple-name collision · Warning

Two or more components with scoped TypeScript share the same simple type name. The browser-side namespace key window.Rask["Name"] is shared, and the last registration silently wins.

Fix: rename one component, move it to a differently-named sibling, or expose its exports under a sub-namespace inside the TypeScript file. Promote to an error with <WarningsAsErrors>RASK020</WarningsAsErrors>.

RASK021

Root component must not render the page shell · Warning

The root TApp renders into <body> — Rask emits the doctype, <html>, <head> and <body> around whatever it returns. A root that builds them itself nests a second document inside the body, which the HTML parser silently unwraps: the page keeps rendering, and quietly loses the nested tags' attributes. Nothing fails, so this warning is the only signal there is.


// ✗ RASK021 — the root builds the document
protected override Component? Render() =>
    [Doctype, Html("en")[Head, Body[Router]]];

// ✓ the root renders the body's content
protected override Component? Render() => Router;

Fix: return the body's content (usually Router()) and move the shell's pieces to the overrides that own them — <head> content to Head, <html lang> to HtmlLang, <html dir> to HtmlDir, <body class> to BodyClass, and a genuinely custom document to Shell(head, body), which receives the framework's <head> and the rendered body as parameters. Do not add a runtime <script>; it's auto-appended to <body>. Doctype/Html/Head/Body stay ordinary tag components for documents you build by hand (ToHtml(), an email body) — they have just left the app-authoring path. See the document and the Head override.

RASK022

List item is missing a Key · Warning

A Rask component appears in a sibling-list context (a .Select/.SelectMany projection, or .Add in a loop) without a Key. Keyless items reconcile by position, which loses focus and input state and emits untrusted structural diffs on insert/remove/move — and for a component, position also decides which instance is reused, so the row's own state follows the slot rather than the item (see RASK046).

Both chain spellings are recognised — Li[…] and Li.Class("c")[…]. The chain went unreported until #704, when the only surface this checked was a factory call: the check matched a method named after the component, and a chain has none.


// ✗ items.Select(i => Li[ i.Name ])
// ✓ items.Select(i => Li.Key(i.Id)[ i.Name ])

Fix: pass a stable .Key(…) (an entity id, not the loop index). See keyed lists and the live-rendering architecture for why identity beats position.

RASK023

Img is missing Alt text · Warning

An Img is built without naming Alt. Without a text alternative, screen readers fall back to announcing the file name (or nothing), failing WCAG 1.1.1.

Both chain spellings are recognised — Img.Src("/x") and the bare Img. The chain went unreported until #704, when the only surface this checked was a factory call: it matched a static method named Img, and on a chain the outermost call is the Src setter — so an accessibility check that the docs' own examples should have tripped never fired at all.


// ✗ Img.Src("/logo.png")
// ✓ Img.Src("/logo.png").Alt("Rask logo")
// ✓ Img.Src("/divider.png").Alt("")   // decorative: empty alt hides it from assistive tech

Fix: pass a meaningful Alt:, or the empty string Alt: "" for a purely decorative image so assistive technology skips it (quick-fix available — the IDE lightbulb inserts Alt: "", which you then fill in for informative images). See accessibility.

RASK024

UseAuthentication() must precede UseRask() · Warning

app.UseRask<App>() is wired before app.UseAuthentication(). Rask seeds the live session from HttpContext.User during the initial GET render and the WebSocket upgrade — if the authentication middleware runs after UseRask, the principal is empty at that point and every [Authorize] page challenges.


// ✗ app.UseRask<App>();
//   app.UseAuthentication();
// ✓ app.UseAuthentication();   // populates HttpContext.User on GET + WS upgrade
//   app.UseAuthorization();
//   app.UseRask<App>();

Fix: call app.UseAuthentication() (and app.UseAuthorization()) before app.UseRask(). The warning fires only when both calls are present and UseAuthentication is positioned after UseRask; an app with no authentication middleware is left alone. See authentication.

RASK025

InputType conflicts with the bound Input<T> value type · Warning

A generic Input<T> derives its HTML input type from T (bool→checkbox, int/decimal→number, DateOnly→date, …). The string-only InputTypes — Text, Search, Tel, Url, Email, Password — only apply to Input<string>; pairing one with Input<int>/Input<bool>/… is a mistake (the entered value could never round-trip to T).


// ✗ Age is int — a number input can't be an email field:
Input.Bind(() => model.Age).Type(InputType.Email)
// ✓ let the type derive from T (int → number):
Input.Bind(() => model.Age)
// ✓ or use a string-only type on a string field:
Input.Bind(() => model.Email).Type(InputType.Email)

Fix: drop the explicit Type (it's inferred from T), or bind a string. The warning fires only for a statically-known string-family InputType on a non-string Input<T>; Input<int>(Type: InputType.Number) and any Input<string> are left alone. Suppressible like any analyzer.

RASK026

Redundant StateHasChanged in a Rask callback · Warning

Rask re-renders the component that owns an event/binding callback automatically after the callback runs — including when a child control raised it (the framework re-renders the delegate's owner, captured from the lambda's this) and after a two-way bound write (the binding re-renders its authoring component). So calling your own StateHasChanged() from inside OnChange/OnClick/OnInput/OnSubmit/… or the AfterBind/AfterBindAsync hooks is dead weight. The tell-tale anti-pattern is reaching for AfterBind: _ => StateHasChanged() to make derived UI refresh.


// ✗ redundant — the framework re-renders this component after OnChange runs:
Select<string>().Value(_pick).OnChange(v => { _pick = v; StateHasChanged(); })
// ✓ just update state; the render is automatic:
Select<string>().Value(_pick).OnChange(v => _pick = v)

// ✗ AfterBind only to force a re-render of a sibling readout:
RadioGroup(() => model.Plan, options, AfterBind: _ => StateHasChanged())
// ✓ a two-way write already re-renders the binding's owner — derived UI updates on its own:
RadioGroup(() => model.Plan, options)

Fix: remove the StateHasChanged() call. If derived UI still isn't updating, the problem is the owner of the callback or binding (write the lambda where it captures the right this, or bind the model the consumer reads), not the render count. The warning fires only for a self-call (StateHasChanged() / this.StateHasChanged()) lexically inside a Rask callback; a StateHasChanged in a lifecycle hook, async loop, or event subscription (feed.Updated += StateHasChanged), or a call on a different component, is left alone. Suppressible like any analyzer.

RASK027

Both the sync and async handler are set for one event · Error

Every DOM event on a component maps to a single handler slot. The typed OnX (sync) and OnXAsync (async) properties are two views over that one slot, so wiring both for the same event — e.g. Button.OnClick(...).OnClickAsync(...) — is a mistake: the runtime keeps the sync handler and silently ignores the async one, which is rarely what the author intended. Set exactly one handler per event.


// ✗ both set — OnClickAsync is silently dropped at runtime:
Button.OnClick(() => Toggle()).OnClickAsync(async () => await SaveAsync())["Save"]
// ✓ pick one — the async handler, since it awaits:
Button.OnClickAsync(async () => await SaveAsync())["Save"]
// ✓ passing null for the sibling is allowed (a deliberate "at most one" conditional):
Button.OnClick(useAsync ? null : Sync).OnClickAsync(useAsync ? Async : null)["Save"]

Fix: remove one of the two handlers (keep the async OnXAsync if it awaits, else the sync OnX). The error fires only when both siblings are passed as non-null arguments to the same factory call; passing null for one (a conditional "set at most one") is left alone. Applies to every paired event, including form callbacks (OnInput/OnInputAsync, OnChange/OnChangeAsync, …). Suppressible like any analyzer.

RASK028

Ambiguous request handler · Error

A query (IQuery<T>) or command (ICommand / ICommand<T>) must have exactly one handler — the Rask.Cqrs dispatcher maps each request type to a single handler, so two handlers for the same request would make dispatch non-deterministic. The generator reports this on each competing handler.


public sealed record GetValue : IQuery<int>;
public sealed class HandlerOne : IQueryHandler<GetValue, int> { /* ... */ } // ✗ RASK028
public sealed class HandlerTwo : IQueryHandler<GetValue, int> { /* ... */ } // ✗ RASK028

Fix: keep a single handler for the request type (merge the logic, or split into two distinct request types). Notifications are exempt — an INotification may have any number of INotificationHandlers.

RASK029

Handler cannot be registered · Warning

A discovered handler can't be registered in DI, so it is skipped and dispatching its request would throw at runtime. The causes are an open generic handler (its type parameters can't be closed at registration time), a handler with no public constructor (the container can't build it), and a handler the generated registry can't name — one declared file-local, or private/protected at any level of its containing chain. That last group used to emit code that didn't compile (CS0234 / CS0122) instead of being skipped.


public sealed record GetValue : IQuery<int>;
public sealed class PrivateHandler : IQueryHandler<GetValue, int>
{
    private PrivateHandler() { }                                          // ✗ RASK029: no public ctor
    public Task<int> Handle(GetValue query, CancellationToken ct) => Task.FromResult(1);
}

Fix: give the handler a public constructor, make it a closed (non-generic) type, or raise its accessibility to at least internal and move it out of a file-local declaration. A request with no handler at all is not flagged (the handler may live in another assembly) — it throws a clear InvalidOperationException when dispatched.

RASK030

Retired. It asked you to name the arguments of a factory call once three or more were positional, because the generated parameter order could shift under an unrelated edit and silently rebind them. A chain has no positional arguments — every step names its property — so there is nothing left to misbind. The id is not reused.

RASK031

Two pages resolve to the same route · Warning

Two different top-level pages resolve to the same route, so both match the same URL and which one renders is arbitrary. Templates are compared the way the runtime router matches them, not by raw string — so these all collide: /Products/products (literals match case-insensitively), /productsproducts/ (surrounding slashes trimmed), and /item/{id:int}/item/{id:guid}/item/{slug} (the router ignores parameter names and :constraints). The check covers pages without a [ParentRoute] (whose template is the full path); parent-composed paths are not resolved here, so nested-route collisions are not flagged (the check under-reports rather than risk a false positive).


[Route("/products")] public sealed partial class ProductList : Component { }   // first — canonical
[Route("/Products")] public sealed partial class ProductGrid : Component { }   // ✗ RASK031: same URL as ProductList

A warning, not an error — a collision is a real bug, but the app still runs (it just picks arbitrarily), so upgrading Rask never hard-breaks a build that compiled before.

Fix: give one page a distinct route, or merge the two. Reported on every colliding page after the first (ordered by fully-qualified name), naming the page it collides with.

RASK033

Hardcoded path for internal navigation instead of the generated route URL · Warning

Rask generates a type-safe RouteUrl factory — Routes.<Page>() — for every page's primary [Route] (see Routing → type-safe URLs). Using the raw path string for internal navigation bypasses that safety: rename or remove the [Route] and the string becomes a silent dead link that still compiles, whereas Routes.<Page>() becomes a compile error you fix immediately. The analyzer flags a string literal passed to internal navigation — Navigator.NavigateTo("…") or any RouteUrl slot (NavLink(Href: …), BsNavItem.Href(…), via the string → RouteUrl implicit conversion) — only when the path maps to a generated parameterless factory.

It deliberately leaves alone:

  • External URLshttps://…, or anything wrapped in RouteUrl.External("…").
  • Parameterised routes/users/42 needs Routes.UserPage("42"), which can't be reconstructed from a bare literal.
  • Secondary [Route] templates — the factory formats a page's first template only, so a literal like /todos/new on a page whose primary route is todos has no Routes.*() equivalent and is not flagged.

[Route("todos")] public sealed partial class TodosPage : Component { /* … */ }

nav.NavigateTo("/todos");            // ✗ RASK033 — use Routes.TodosPage()
NavLink.Href("/todos")["Todos"];    // ✗ RASK033 — string → RouteUrl conversion

nav.NavigateTo(Routes.TodosPage());  // ✓ type-safe; a renamed route is a compile error
nav.NavigateTo("/todos/new");        // ✓ secondary template — no factory, left alone
A("https://example.com", "_blank")["Docs"]; // ✓ external — untouched

Fix: call the generated Routes.<Page>() (with arguments for any route/query params). For a genuinely dynamic or external target, use RouteUrl.External("…"), or suppress with #pragma warning disable RASK033 / .editorconfig (dotnet_diagnostic.RASK033.severity = none).

RASK035

Background job or outbox event type cannot be registered · Warning

A type implementing IBackgroundJob or IOutboxEvent was found, but the generated registry can't map its stored name to its CLR type — so it is skipped. Enqueuing it still writes a row; the processor then fails to rehydrate it, records No registered job type '…', and retries until MaxAttempts before dead-lettering. Before this diagnostic existed the type was skipped silently, which is exactly what made the failure hard to place: the job looked queued, then quietly stopped.

The reasons, all about reconstructing a runtime Type.FullName from a name the generated file can write:

Shape Why
Genericrecord Reindex<T> : IBackgroundJob A closed generic's FullName carries assembly-qualified type arguments, so no static key matches.
Nested in a genericclass Outer<T> { record Ev : IOutboxEvent; } Naming it would leak T into the generated file.
file-localfile record Ev : IOutboxEvent; Invisible outside its own file, and its FullName carries a synthesized <file>F0__ segment.
Inaccessibleprivate/protected at any level of its containing chain The generated registry lives in the same assembly but a different file, so it can't name the type.

An abstract base carrying the marker is skipped without a warning — modelling a hierarchy that way is normal, and its concrete derivatives register as usual.


public class Outer<T>
{
    public sealed record Raised(int Id) : IOutboxEvent;    // ✗ RASK035: nested inside the generic type 'Outer'
}

public static class OrderEvents
{
    public sealed record Raised(int Id) : IOutboxEvent;    // ✓ nested in a non-generic type is fine
}

Fix: move the type out of the generic (or file-local, or inaccessible) declaration, and make it non-generic — nesting inside a plain static class is the usual way to keep events grouped. Suppress with #pragma warning disable RASK035 / .editorconfig (dotnet_diagnostic.RASK035.severity = none) only if you never enqueue that type.

RASK036

A builder-entry host must be partial · Warning

Rask's own components (Div, BsCard, …) get their entries from Rask.Core.RaskMarkup, which Component derives from — so every component inherits them, and so does anything else that derives from RaskMarkup (a test class, a fixture, a factory of demo components). Your components cannot ride there: a source generator can only add members to types in the compilation it is running in, and RaskMarkup lives in a referenced assembly. using static is not a way out either — a static-imported member loses to a same-named type in scope (CS0119), which is the whole reason the entries are inherited rather than imported.

So the entry for each of your components is injected into every other type of yours that might name one — every component, and every RaskMarkup host — which needs a partial to inject it into:


public sealed class Dashboard : Component        // ✗ RASK036 — no partial to inject into
{
    protected override Component? Render() => Div[SalesCard];   // CS0103: 'SalesCard' not found
}

public sealed partial class Dashboard : Component   // ✓
{
    protected override Component? Render() => Div[SalesCard];
}

public partial class DashboardTests : RaskMarkup     // ✓ — same rule outside a component
{
    [Fact]
    public void It_renders() => Assert.Equal("<div>…</div>", Div[SalesCard].ToHtml());
}

[RaskMarkup]                                         // ✓ — and when the base slot is not yours
public static partial class Demos                    //     to spend, or there is none to spend
{
    public static Component Badge() => Div[SalesCard];
}

For a host that derives from RaskMarkup, nothing else is lost: the component still renders, still gets its own entry elsewhere, and the type itself is unaffected — new SalesCard() inside Rask.Core keeps working from anywhere. An [RaskMarkup] host loses more, and the message says so: the generated partial is where its base — or, when the base slot is already spent, the framework tags themselves — would have come from, so without partial it gets no builder surface at all.

A nested host is injected into as well — the generated file re-opens each enclosing type as a partial around it — so every one of them has to be partial too. When one is not, this is the warning you get, naming the nested component that loses its entries:


public partial class RouterTests : RaskMarkup        // ✓ — enclosing type is partial too
{
    private sealed partial class CounterPage : Component
    {
        protected override Component? Render() => Div["…"];
    }
}

This used not to matter: the HTML tags lived in Rask.Core and reached a nested component by inheritance, where nesting is irrelevant. They ship from Rask.Html now, and a referenced library's entries can only be injected — so a nested component whose enclosing chain is not partial would silently lose the chain, which is what this reports instead.

Fix: add partial. Suppress with #pragma warning disable RASK036 / .editorconfig (dotnet_diagnostic.RASK036.severity = none) if you build every component through the factory.

RASK037

using alias is hidden by a builder entry · Warning

On the builder surface every component type contributes an entry — a member named after itself, inherited by every component (Div, Card, Line). Inside a component body a member beats a using alias in simple-name lookup, so an alias that shares an entry's name quietly stops meaning what it says:


using B = Acme.Benchmarks;               // ✗ RASK037 — the <b> tag's entry wins

public sealed partial class Report : Component
{
    protected override Component? Render() =>
        Div[B.Summary.Render()];         // CS1061: 'B' does not contain a definition for 'Summary'
}

The compiler's own message is CS1061 at the use, naming a B nobody wrote and pointing nowhere near the alias. It is also unreachable by a quick-fix: by the time the error exists the alias has already lost the lookup. RASK037 reports it at the alias instead, before it is ever used.

The analyzer flags an alias only when an entry actually claims the name — either on a component declared in the same file, or (for a global using alias) on Component itself. Aliases in files that declare no component are left alone.

Fix: rename the alias to something no tag or component uses (using Bench = Acme.Benchmarks;). The two-letter tag names are the ones that bite: A, B, I, P, Td, Tr. Suppress with #pragma warning disable RASK037 / .editorconfig (dotnet_diagnostic.RASK037.severity = none) if the alias is only ever used outside a component body.

RASK038

Builder chain does not set a required property · Error

A non-nullable property with no member initializer is required — see RASK001. Most required properties are enforced by the chain's own type: they are steps the component does not exist until you take. This analyzer covers what that cannot reach — a chain the compiler cannot follow end to end (see RASK039), where the property is set by a setter somewhere along the way and leaving it out compiles cleanly, rendering with a null it was never supposed to hold.


public sealed partial class Card : Component
{
    public string Title { get; set; }          // required: non-nullable, no initializer
    public string? Note  { get; set; }         // optional
}

Card.Note("later")                             // ✗ RASK038 — 'Title' is never set
Card.Title("Q3").Note("later")                 // ✓

Order does not matter, and child indexing (Card.Title("Q3")[…]) is part of the same expression. A property whose setter drops an On prefix (OnSave.Save(…)) counts under either spelling.

Properties declared in your own compilation are read straight off the syntax, where the member initializer is right there. A property from a referenced assembly cannot be: an initializer compiles into the constructor and leaves no trace in metadata, so string Title and string Title = "" are the same symbol from outside. The owning assembly therefore publishes the answer — the factory generator emits one [assembly: RaskRequiredProperties("Rask.Bootstrap.BsIcon", "Name")] per component with such a property — and this analyzer reads it back. A library built by an older Rask, or by no Rask at all, publishes nothing, and its properties are then counted only when they carry the language's required modifier, which metadata does preserve.

Fix: add the setter to the chain, or — if the property really is optional — give it a nullable type or a member initializer, which is what marks it optional for both surfaces. Suppress with #pragma warning disable RASK038 / .editorconfig (dotnet_diagnostic.RASK038.severity = none).

RASK039

Builder chain is split across statements, so its required properties can't be checked · Warning

RASK038 is only sound while the chain is a single expression. Store it in a local or a field and the remaining setters can be applied anywhere — in a branch, a loop, another method — so claiming a property is missing would be a guess. Rask reports the gap in the analysis instead of a wrong answer:


var card = Card.Note("later");          // ✗ RASK039 — 'Title' may or may not be set below
if (highlight) card = card.Title("!");  //   …and here it depends on a runtime value
return card;

The warning only appears when something is still missing at the end of the visible chain: a stored chain that is already complete says nothing.

Fix: keep the chain in one expression, or set the required properties before storing it. Suppress with #pragma warning disable RASK039 / .editorconfig (dotnet_diagnostic.RASK039.severity = none) if you assemble components across statements by design.

RASK040

Two components share a simple name, so neither can have a builder entry · Warning

A member name has no namespace, so the two do not separate the way the types themselves do. An entry is keyed by simple name: it is a single member named after its type, and one name can only stand for one type.


namespace Features.Products { public sealed partial class Card : Component { } }   // ✗ RASK040
namespace Features.Orders   { public sealed partial class Card : Component { } }   // ✗ RASK040

Neither component gets an entry, because choosing which type Card means is the author's decision, not the generator's. Both stay reachable through their generated factories, so nothing stops compiling — you just cannot write Card bare.

Fix: rename one of them (ProductCard / OrderCard). Suppress with #pragma warning disable RASK040 / .editorconfig (dotnet_diagnostic.RASK040.severity = none) if you are happy to build both with new from inside Rask.Core.

RASK041

The builder surface's shared pending-bit budget is exhausted · Warning

This one is for people changing Rask itself, not for app code. A chain writes only the properties it names, so a builder entry marks its folding properties pending and resets whatever is still pending when the parent's Render() returns — that is what makes Div.Id("x") on one render and a bare Div on the next drop the id, exactly as the factory does. The pending bits are split so a component compiled against one Rask.Core cannot collide with a shared property added in a later one: the shared Element/Component surface owns the low 32 (BuilderRuntime.OwnPendingBit), each component's own properties get the rest.

Those 32 are handed out in ordinal name order, which is the trap: adding one folding property too many to Element does not push itself off the end — it pushes whichever alphabetically-later property was last (Title, TabIndex) onto the eager reset path, which reports that property changed on every render and defeats the render cache for it. Nothing fails to compile and no test goes red, which is why the generator counts them.

Fix: raise Rask.Core.BuilderRuntime.OwnPendingBit and the generator's mirrored OwnPendingBit constant together (they are a wire format between an app and the Rask it was built against), or make the new property non-folding. The budget was raised 16 → 32 when the global attributes landed (#693) and the shared surface reached 19 folding properties; because a component compiled against the old value numbered its own properties from 16, everything must be rebuilt against the new pair.

RASK042

Retired — delegate-typed property cannot receive a builder setter

Reported while a chain's receiver was the component itself: a delegate-typed property is invocable, so .OnClick(Save) bound to the property instead of to the same-named setter, and the setter was unreachable dead code. The fix at the time was to wrap the delegate in a carrier.

A chain now receives on Build<TComponent>, so the property is not on the receiver, the lookup that caused this cannot happen, and a callback property is an ordinary Action / Func<…>. The ID is not reused.

RASK043

A component name is used in a type that has no builder entries · Warning

The chain is reachable only from inside a type that has the entries. They are inherited members — that is the whole design, because a static-imported property loses to a same-named type (CS0119) while a member of the enclosing type wins. A component is such a type; so is anything deriving from Rask.Core.RaskMarkup, which is Component's own base and carries the framework entries and nothing else; and so is anything marked [RaskMarkup], which is the same opt-in for a type that has no base slot to spend.

In a type that is none of those, the simple name binds to the component type instead:


using Rask.Core.Components;

internal static class Parts
{
    public static Component Loading() => Div.Class("spinner")["…"];   // ✗ RASK043 — CS0119
}

using Rask.Core;

// ✓ a static class can derive from nothing, so the attribute is the way in — it stays static and the
//   framework entries are injected as its own members. Prefer `: RaskMarkup` when the base slot is
//   free; the attribute takes it for you in that case anyway, and only injects when it cannot.
[RaskMarkup]
internal static partial class Parts
{
    public static Component Loading() => Div.Class("spinner")["…"];
}

The compiler's own report is CS0119 ("'Div' is a type, which is not valid in the given context"), often with a CS0021 on the […] that would have carried the children, or a CS0120 in a static context — none of which mentions Rask or the one line that fixes it.

Fix: derive the enclosing type from Rask.Core.RaskMarkup, or mark it [RaskMarkup] when its base slot is taken or it is a static class — or, if it was really a component all along, make it one. A static class cannot derive from anything, so the attribute is the way in there; nesting it inside a host works too, since simple-name lookup walks out through enclosing types. Suppress with #pragma warning disable RASK043 / .editorconfig (dotnet_diagnostic.RASK043.severity = none).

CS0108 (a member hides a builder entry)

Not a Rask diagnostic, but a Rask quick-fix. Because every component type contributes an entry named after itself, any member that shares a tag's or a component's name now hides one, and the compiler asks for new:


public sealed partial class BsModal : Component
{
    public new Component? Footer { get; set; }        // vs the <footer> entry
    private new Component Section(string t) => …;     // vs the <section> entry
    public new sealed record Line(int X, int Y);      // vs the SVG <line> entry
}

The lightbulb inserts new where csharp_preferred_modifier_order wants it (after the accessibility, before sealed / readonly), and is offered only inside a component — hiding in your own class hierarchy is your design decision, not the framework's.

Deliberately a code fix and not a DiagnosticSuppressor. A suppressor satisfies the compiler, but dotnet format does not honour suppressors and applies the underlying fix anyway, so the format gate never settles.

The related using-alias collision cannot be fixed this way — it surfaces as a hard CS1061 after the alias has already lost the lookup, which is what RASK037 exists for.


RASK044

Builder chain sets the same property twice · Warning

A setter writes its property and hands the component back, so a chain that names one twice simply overwrites it. The last call wins, the earlier one has no effect, and the compiler is perfectly happy — which is why this needs saying out loud.


Card.Title("Coffee").Note("Dark roast").Title("Tea")   // ✗ RASK044 — renders "Tea"
Card.Title("Coffee").Note("Dark roast")                // ✓

Two writes to one property are always either a merge artefact or a copied line that was not adjusted. Nothing about the shape is legitimate: if the value really is conditional, compute it once and pass it.


Card.Title(featured ? "Coffee" : "Tea")                // ✓

Reported once per chain, naming the property, not once per extra call.

Two separate chains are not a duplicate — Div[Card.Title("a"), Card.Title("b")] is two components that each name Title once, which is ordinary markup.

Why an analyzer and not the type. The chain already makes some mistakes unwritable: a required property cannot be omitted, and Bind and Value cannot both be used, because each step returns a type that offers only what is still legal. Extending that to every setter would mean one state per subset of the surface — 2n over roughly ninety properties — where the required-property machinery pays 2k over the few that are required. So this one is reported rather than prevented.

Silence it per line with #pragma warning disable RASK044, or per project in .editorconfig (dotnet_diagnostic.RASK044.severity = none).

RASK045

Component built by a chain is assigned to afterwards · Warning

A chain states everything a component was given, in one expression, where the reader of the call site can see it. An assignment after the chain has ended is invisible from there, and nothing reconciles the two — a chain step and a later write to the same property simply disagree, and the write wins.


Card c = Card.Note("a");
c.Note = "b";                       // ✗ RASK045 — the chain says "a", the reader has to find this line

Card.Note("b")                      // ✓ one expression, one answer

Only a component a chain produced is held to this. One built any other way — new, a factory, a field the component assigned itself — is not reported: the surface it came through is what decides, and only a chain promises to be the whole story.

It has to be an analyzer rather than a property of the type. Build<T> converts implicitly to the component it built, which is what keeps the chain out of the way at every call site that wants the component itself (a property typed as a particular component, a strongly-typed children collection, a test asserting on the result). Once it has converted, the result is an ordinary component with ordinary settable properties, and nothing in the type system is left to forbid the write.

Fix: move the assignment into the chain — every property a chain can reach has a step of the same name. Suppress with #pragma warning disable RASK045 / .editorconfig (dotnet_diagnostic.RASK045.severity = none) where a component genuinely has to be completed later.


RASK046

Key must open a component's chain · Warning

A keyed child is identified by its key rather than by its position among its siblings, so that the state a row holds itself — a private field, an edit buffer, an OnMount subscription — moves with the item rather than with the slot when the list changes shape. Settling that identity means handing back the instance the key owns and discarding the one the entry just built, so any step written before Key is applied to a component that is about to be thrown away:


TodoRow.Item(item).Key(item.Id)   // ✗ RASK046 — Item is written to the instance Key then discards
TodoRow.Key(item.Id).Item(item)   // ✓ identity first, then everything else

It compiles and it renders. The value goes missing only once the list changes shape — an insert at the top, a reorder — which is the worst possible time to discover it.

Elements are exempt, and that is not a carve-out. An element is re-specified in full on every render: whatever its chain does not name, the deferred reset puts back. Its instance therefore carries nothing, it is never claimed, and its DOM identity comes from data-rask-key in the diff codec rather than from the parent's child map. So the common spelling stays exactly as it reads:


Div.Class("line").Key(index)[cells]   // ✓ an element is never claimed

Fix: move .Key(…) to the front of the chain. See composition → keys and the reconciliation note in the live-rendering codec.


RASK047

Retired. It reported a Page.Route override that was not a compile-time constant. Routes are declared with [Route("...")], whose argument is an attribute argument and therefore constant by construction, so the failure it guarded can no longer be written. The id is retired, not reused.

RASK032, RASK048, RASK049, RASK050

Retired. All four guarded the native hosting model: native chrome inside an HTML tree (RASK032), HTML inside a native screen (RASK048), a NativeWebView that set a Url and took children (RASK049), and a native head named on a web-only host (RASK050). Rask is a web framework — Rask.Native and every type those rules mentioned are gone, so none of the mistakes they caught can be written any more. The ids are retired, not reused.

RASK051

Translation catalog is malformed · Error

A translation catalog is a JSON object whose values are text or further objects, named Resources/{Family}.{culture}.json. This fires when one cannot be read, or when it describes strings that would fail at runtime.

// Resources/Strings.en.json
{
  "Greeting": "Hello, {name}!",
  "Home": { "Title": "Dashboard" }
}

The reported cause names the file and the problem:

Cause Why it is an error
a JSON syntax error, a duplicate key, a value that is not text or an object nothing can be generated
a key that is not a usable C# identifier the member it would generate cannot be written
an unclosed {, a stray }, a mix of {0} and {name} the message cannot be turned into a format string
a translation whose placeholder set differs from the neutral catalog's string.Format throws FormatException the first time that string renders — in that one language only
no catalog for the neutral language nothing defines which keys exist

The placeholder rule is about the set, not the order: other languages reorder arguments, and naming placeholders is what makes that safe.

// Resources/Strings.hu.json — fine, the same names in a different order
{ "M": "{b} majd {a}" }

// ✗ RASK051 — {nev} is not {name}, so this would throw when a Hungarian visitor sees it
{ "Greeting": "Szia, {nev}!" }

Plural sets

A key whose text depends on a count is written as an object carrying $plural:

{ "Cart": { "$plural": "count", "one": "{count} item", "other": "{count} items" } }

RASK051 also fires when such a set cannot produce correct grammar:

Cause Why
Rask does not carry that language's plural rules applying English rules would produce text that reads as broken to a native speaker, and nothing at runtime would say so
the language's residual form is missing it is the arm every unmatched count lands on
a form the language never selects (few in English) that text could never be shown
a form that is not a CLDR category at all it is a typo
the key is a plural set in one language and a single string in another they generate different members

The residual is not always other. Polish integers never select other — CLDR routes the residual to many — so a Polish catalog supplies one/few/many and requiring other there would mean writing text no visitor could ever see.

// Resources/Strings.pl.json — complete, and correctly has no "other"
{ "Cart": { "$plural": "n", "one": "{n} plik", "few": "{n} pliki", "many": "{n} plików" } }

Fix: correct the file the message names. A JSON file in Resources/ that is not a catalog needs no action — one without a culture tag in its name is ignored.

RASK052

Translation catalog disagrees with the neutral catalog · Warning

The neutral catalog defines which keys exist; a translation supplies their text. This fires when a translation is missing a key, or carries one the neutral catalog does not define.

// Resources/Strings.en.json
{ "Save": "Save", "Cancel": "Cancel" }

// Resources/Strings.hu.json
{ "Save": "Mentés" }        // ⚠ RASK052 — no translation for 'Cancel'

A missing translation is a warning, not an error, because a partly translated app is the normal state of every real project: the neutral text is used until it is filled in, so the page works. The opposite case — a key only a translation has — is also a warning: it generates nothing and is almost always a rename that was applied to one file.

A plural set is checked the same way: a translation missing a category its own language distinguishes is reported, and the residual form carries the page until it is filled in.

// Resources/Strings.ru.json — ⚠ RASK052, Russian also distinguishes "few"
{ "Cart": { "$plural": "n", "one": "{n} файл", "many": "{n} файлов" } }

Fix: add the key, or delete it. To gate a release on complete translations, promote it:

# .editorconfig
dotnet_diagnostic.RASK052.severity = error

Or silence it while translation is in progress with = none.

RASK053

Remote message has no wire encoding · Error

A message reaches a handler in another process by being encoded, and Rask generates that encoder at compile time rather than discovering it by reflection — which is what lets a remote dispatch publish clean under the WASM/AOT trimmer. The cost of that choice is that the set of shapes a message may take is fixed, and a shape outside it has to be reported now rather than failing on the wire.


// ✗ RASK053 — an interface names no single concrete type, so the receiver cannot know what to build
public sealed record Search(IFilter Filter) : IQuery<Hit[]>;

// ✓ a concrete type has one shape, so both sides agree on it
public sealed record Search(TextFilter Filter) : IQuery<Hit[]>;

Supported shapes. bool, the numeric types, char, string, Guid, DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, Uri, enums (encoded as their number, so renaming a member does not break the wire), byte[] (base64), Nullable<T> of any of those, arrays and List/IReadOnlyList/IEnumerable/ICollection/IList of them, Dictionary keyed by string, and records or classes composed of the same. A composite needs either a public constructor whose parameters all match properties — which every positional record has — or a public parameterless constructor with settable properties.

Not supported, and why. An interface or abstract type names no single thing to construct. object has no shape at all. An open generic has no single wire shape. A type that refers back to itself has no finite encoding. A dictionary keyed by anything but string has no key encoding, because a JSON object's keys are strings. A RaskFile is allowed only as a direct property of the message: a file is addressed by its index in the multipart body, written where the property sits in the JSON, so one nested inside a list or a sub-object would have nowhere to be addressed from and its bytes would be dropped.

Fix: give the property a concrete, supported type — or, if the message is never sent anywhere, mark it [LocalOnly]:


// A job payload, an outbox event, a command only another handler publishes: never on the wire, so
// never encoded, so free to carry whatever its handler finds convenient.
[LocalOnly]
public sealed record RebuildIndex(IComparer<string> Order) : ICommand;

[LocalOnly] on an interface marks every message implementing it, which is how Rask.Jobs' IBackgroundJob and Rask.Outbox' IOutboxEvent keep whole families of in-process messages out of the wire vocabulary at once.

This diagnostic only fires in a project that references a remote transport (Rask.Cqrs.Client or Rask.Cqrs.Server). An app using Rask.Cqrs purely in-process generates no codecs, so none of these constraints apply to its messages.

RASK054

Page cannot run in the browser · Info

A routed page injects something that only exists in the server process, so it stays server-live and will not move into WebAssembly.

This is not a fault. The page is correct, and for most apps this describes every data page. It is Info rather than a warning for exactly that reason — a warning on each of them would be noise nobody reads. The diagnostic exists so that "why did this page not move?" has an answer at the call site rather than only in a runtime log.


using Microsoft.EntityFrameworkCore;

[Route("/orders")]
// ℹ RASK054 — a DbContext cannot exist in a browser, so this page stays server-live
public sealed partial class Orders(IDbContextFactory<AppDb> db) : Component
{
    protected override Component? Render() => /* … */;
}

To make the page eligible, reach the same data through something that already crosses the wire — a Rask.Query query or a CQRS message, both of which are dispatched remotely by default:


[Route("/orders")]
public sealed partial class Orders(IQueryClient client) : Component   // no diagnostic
{
    private readonly Query<Order[]> _orders = client.Query(new GetOrders());

    protected override Component? Render() => /* … */;
}

What it looks at. Constructor parameters of a component carrying [Route]. It does not report a shared component that injects a server-only type — that is a property of whichever page uses it, and pointing at the component would name a file whose author cannot see which page is affected.

The list of server-only types is short and deliberately not exhaustive: Entity Framework's DbContext and IDbContextFactory<T>, and anything from the Rask.Server assembly. It names what this framework hands people rather than everything that could fail in a browser, because the analyzer compiles against the server half and has no view of what a browser build references.


RASK055

Scoped JavaScript is no longer supported · Error

A .js file sits beside a component of the same name. Scoped component assets are TypeScript, and Rask neither compiles nor registers a .js sibling.

Features/Counter.cs
Features/Counter.js     ✗ RASK055
Features/Counter.ts     ✓

Fix: rename the file. TypeScript is a superset of JavaScript, so an existing ES module is already valid TypeScript — the body needs no change, and tsgo compiles it before the browser sees it. Add type annotations at whatever pace suits you.

Why this is an error and not a quiet skip. A scoped script that stops being registered does not fail. window.Rask["Counter"] simply has no methods on it, so every call from C# resolves to nothing and the component renders a control that does nothing — with no error at build time, none at startup, and none in the console. There is no useful place for that to surface later, so it surfaces here.

It fires only for a real scoped asset. The rule is "a .js beside a non-abstract, non-generic Component subclass of that name" — exactly the set of files that worked as scoped JavaScript before. A Helpers.js next to an ordinary static Helpers.cs, or any vendored script, is somebody else's file and is left alone.

Files under wwwroot/, Resources/ and Browser/ are outside the scoped-asset convention entirely and are never considered. A plain site-wide script belongs in wwwroot and is linked from your Head, exactly as before.

RASK056

External component must be partial · Error

A ReactComponent or LitComponent is completed by a second part of the class: its name, its module and its props writer. Without partial there is nowhere to put any of it.


// ✗ RASK056 — nothing can be generated into it
public sealed class Chart : ReactComponent { }

// ✓
public sealed partial class Chart : ReactComponent { }

Reported here, against the declaration, rather than left to the compiler: what it would otherwise produce is three "does not implement inherited abstract member" errors naming ComponentName, Module and WriteProps, none of which the author wrote or should have to know about.

RASK057

External component prop has no wire encoding · Error

Props are serialized to JSON by generated code rather than by reflection — which is what lets them survive trimming and AOT — so a shape the generator cannot express has to be reported now rather than arriving as null in the browser.

The supported set is the same wire vocabulary RASK053 documents, plus callbacks as Action, Action<T>, Func<Task> and Func<T, Task>.


// ✗ RASK057 — an interface names no single concrete type
public IFilter? Filter { get; set; }

// ✓ a concrete type has one shape
public TextFilter? Filter { get; set; }

A property that is not meant to reach the browser at all — an injected service, a computed helper — should say so with [SkipFactory], which keeps it out of the props entirely.

RASK058

External component name collision · Error

The client runtime resolves a module by the component's simple type name, so two sharing one name would resolve to whichever registered last — silently, and potentially differently between builds.


// ✗ RASK058 — both are "Chart" to the browser
namespace Sales    { public sealed partial class Chart : ReactComponent { } }
namespace Support  { public sealed partial class Chart : ReactComponent { } }

Rename one, or give it an explicit module by overriding Module.

RASK059

Module override must be a constant string · Error

The bundler needs the module specifier at build time, to generate the entry that pairs the component with its adapter — long before any of this code runs. So the override has to be a literal the generator can read straight out of the syntax.


// ✗ RASK059 — the build cannot evaluate this
protected override string Module => $"./widgets/{Name}.ts";

// ✓
protected override string Module => "@acme/charts/Chart";

Anything computed would leave the browser resolving a name the bundle never built — markup pointing at a chunk that does not exist, with nothing failing until someone loads the page.

RASK060

AddRask is called twice on the same service collection · Warning

A second AddRask does not add to the first. Its options are registered with TryAddSingleton, which keeps the registration already there — so everything the later call configures is discarded, while the call itself compiles and reads exactly as though it worked.

The visible casualty is configureCulture. The second call builds a fresh RaskCultureOptions, runs your callback over it, and then loses the registration race, so an app that named its languages ships with none. It is worse than a plain no-op: AddRaskCulture still flips the process-wide RaskCulture.IsEnabled, so culture negotiation turns on over an empty catalog.


// ✗ builder.Services.AddRask();
//   builder.Services.AddRask(configureCulture: c => c.SupportedCultures.Add("hu"));   // silently dropped

// ✓ builder.Services.AddRask(configureCulture: c =>
//   {
//       c.SupportedCultures.Add("en");   // the first entry is the default
//       c.SupportedCultures.Add("hu");
//   });

Fix: pass every option to a single AddRask call. The warning fires only for two calls in the same method body on the same receiver as written, so a test file that builds one ServiceCollection per case — or a method configuring two collections side by side — is left alone. See configuration and localization.

RASK061

Blazor island must be partial · Error

A BlazorComponent<T> is completed by a second part of the class — the chain steps taken from the hosted component's [Parameter]s, and the reflection-free writer that maps them onto it. Without partial there is nowhere to put any of it.


// ✗ RASK061 — nothing can be generated into it
public sealed class Chart : BlazorComponent<MudChart> { }

// ✓
public sealed partial class Chart : BlazorComponent<MudChart> { }

Reported against the declaration rather than left to the compiler, which would otherwise report a missing WriteParameters the author never wrote and should not have to know about.

RASK062

An island takes no children · Error

An island renders markup a foreign renderer owns — a hosted Blazor component, a React tree, a Lit element — and nothing else. It is a leaf.


// ✗ RASK062 — the children would compile and never render
Chart.Series(_series)[ H2["Revenue"] ]

// ✓ compose the other way round
Div.Class("rounded-xl border p-4")[ H2["Revenue"], Chart.Series(_series) ]

Rask children would have to cross the diff boundary, and no crossing is right for every component. A hosted Blazor component may have no RenderFragment parameter at all, one under a name only it knows (Content, Body), or several (HeaderContent, RowTemplate); a .tsx or Lit component takes the nodes and then owns them, so once its framework has moved them every DOM path Rask holds is wrong and the content goes dead after its first paint.

This is an error rather than a convention because it cannot be one. The children indexer lives on Component and Build<T>, so it is available on every chain and cannot be withheld from a single type — without the diagnostic the children bind, compile, and silently render nothing.

Applies to both island families: BlazorComponent<T> (see Blazor components) and ReactComponent/LitComponent (see islands).

RASK064

Blazor island name collision · Error

An island's simple name identifies it in the rendered markup (<rask-blazor name="Chart">), so two islands sharing one is ambiguous in the page and in anything reading it.


// ✗ RASK064 — both are "Chart"
namespace Dashboard { public sealed partial class Chart : BlazorComponent<MudChart> { } }
namespace Reports   { public sealed partial class Chart : BlazorComponent<MudChart> { } }

Fix: rename one. The namespace does not disambiguate, because the name is written into HTML.

RASK066

Hosted Blazor component's parameters cannot be verified · Warning

The island's chain steps are read from the hosted component's [Parameter] properties. That works for a type from a referenced project or package, but not for a .razor in the same project: that class is produced by the Razor source generator, and one source generator never sees another's output — so Rask's generator cannot resolve its parameters and emits no chain steps for them.

The island still renders. What is lost is compile-time checking of what you pass it.


// ⚠ RASK066 — Widget.razor is in this project
public sealed partial class WidgetIsland : BlazorComponent<Widget> { }

Fix: move the .razor into a Razor Class Library and reference it. That is also where a Blazor component belongs if anything else will ever use it — and it is why hosting MudBlazor or Radzen, which are referenced packages, is fully checked with no warning at all.


RASK067

Endpoint shape has no wire encoding · Error

A parameter or response type of an API endpoint cannot be encoded on the wire, so no typed client method can call it. The message names the shape and says what is wrong with it — the same walk, and the same vocabulary, as RASK053 for a CQRS message.


[HttpPost("")]
public ActionResult<Post> Create([FromBody] Stream body) => ...;   // ✗ RASK067

Fix: take a record, a class or a collection of them. A type Rask can encode is one whose public properties are themselves encodable — the shapes a JSON body can carry. If the parameter is really a service rather than something the caller sends, mark it [FromServices], and it leaves the client's signature instead of being reported.


RASK068

Endpoint has no generated client method · Warning

The endpoint works over plain HTTP; it just gets no typed caller, because something about it cannot be expressed as a method signature. The message says which:

  • its route has a catch-all segment ({*rest}), which no single parameter can fill;
  • its route names a token no parameter supplies, so the client would build a URL with a hole in it;
  • it would take a second request body, and a request has one.

[HttpGet("files/{*path}")]                    // ⚠ RASK068 — catch-all
public ActionResult<string> Read(string path) => ...;

[HttpGet("{id:int}")]                         // ⚠ RASK068 — nothing supplies {id}
public ActionResult<Post> Get() => ...;

A warning rather than an error, because one unusual endpoint should not break a build. It is reported rather than skipped in silence for the opposite reason: a method simply missing from the client reads as a broken generator, and the author has no way to find out why.

Fix: give the route a normal parameter segment, add the parameter it names, or accept that this endpoint is called by hand.


RASK069

Two endpoints claim one client method · Error

Two actions on the same controller generate the same client method name — an overload the generator cannot distinguish, and a CS0111 inside generated code if it were emitted.


[HttpGet("{id:int}")] public ActionResult<Post> Get(int id) => ...;
[HttpGet("by-slug/{slug}")] public ActionResult<Post> Get(string slug) => ...;   // ✗ RASK069

Fix: rename one of the actions. The client method takes the action's own name, so naming them for what they do — Get and GetBySlug — fixes the client and reads better on the server too.


RASK070

Endpoint's response type is not statically known · Warning

The action returns IActionResult, ActionResult or IResult, so what it answers with is decided at run time and there is no type for a client method to return.


[HttpGet("{id:int}")]
public IActionResult Get(int id) => Ok(new Post(id));   // ⚠ RASK070

Fix: return the type, and let ASP.NET wrap it — ActionResult<Post> still lets you return NotFound(). Where the return type genuinely has to stay open, declare what the success case sends and the client is generated from that:


[HttpGet("{id:int}")]
[ProducesResponseType(typeof(Post), 200)]
public IActionResult Get(int id) => ...;               // typed client method

An action that answers nothing does not need either: return Task or void and the client method returns a bare Task.


RASK071

ASP.NET route attribute on a Rask component · Error

Rask's route attribute and ASP.NET's share the short name Route and differ only by namespace. In a server project that already has using Microsoft.AspNetCore.Mvc; — or in a file written by someone arriving from Blazor, where the attribute is Microsoft.AspNetCore.Components.RouteAttribute — the wrong one is one completion away, and nothing downstream notices:

  • MVC reads its attribute only while building the controller application model, and a Component is never scanned.
  • Blazor's is read by a renderer this framework does not run.
  • Rask's RoutesGenerator matches on the full name, so it sees nothing to register.

The build is green and the page is simply absent from the route table. The first sign of it is a 404 in a browser, which is why this is an Error rather than a warning you could scroll past.


using Rask.Core;
using Microsoft.AspNetCore.Mvc;

// ✗ RASK071 — binds to MVC's attribute; this page is never registered
[Route("/orders")]
public sealed partial class Orders : Component
{
    protected override Component? Render() => Div["orders"];
}

Fix: apply Rask.Core.Routing.RouteAttribute instead (quick-fix available — "Use Rask's [Route]").

The quick-fix rewrites only the attribute's name. It adds no using and removes none, and it writes the name qualified wherever the short form would bind back to ASP.NET's attribute or be ambiguous — so on the file above, with using Microsoft.AspNetCore.Mvc; still present, the lightbulb produces:


[Rask.Core.Routing.Route("/orders")]       // qualified: a bare `Route` would still be MVC's

Tidying the imports by hand gives the shorter spelling:


using Rask.Core;
using Rask.Core.Routing;                   // MVC's import dropped

[Route("/orders")]                         // Rask's — the page registers
public sealed partial class Orders : Component
{
    protected override Component? Render() => Div["orders"];
}

The lightbulb is withheld where carrying the arguments over would not compile: MVC's attribute also has settable Name and Order, which Rask's does not, and an alias may bake its template in and take no arguments at all. Rewriting those would answer RASK071 with a CS0117 or CS7036, so the attribute is left for you to move over deliberately.

A page carrying both attributes is left alone. It registers correctly through Rask's, so the ASP.NET one is inert rather than harmful, and failing a build that is producing the right route table would be the worse outcome.

This does not fire on ordinary classes. A Rask server project is an ASP.NET project and may hold genuine controllers; [Route] on one of those is correct and is never reported.


RASKVAL001

Two validators for the same model · Error

A form asks for the validator of its model type and gets exactly one, so two AbstractValidator<T> for the same T in one compilation is ambiguous. Which one ran would depend on compilation order, and the rules in the other would silently never run — which reads as a validator that does not work rather than one that was never reached.


public sealed class OrderValidator     : AbstractValidator<Order> { }   // ✗ RASKVAL001
public sealed class OrderRulesValidator : AbstractValidator<Order> { }

Fix: combine the rules into one AbstractValidator<Order>. To switch between sets of rules, use FluentValidation's own rule sets inside that single validator rather than two validator classes.

A private or protected validator is never discovered in the first place, so it does not collide — declaring it that way is itself the statement that nothing outside constructs it, and it has to be registered by hand with RaskValidators.Register.


RASKVAL002

Validator cannot be constructed automatically · Warning

Registration builds the validator for you: a parameterless constructor is called directly, and one taking parameters has each resolved from the render scope. Several public constructors leave no way to choose, so nothing is generated.


public sealed class OrderValidator : AbstractValidator<Order>   // ⚠ RASKVAL002
{
    public OrderValidator() { }
    public OrderValidator(IProductRepo repo) { }
}

Fix: leave one public constructor, or register it yourself:


RaskValidators.Register(typeof(Order), sp => new OrderValidator(Pick(sp)));

This is a warning rather than an error because the validator is still usable by hand — but until it is registered its rules never run, and a validator that silently does nothing is the failure worth naming.