-
Notifications
You must be signed in to change notification settings - Fork 1
SupportedFeatures
This document provides a comprehensive list of C# features, .NET APIs, and patterns supported by the eQuantic.UI compiler.
Legend
✅ Full Support: Transpiles to equivalent JavaScript behavior.
⚠️ Partial Support: Works with caveats or minor differences.🚧 Planned: On the roadmap.
❌ Not Supported: fundamentally incompatible logic (e.g., blocking I/O, unsafe pointers).
| Feature | Status | Notes |
|---|---|---|
| Classes & Structs | ✅ | Transpiled to ES6 Classes. |
| Interfaces | ✅ | Used for TypeScript type checking (erased at runtime). |
| Enums | ✅ | Transpiled to JS Objects (bidirectional mapping). |
| Generics | ✅ | Fully supported (erased at runtime). |
| Extension Methods | ✅ | Resolved via Semantic Model and transpiled to direct calls. |
| Async / Await | ✅ | Maps to async / await and Promise. |
| Lambda Expressions | ✅ | Maps to Arrow Functions () => {}. |
| Pattern Matching | ✅ |
is patterns, Property Patterns, Recursive Patterns. |
| String Interpolation | ✅ | Maps to Template Literals `${var}`. |
| Null-Coalescing | ✅ |
?? and ??= mapped to JS equivalents. |
| Object Initializers | ✅ |
new Obj { Prop = 1 }. |
| Collection Initializers | ✅ |
new List<int> { 1, 2 } maps to [1, 2]. |
| Deconstruction | ✅ |
var (a, b) = tuple maps to [a, b] = tuple. |
| Local Functions | ✅ | Transpiled to inner functions. |
Records (with) |
✅ | Maps to object spread { ...src, prop: val }. |
typeof |
✅ | Maps to type name string literal. |
base calls |
✅ | Maps to super keyword. |
cast & as |
✅ | Maps to JS passthrough/truncation. |
sizeof |
✅ | Maps to C# primitive sizes. |
| Anonymous Methods | ✅ |
delegate(...) { ... } maps to arrow functions. |
stackalloc |
✅ | Maps to Typed Arrays (e.g., Int32Array). |
yield return |
✅ | Maps to JS Generator Functions (function*). |
lock |
Transpiled to a no-op block (JS is single-threaded). |
The compiler includes specialized strategies for nearly all LINQ methods.
| Logic | Methods | Status |
|---|---|---|
| Filtering |
Where, OfType
|
✅ |
| Projection |
Select, SelectMany, Cast
|
✅ |
| Partitioning |
Skip, Take, SkipWhile, TakeWhile
|
✅ |
| Ordering |
OrderBy, OrderByDescending, ThenBy, Reverse
|
✅ |
| Aggregation |
Count, Sum, Min, Max, Average, Aggregate
|
✅ |
| Quantifiers |
Any, All, Contains
|
✅ |
| Sets |
Distinct, DistinctBy, Union, Intersect, Except, Concat
|
✅ |
| Elements |
First, FirstOrDefault, Single, Last, ElementAt
|
✅ |
| Utility |
SequenceEqual, DefaultIfEmpty
|
✅ |
| Conversion |
ToList, ToArray, ToDictionary, ToHashSet
|
✅ |
| Grouping/Join |
GroupBy, Join, Zip
|
✅ |
We map common .NET types to their JavaScript equivalents.
| .NET Type | JavaScript Equivalent |
|---|---|
string |
String |
int, double, float
|
Number |
bool |
Boolean |
object |
Object |
dynamic |
any |
- ✅
Join,Format - ✅
IsNullOrEmpty,IsNullOrWhiteSpace - ✅
Split,Replace,Substring,Trim - ✅
ToLower,ToUpper,StartsWith,EndsWith
The temporal types are backed by tick-precise compat types (100-ns ticks, proleptic Gregorian
calendar — not the lossy new Date() / numeric-milliseconds mapping). Ctors, components, Add*,
arithmetic (-→TimeSpan), comparisons, and invariant .ToString() all match .NET; values cross the
SSR wire as ISO-8601 / "c" strings and are hydrated back into the compat type.
See the .NET BCL Coverage & Conformance table below for the per-type detail.
- ✅
List<T>→ Javascript Array[] - ✅
Dictionary<TKey, TValue>→ Javascript Object{}orMap(depending on usage). - ✅
HashSet<T>→ JavascriptSet. - ✅
Queue<T>,Stack<T>→ Javascript Array methods (push/shift/pop).
- ✅
Task,Task<T>→Promise. - ✅
Task.Delay→setTimeoutwrapper. - ✅
Task.WhenAll,Task.WhenAny. ⚠️ Task.Runexecutes on the main thread (microtask), NOT a background thread.
- ✅
Console.WriteLine→console.log. - ✅
Math.*(Min, Max, Abs, Round, etc.) →Math.*. - ✅
Guid(NewGuid,Empty,Parse) →crypto.randomUUID(). - ✅
Regex→ JavaScriptRegExp.
eQuantic.UI.Lucide / Heroicons / RadixIcons / TablerIcons / Phosphor / SimpleIcons / BootstrapIcons / Iconoir / ...
Purpose: Comprehensive Icon Sets. Contains:
- SVG resolution logic
- Specialized icon components
-
IIconProviderimplementation
| Package | Purpose | Status |
|---|---|---|
| eQuantic.UI.Charts (Apex/ChartJS) | High-performance visualization. | ✅ |
| eQuantic.UI.Lottie | High-performance animations. | ✅ |
| eQuantic.UI.Image | Optimized (Lazy/Blur/Priority). | ✅ |
| eQuantic.UI.Tailwind | Standard styling integration. | ✅ |
| Pattern | Interface | Description | Status |
|---|---|---|---|
| Metadata Management | IHandleMetadata |
SEO tags and head elements. | ✅ |
| Asset Management | IRequireAssets |
Dynamic script/style injection. | ✅ |
| Server Actions | [ServerAction] |
Secure RPC from Client to Server. | ✅ |
| Compound Components | N/A | Semantic sub-component patterns. | ✅ |
-
Blocking Code:
- ❌
.Wait(),.Resulton Tasks are NOT supported. You must useawait. Blocking the main thread freezes the browser UI.
- ❌
-
Reflection:
- ❌
System.Reflectionis largely unsupported. - ✅
typeof(T).Nameandnameof(...)are supported constants.
- ❌
-
File System:
- ❌
System.IO(File,Directory) is not strictly forbidden but will fail at runtime in the browser. - Use Server Actions to handle file operations.
- ❌
-
Numbers:
- ✅
int/double/floatmap to JS numbers. Integer division truncates (Math.trunc) andMath.Rounduses banker's rounding (MidpointRounding.ToEven) via theroundcompat helper. - ✅
decimalis exact end-to-end — compiled to the runtimeDecimalcompat type (BigInt mantissa + scale), so0.1m + 0.2m == 0.3mistrue. Decimals also cross the wire as JSON strings (EqJson) and are hydrated back intoDecimalon the client (the field'sDecimaldefault drives a type-preserving coercion — seehydrate-value.ts), so server-provided decimals keep all 28 digits instead of rounding through a double. - ✅
long/ulongare now exact — compiled to JS BigInt via thelongcompat helper.9007199254740993L + 1Lis9007199254740994(a plain JS number would round to…992). Literals become BigInt (5L→5n); arithmetic/comparison operands are wrapped inlong()(which coercesnumber/string→bigint) so mixed expressions never throw. On the wire, 64-bit ints cross as JSON strings (Server Actions + SSR state, viaEqJson) so values beyond 2^53 survive the round trip. Other numeric type suffixes (1.5f,100u) are stripped.
- ✅
-
Thread Safety:
- Since JS is single-threaded,
lockstatements are compiled away (ignored). -
Thread.Sleepis not supported (useTask.Delay).
- Since JS is single-threaded,
Transpilation fidelity is enforced by a conformance harness (tests/eQuantic.UI.Conformance.Tests):
each case runs the same C# expression two ways — transpiled to JS (executed via the embedded Bun) and
evaluated directly in .NET (Roslyn scripting) — and asserts identical results. 390+ cases are green.
(Most recent: OrderBy/ThenBy composite stable sort.) This covers both expressions and statement blocks (control flow: if/for/foreach/while/switch/ try-catch-finally/local functions — the block runs in an IIFE and its returned value is compared).
Every construct resolves via one of three mechanisms (see docs/DOTNET-COVERAGE-PROGRAM.md):
-
Native strategy — idiomatic JS when the runtime has an equivalent.
-
.NET-compat runtime helper — faithful .NET semantics where JS has none. The transpiler emits these under a single namespace
$eq(organised by domain), brought in with one import per module —import { $eq } from "@equantic/runtime"(resolved by the page's import map) — instead of N loose helper imports, and$eq.*can never collide with a user identifier in the generated scope:-
$eq.num—dec(exact Decimal),long(Int64 via BigInt) -
$eq.math—round(banker's rounding) -
$eq.text—format(number/string formatting),stringBuilder -
$eq.time—dateTime,timeSpan,dateOnly,timeOnly,dateTimeOffset -
$eq.enums—parse(enum member-name) -
$eq.collections—queue(FIFO),stack(LIFO) -
$eq.nullable—arith,cmp(liftedNullable<T>operators: null-propagating arithmetic, false-on-null relational) -
$eq.equals— structural (value) equality for records/structs/tuples (backs==,.Equals,Contains,Distinct) -
$eq.css—styleBuilder,classBuilder,joinClasses,whenClass(the styling subsystem)
-
-
Fail-on-unsupported — a construct with no possible JS representation now raises a build error (with a stable
EQcode) instead of being silently emitted verbatim. Two layers:-
UnsupportedConstructStrategy(EQ2001) — typed-reference intrinsics (__makeref,__refvalue,__reftype), pointer types, function pointers. -
SemanticValidatorclient/server boundary (EQ21xx) — calls intoSystem.IO,System.Net.Http,System.Net.Sockets, EF Core /System.Data, OS threading (Thread/Monitor/Mutex),Process,InteropServices(P/Invoke),Reflection.Emitfrom a client component. (System.Threading.Tasksis not forbidden — async maps to Promise.) The fix is to move the call into a[ServerAction].
Any other construct that hits no strategy is emitted verbatim but now reported as a warning (
EQ1001/EQ1002) so it is visible rather than silent. Diagnostics print in MSBuild-canonical form, so errors faildotnet build. -
| Area | Status | Notes |
|---|---|---|
| Arithmetic / bitwise / comparison | ✅ | integer division truncates; %, shifts, `& |
Math.* |
✅ |
Truncate→trunc, Ceiling→ceil, Round banker's via round helper |
decimal |
✅ | exact via Decimal (literals + + - * / == != < > <= >=) |
| Numeric constants | ✅ |
int.MaxValue, double.Epsilon, … → literals |
Parsing / Convert.*
|
✅ |
int/double.Parse, bool.Parse, Convert.ToInt32/ToDouble/ToString/ToBoolean/…
|
| Strings | ✅ | Substring/IndexOf/Replace/Split/Pad/Trim(char)/Concat/Format/Join/IsNullOrEmpty/IsNullOrWhiteSpace; StringComparison-aware Equals/StartsWith/EndsWith/Contains/IndexOf (Ordinal + IgnoreCase). Culture-sensitive ordering (CompareTo) is out of scope. |
char.* |
✅ |
ToUpper/ToLower/IsDigit/IsLetter/IsWhiteSpace/… (Unicode-aware) |
StringBuilder |
✅ | compat type — Append(incl. bool→"True"/"False")/AppendLine("\n")/Insert/Remove/Replace/Clear/Length/ToString
|
| LINQ | ✅ | Where/Select/SelectMany/Where-Select(indexed)/OrderBy/Distinct(By)/GroupBy/ToDictionary/ToLookup/Zip/Chunk/MinBy/MaxBy/Take(While)/Skip(While)/Aggregate/Sum/Min/Max/Average/Count/Any/All/First/Last/Concat/Reverse/Join/GroupJoin/ThenBy/ThenByDescending (Join/GroupJoin = order-preserving hash join over primitive keys; OrderBy+ThenBy = single stable composite sort, source copied) |
| Collections | ✅ | List, Dictionary, HashSet (incl. initializers and .Count); Queue/Stack compat (Enqueue/Dequeue/Push/Pop/Peek/Count/Contains/ToArray) |
enum |
✅ | member-name string (equality/switch/ternary) |
long/ulong
|
✅ | exact via BigInt (long helper); literals 5L→5n, wire as JSON string |
DateTime |
✅ | tick-precise DateTime compat type — ctors, components, Add*, -→TimeSpan, comparisons, .ToString()/format; ISO-8601 wire + hydration |
TimeSpan |
✅ | tick-precise TimeSpan compat type — From*, ctors, components/totals, + -, comparisons, .NET "c" .ToString(); "c" wire + hydration |
DateOnly / TimeOnly
|
✅ | compat types (.NET 6+) — ctors, components, Add*(+ TimeOnly wrap), comparisons, invariant .ToString() (MM/dd/yyyy / HH:mm); ISO wire + hydration |
DateTimeOffset |
✅ | tick-precise compat type (wall-clock + offset, compared by the instant) — ctors, components, Offset/UtcDateTime/LocalDateTime, ToOffset, Add*, From/ToUnixTime*, - →TimeSpan, instant comparisons, invariant .ToString() (MM/dd/yyyy HH:mm:ss zzz); ISO+offset wire + hydration |
record / struct / value tuple |
✅ |
Value semantics. Records/structs are plain objects (positional new Point(1,2) → {x,y}, object initializers merge), tuples are arrays with element access by position (t.Item1) and by declared name ((int X, int Y).X) → index. ==/!=, .Equals, Contains, Distinct compare structurally via $eq.equals; with copies-and-replaces. Deconstruction var (a, b) = … works for tuples (array destructuring, discard holes) and records (object destructuring by Deconstruct order). Follow-ups: user-defined instance methods on records/structs (need named-class emission), record-keyed dictionaries. |
Nullable<T> (T?) |
✅ |
HasValue/Value, GetValueOrDefault() (type-aware default: 0/false/$eq.num.dec(0)/enum zero-member/…) and GetValueOrDefault(fallback), ??; lifted operators via $eq.nullable.* — arithmetic propagates null, relational (< > <= >=) is false when either side is null (not a numeric coercion). No-arg GetValueOrDefault() on DateTime?/Guid?/struct yields null — use the fallback form there. |
Guid |
✅ |
Guid.NewGuid()→crypto.randomUUID(), Guid.Empty, Guid.Parse; string wire |