-
Notifications
You must be signed in to change notification settings - Fork 1
ExternalTypeResolution
The eQuantic.UI compiler resolves types defined in external files within the same project. Components reference models, DTOs, and other classes without requiring them to be in the same file — the compiler works against the full project Roslyn Compilation, which includes all source files and references.
// Models/User.cs
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
// Pages/UserProfile.cs
[Page("/profile")]
public class UserProfile : StatefulComponent
{
private User _currentUser; // ✅ Type 'User' fully resolved
protected override HtmlNode Render()
{
return Text(_currentUser.Name); // ✅ Converts to: this._currentUser.name
}
}A full semantic model matters because member-access conversion depends on knowing the receiver's type: with it, _currentUser.Name lowers to this._currentUser.name; without it, the compiler cannot distinguish a property from a local or pick the right method mapping.
using eQuantic.UI.Compiler;
using eQuantic.UI.Compiler.Services;
// Get the full project compilation
var compilation = await ProjectCompilationHelper
.GetProjectCompilationAsync("path/to/MyApp.csproj");
// Create compiler and set project compilation
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);
// Now compile components - external types will be resolved
var results = compiler.CompileFile("Pages/UserProfile.cs");Useful in MSBuild tasks where MSBuildWorkspace might not be available:
using eQuantic.UI.Compiler.Services;
// Get all .cs files in the project
var sourceFiles = ProjectCompilationHelper
.GetProjectSourceFiles("path/to/MyApp");
// Get assembly references
var assemblyPaths = new[]
{
"path/to/eQuantic.UI.Core.dll",
"path/to/other-dependencies.dll"
};
// Create compilation from sources
var compilation = ProjectCompilationHelper.CreateCompilationFromSources(
sourceFiles,
assemblyPaths,
"MyApp");
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);Revert to minimal compilation mode (isolated files):
compiler.ClearProjectCompilation();┌─────────────────────────────────────────┐
│ MSBuild Project Compilation │
│ - All .cs files in project │
│ - All referenced assemblies │
│ - Full type information │
└────────────┬────────────────────────────┘
│
↓ SetProjectCompilation()
┌────────────────────────────────────────┐
│ SemanticModelProvider │
│ - Stores project compilation │
│ - Returns SemanticModel for each file │
└────────────┬───────────────────────────┘
│
↓ GetSemanticModel(tree)
┌────────────────────────────────────────┐
│ CSharpToJsConverter │
│ - Uses SemanticModel for type info │
│ - Converts _currentUser.Name → name │
└────────────────────────────────────────┘
-
SemanticModelProvider(SemanticModelProvider.cs)-
SetProjectCompilation(Compilation)- Set full project compilation -
GetSemanticModel(SyntaxTree)- Returns semantic model with full type info
-
-
ProjectCompilationHelper(ProjectCompilationHelper.cs)-
GetProjectCompilationAsync(string)- Load from .csproj -
CreateCompilationFromSources(...)- Build from source files -
GetProjectSourceFiles(string)- Find all .cs files
-
-
ComponentCompiler(ComponentCompiler.cs)-
SetProjectCompilation(Compilation)- Enable external type resolution -
ClearProjectCompilation()- Revert to isolated mode
-
Models/Product.cs:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
}Pages/ProductCard.cs:
[Component]
public class ProductCard : StatelessComponent
{
public Product Item { get; set; }
protected override HtmlNode Build()
{
return Container(
Heading(Item.Name),
Text($"${Item.Price:F2}"),
Text(Item.InStock ? "In Stock" : "Out of Stock")
);
}
}Generated JavaScript (with project compilation):
class ProductCard extends StatelessComponent {
build() {
return Container([
Heading(this.item.name),
Text(`$${this.item.price.toFixed(2)}`),
Text(this.item.inStock ? 'In Stock' : 'Out of Stock')
]);
}
}Models/Address.cs:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}Models/Customer.cs:
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
public Address ShippingAddress { get; set; }
}Pages/CheckoutPage.cs:
[Page("/checkout")]
public class CheckoutPage : StatefulComponent
{
private Customer _customer;
protected override HtmlNode Render()
{
return Container(
Text($"Customer: {_customer.Name}"),
Text($"Email: {_customer.Email}"),
Text($"Shipping: {_customer.ShippingAddress.City}, {_customer.ShippingAddress.ZipCode}")
);
}
}All property accesses are correctly resolved and converted to JavaScript.
Tests verify external type resolution works correctly:
[Fact]
public void SemanticModel_WithProjectCompilation_CanResolveExternalTypes()
{
// Create compilation with User and Component
var userTree = CSharpSyntaxTree.ParseText("public class User { ... }");
var componentTree = CSharpSyntaxTree.ParseText("public class UserProfile { ... }");
var compilation = CSharpCompilation.Create("Test", new[] { userTree, componentTree }, ...);
var provider = new SemanticModelProvider();
provider.SetProjectCompilation(compilation);
var semanticModel = provider.GetSemanticModel(componentTree);
// Assert: User type is resolved
var userType = semanticModel.Compilation.GetTypeByMetadataName("User");
userType.Should().NotBeNull();
}See ExternalTypeResolutionTests.cs for complete test suite.
- Without
SetProjectCompilation(): minimal compilation — each file is resolved in isolation - With
SetProjectCompilation(): full project type resolution
Minimal overhead:
- Project compilation is created once at build time
- Shared across all component files
- No per-file compilation penalty
Memory efficient:
- Single compilation instance
- Reuses existing Roslyn infrastructure
-
Requires compilation before component compilation: The project must be compiled (or at least parsed) before running the component compiler.
-
MSBuildWorkspace availability:
GetProjectCompilationAsyncrequires MSBuild APIs, which may not be available in all contexts. UseCreateCompilationFromSourcesas fallback. -
Generated code: Auto-generated files in
obj/are excluded to avoid conflicts.
- Components reference classes from other files; full type information is available during compilation, so JavaScript generation for external type members is correct.
- The SDK build feeds the compiler the project's real assembly references (
--refs), so receiver types resolve even across package boundaries.
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);