From f0f2c3e6175bd67f22dbcf7d4144bfb0ffcae33d Mon Sep 17 00:00:00 2001 From: David Pine Date: Tue, 22 Mar 2022 09:17:53 -0500 Subject: [PATCH] Blazorator updates. Target interface instead of extensions. Append service naming. --- README.md | 759 ++++++++++-------- .../Pages/ClientPosition.razor | 15 +- .../Pages/Counter.razor | 2 +- ...onExtensions.cs => IGeolocationService.cs} | 4 +- src/Blazor.Geolocation.Server/README.md | 6 +- ...onExtensions.cs => IGeolocationService.cs} | 4 +- src/Blazor.Geolocation.WebAssembly/README.md | 6 +- ...torageExtensions.cs => IStorageService.cs} | 7 +- src/Blazor.LocalStorage.Server/README.md | 6 +- ...torageExtensions.cs => IStorageService.cs} | 7 +- src/Blazor.LocalStorage.WebAssembly/README.md | 6 +- .../Builders/MethodBuilderDetails.cs | 13 +- .../Builders/SourceBuilder.cs | 22 +- .../CSharp/CSharpObject.cs | 26 +- ...nsionObject.cs => CSharpTopLevelObject.cs} | 319 ++------ .../CSharp/CSharpType.cs | 6 +- .../Extensions/CSharpMethodExtensions.cs | 6 +- .../Extensions/StringExtensions.cs | 22 +- ...ails.cs => InterfaceDeclarationDetails.cs} | 4 +- .../JavaScriptInteropGenerator.cs | 92 +-- .../JavaScriptInteropSyntaxContextReceiver.cs | 12 +- .../Options/GeneratorOptions.cs | 4 +- .../Parsers/LibDomParser.Interfaces.cs | 30 +- .../Parsers/LibDomParser.cs | 6 +- ...ourceCode.JSAutoGenericInteropAttribute.cs | 2 +- .../SourceCode.JSAutoInteropAttribute.cs | 5 +- .../LibDomParserInterfacesTests.cs | 2 +- .../LibDomParserTests.cs | 2 +- 28 files changed, 645 insertions(+), 750 deletions(-) rename src/Blazor.Geolocation.Server/{AsynchronousGeolocationExtensions.cs => IGeolocationService.cs} (60%) rename src/Blazor.Geolocation.WebAssembly/{SynchronousGeolocationExtensions.cs => IGeolocationService.cs} (57%) rename src/Blazor.LocalStorage.Server/{AsynchronousLocalStorageExtensions.cs => IStorageService.cs} (62%) rename src/Blazor.LocalStorage.WebAssembly/{SynchronousLocalStorageExtensions.cs => IStorageService.cs} (63%) rename src/Blazor.SourceGenerators/CSharp/{CSharpExtensionObject.cs => CSharpTopLevelObject.cs} (57%) rename src/Blazor.SourceGenerators/{ClassDeclarationDetails.cs => InterfaceDeclarationDetails.cs} (65%) diff --git a/README.md b/README.md index abb062e..6fd9e53 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,6 @@ Consider the _SynchronousLocalStorageExtensions.cs_ C# file: namespace Microsoft.JSInterop; -/// -/// Source generated extension methods on the implementation. -/// [JSAutoGenericInterop( TypeName = "Storage", Implementation = "window.localStorage", @@ -46,12 +43,14 @@ namespace Microsoft.JSInterop; "getItem", "setItem:value" })] -internal static partial class SynchronousLocalStorageExtensions +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IStorageService +#pragma warning restore CS1591 // The XML comments are source generated { } ``` -This code designates itself into the `Microsoft.JSInterop` namespace, making all of the source generated extensions available to anyone consumer who uses types from this namespace. It uses the `JSAutoInterop` to specify: +This code designates itself into the `Microsoft.JSInterop` namespace, making the source generated implementation available to anyone consumer who uses types from this namespace. It uses the `JSAutoGenericInterop` to specify: - `TypeName = "Storage"`: sets the type to [`Storage`](https://developer.mozilla.org/docs/Web/API/Storage). - `Implementation = "window.localStorage"`: expresses how to locate the implementation of the specified type from the globally scoped `window` object, this is the [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage) implementation. @@ -61,183 +60,134 @@ This code designates itself into the `Microsoft.JSInterop` namespace, making all > The generic method descriptors syntax is: > `"methodName"` for generic return type and `"methodName:parameterName"` for generic parameter type. -The file needs to define an extension class and needs to be `partial`, for example; `internal static partial class`. Decorating the class with the `JSAutoInterop` (or `JSAutoGenericInterop) attribute will source generate the following C# code: +The file needs to define an interface and it needs to be `partial`, for example; `public partial interface`. Decorating the class with the `JSAutoInterop` (or `JSAutoGenericInterop) attribute will source generate the following C# code, as shown in the source generated `IStorageService.g.cs`: ```csharp -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License: -// https://github.com/IEvangelist/blazorators/blob/main/LICENSE -// Auto-generated by blazorators. - using Blazor.Serialization.Extensions; using System.Text.Json; #nullable enable namespace Microsoft.JSInterop; -internal static partial class SynchronousLocalStorageExtensions +/// +/// Source generated interface definition of the Storage type. +/// +public partial interface IStorageService { - /// - /// Source generated extension method implementation of window.localStorage.clear. - /// - /// - public static void Clear( - this IJSInProcessRuntime javaScript) => - javaScript.InvokeVoid("window.localStorage.clear"); - - /// - /// Source generated extension method implementation of window.localStorage.getItem. - /// - /// - public static TResult? GetItem( - this IJSInProcessRuntime javaScript, - string key, - JsonSerializerOptions? options = null) => - javaScript.Invoke( - "window.localStorage.getItem", - key) - .FromJson(options); - - /// - /// Source generated extension method implementation of window.localStorage.key. - /// - /// - public static string? Key( - this IJSInProcessRuntime javaScript, - double index) => - javaScript.Invoke( - "window.localStorage.key", - index); - - /// - /// Source generated extension method implementation of window.localStorage.removeItem. - /// - /// - public static void RemoveItem( - this IJSInProcessRuntime javaScript, - string key) => - javaScript.InvokeVoid( - "window.localStorage.removeItem", - key); - - /// - /// Source generated extension method implementation of window.localStorage.setItem. - /// - /// - public static void SetItem( - this IJSInProcessRuntime javaScript, - string key, - TArg value, - JsonSerializerOptions? options = null) => - javaScript.InvokeVoid( - "window.localStorage.setItem", - key, - value.ToJson(options)); - - /// - /// Source generated extension method implementation of window.localStorage.length. - /// - /// - public static double Length( - this IJSInProcessRuntime javaScript) => - javaScript.Invoke( - "eval", "window.localStorage.length"); + /// + /// Source generated implementation of window.localStorage.length. + /// + /// + double Length + { + get; + } + + /// + /// Source generated implementation of window.localStorage.clear. + /// + /// + void Clear(); + + /// + /// Source generated implementation of window.localStorage.getItem. + /// + /// + TValue? GetItem(string key, JsonSerializerOptions? options = null); + + /// + /// Source generated implementation of window.localStorage.key. + /// + /// + string? Key(double index); + + /// + /// Source generated implementation of window.localStorage.removeItem. + /// + /// + void RemoveItem(string key); + + /// + /// Source generated implementation of window.localStorage.setItem. + /// + /// + void SetItem(string key, TValue value, JsonSerializerOptions? options = null); } ``` These internal extension methods rely on the `IJSInProcessRuntime` to perform JavaScript interop. From the given `TypeName` and corresponding `Implementation`, the following code is also generated: -- `IStorage.g.cs`: The interface for the corresponding `Storage` Web API surface area. -- `LocalStorge.g.cs`: The `internal` implementation of the `IStorage` interface. +- `IStorageService.g.cs`: The interface for the corresponding `Storage` Web API surface area. +- `LocalStorgeService.g.cs`: The `internal` implementation of the `IStorageService` interface. - `LocalStorageServiceCollectionExtensions.g.cs`: Extension methods to add the `IStorage` service to the dependency injection `IServiceCollection`. -Here is the source generated `IStorage.g.cs`: +Here is the source generated `LocalStorageService` implementation: ```csharp -using Blazor.Serialization.Extensions; -using System.Text.Json; +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License: +// https://github.com/IEvangelist/blazorators/blob/main/LICENSE +// Auto-generated by blazorators. #nullable enable -namespace Microsoft.JSInterop; - -/// -/// Source generated interface definition of the Storage type. -/// -public interface IStorage -{ - /// - /// Source generated implementation of window.localStorage.clear. - /// - /// - void Clear(); - - /// - /// Source generated implementation of window.localStorage.getItem. - /// - /// - TResult? GetItem(string key, JsonSerializerOptions? options = null); - - /// - /// Source generated implementation of window.localStorage.key. - /// - /// - string? Key(double index); - - /// - /// Source generated implementation of window.localStorage.removeItem. - /// - /// - void RemoveItem(string key); - - /// - /// Source generated implementation of window.localStorage.setItem. - /// - /// - void SetItem(string key, TArg value, JsonSerializerOptions? options = null); - - /// - /// Source generated implementation of window.localStorage.length. - /// - /// - double Length { get; } -} -``` - -Here is the source generated `LocalStorage` implementation: -```csharp using Blazor.Serialization.Extensions; +using Microsoft.JSInterop; using System.Text.Json; -#nullable enable namespace Microsoft.JSInterop; -/// -internal class LocalStorage : IStorage +/// +internal sealed class LocalStorageService : IStorageService { - private readonly IJSInProcessRuntime _javaScript = null!; - - public LocalStorage(IJSInProcessRuntime javaScript) => _javaScript = javaScript; - - /// - void IStorage.Clear() => _javaScript.Clear(); - - /// - TResult? IStorage.GetItem(string key, JsonSerializerOptions? options) - where TResult : default => _javaScript.GetItem(key, options); - - /// - string? IStorage.Key(double index) => _javaScript.Key(index); - - /// - void IStorage.RemoveItem(string key) => _javaScript.RemoveItem(key); - - /// - void IStorage.SetItem(string key, TArg value, JsonSerializerOptions? options) => - _javaScript.SetItem(key, value, options); - - /// - double IStorage.Length => _javaScript.Length(); + private readonly IJSInProcessRuntime _javaScript = null; + + /// + double IStorageService.Length => _javaScript.Invoke("eval", new object[1] + { + "window.localStorage.length" + }); + + public LocalStorageService(IJSInProcessRuntime javaScript) + { + _javaScript = javaScript; + } + + /// + void IStorageService.Clear() + { + _javaScript.InvokeVoid("window.localStorage.clear"); + } + + /// + TValue? IStorageService.GetItem(string key, JsonSerializerOptions? options) + { + return _javaScript.Invoke("window.localStorage.getItem", new object[1] + { + key + }).FromJson(options); + } + + /// + string? IStorageService.Key(double index) + { + return _javaScript.Invoke("window.localStorage.key", new object[1] + { + index + }); + } + + /// + void IStorageService.RemoveItem(string key) + { + _javaScript.InvokeVoid("window.localStorage.removeItem", key); + } + + /// + void IStorageService.SetItem(string key, TValue value, JsonSerializerOptions? options) + { + _javaScript.InvokeVoid("window.localStorage.setItem", key, value.ToJson(options)); + } } ``` @@ -272,16 +222,15 @@ The `Blazor.LocalStorage.Server` package, generates extensions on the `IJSRuntim namespace Microsoft.JSInterop; -/// -/// Source generated extension methods on the implementation. -/// [JSAutoInterop( TypeName = "Storage", Implementation = "window.localStorage", HostingModel = BlazorHostingModel.Server, OnlyGeneratePureJS = true, Url = "https://developer.mozilla.org/docs/Web/API/Window/localStorage")] -public static partial class AsynchronousLocalStorageExtensions +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IStorageService +#pragma warning restore CS1591 // The XML comments are source generated { } ``` @@ -299,133 +248,51 @@ using System.Threading.Tasks; #nullable enable namespace Microsoft.JSInterop; -public static partial class AsynchronousLocalStorageExtensions +public partial interface IStorageService { - /// - /// Source generated extension method implementation of window.localStorage.clear. - /// - /// - public static ValueTask ClearAsync( - this IJSRuntime javaScript) => - javaScript.InvokeVoidAsync("window.localStorage.clear"); - - /// - /// Source generated extension method implementation of window.localStorage.getItem. - /// - /// - public static ValueTask GetItemAsync( - this IJSRuntime javaScript, - string key) => - javaScript.InvokeAsync( - "window.localStorage.getItem", - key); - - /// - /// Source generated extension method implementation of window.localStorage.key. - /// - /// - public static ValueTask KeyAsync( - this IJSRuntime javaScript, - double index) => - javaScript.InvokeAsync( - "window.localStorage.key", - index); - - /// - /// Source generated extension method implementation of window.localStorage.removeItem. - /// - /// - public static ValueTask RemoveItemAsync( - this IJSRuntime javaScript, - string key) => - javaScript.InvokeVoidAsync( - "window.localStorage.removeItem", - key); - - /// - /// Source generated extension method implementation of window.localStorage.setItem. - /// - /// - public static ValueTask SetItemAsync( - this IJSRuntime javaScript, - string key, - string value) => - javaScript.InvokeVoidAsync( - "window.localStorage.setItem", - key, - value); - - /// - /// Source generated extension method implementation of window.localStorage.length. - /// - /// - public static ValueTask LengthAsync( - this IJSRuntime javaScript) => - javaScript.InvokeAsync( - "eval", "window.localStorage.length"); + /// + /// Source generated implementation of window.localStorage.length. + /// + /// + ValueTask Length + { + get; + } + + /// + /// Source generated implementation of window.localStorage.clear. + /// + /// + ValueTask ClearAsync(); + + /// + /// Source generated implementation of window.localStorage.getItem. + /// + /// + ValueTask GetItemAsync(string key); + + /// + /// Source generated implementation of window.localStorage.key. + /// + /// + ValueTask KeyAsync(double index); + + /// + /// Source generated implementation of window.localStorage.removeItem. + /// + /// + ValueTask RemoveItemAsync(string key); + + /// + /// Source generated implementation of window.localStorage.setItem. + /// + /// + ValueTask SetItemAsync(string key, string value); } ``` Notice, that since the generic method descriptors are not added generics are not supported. This is not yet implemented as I've been focusing on WebAssembly scenarios. -## Using the `Blazor.LocalStorage.WebAssembly` package 📦 - -The [`Blazor.LocalStorage.WebAssembly`](https://www.nuget.org/packages/Blazor.LocalStorage.WebAssembly) package is a WebAssembly specific implementation of the `localStorage` Web API that has been source generated. The example above is the result of this published package. - -This package exposes a convenience extension method on the `IServiceCollection` type, named `AddWebAssemblyLocalStorage`. Calling this will expose the `IJSInProcessRuntime` as a dependency injection service type as a scoped lifetime. Consider the following _Program.cs_ C# file for an example Blazor WebAssembly template project: - -```csharp -using Blazor.ExampleConsumer; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; - -var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); - -builder.Services.AddScoped( - sp => new HttpClient - { - BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) - }); - -// Adds the IJSInProcessRuntime type to DI. -builder.Services.AddWebAssemblyLocalStorage(); - -await builder.Build().RunAsync(); -``` - -Then, in your components you can consume this as you would any other service type. Consider the _Counter.razor_ file: - -```razor -@page "/counter" -@inject IJSInProcessRuntime JavaScript - -Counter (@_currentCount) - -

Counter

- -

Current count: @_currentCount

- - - -@code { - private int _currentCount = 0; - - private void IncrementCount() => JavaScript.SetItem("CounterValue", (++ _currentCount).ToString()); - - protected override void OnInitialized() - { - base.OnInitialized(); - - if (JavaScript.GetItem("CounterValue") is { } count && int.TryParse(count, out var currentCount)) - { - _currentCount = currentCount; - } - } -} -``` - ## Design goals 🎯 I was hoping to use the [TypeScript lib.dom.d.ts](https://github.com/microsoft/TypeScript/blob/315b807489b8ff3a892179488fb0c00398d9b2c3/lib/lib.dom.d.ts) bits as input. This input would be read, parsed, and cached within the generator. The generator code would be capable of generating extension methods on the `IJSRuntime`. Additionally, the generator will create object graphs from the well know web APIs. @@ -463,117 +330,295 @@ interface Geolocation { Ideally, I would like to be able to define a C# class such as this: ```csharp +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.JSInterop; + [JSAutoInterop( TypeName = "Geolocation", - PathFromWidow = "window.navigator.geolocation", - Url = "https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API", - OnlyGeneratePureJS = false)] -public static partial class GeolocationExtensions { } + Implementation = "window.navigator.geolocation", + Url = "https://developer.mozilla.org/docs/Web/API/Geolocation")] +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IGeolocationService +#pragma warning restore CS1591 // The XML comments are source generated +{ +} ``` The source generator will expose the `JSAutoInteropAttribute`, and consuming libraries will decorate their classes with it. The generator code will see this class, and use the `TypeName` from the attribute to find the corresponding type to implement. With the type name, the generator will generate the corresponding methods, and return types. The method implementations will be extensions of the `IJSRuntime`. -The following is an example resulting source generated `GeolocationExtensions` object: +The following is an example resulting source generated `IGeolocationService` object: ```csharp -using Microsoft.JSInterop; - -namespace Microsoft.JSInterop.Extensions; +namespace Microsoft.JSInterop; -public static partial class GeolocationExtensions +public partial interface IGeolocationService { - /// - /// See . - /// - public static ValueTask GetCurrentPositionAsync( - this IJSRuntime jsRuntime, - T dotnetObject, - string successMethodName, - string? errorMethodName = null, + /// + /// Source generated implementation of window.navigator.geolocation.clearWatch. + /// + /// + void ClearWatch(double watchId); + + /// + /// Source generated implementation of window.navigator.geolocation.getCurrentPosition. + /// + /// + /// The calling Razor (or Blazor) component. + /// Expects the name of a "JSInvokableAttribute" C# method with the following System.Action{GeolocationPosition}". + /// Expects the name of a "JSInvokableAttribute" C# method with the following System.Action{GeolocationPositionError}". + /// The PositionOptions value. + void GetCurrentPosition( + TComponent component, + string onSuccessCallbackMethodName, + string? onErrorCallbackMethodName = null, PositionOptions? options = null) - where T : class - { - return jsRuntime.InvokeVoidAsync( - "blazorator.getCurrentLocation", - DotNetObjectReference.Create(dotnetObject), - successMethodName, - errorMethodName, - options - ); - } - - /// - /// See - /// - public static ValueTask WatchPositionAsync( - this IJSRuntime jsRuntime, - T dotnetObject, - string successMethodName, - string? errorMethodName = null, - PositionOptions? options = null) - where T : class - { - return jsRuntime.InvokeAsync( - "blazorator.watchPosition", - DotNetObjectReference.Create(dotnetObject), - successMethodName, - errorMethodName, - options - ); - } - - /// - /// See - /// - public ValueTask ClearWatchAsync(this IJSRuntime jsRuntime, double id) - { - return jsRuntime.InvokevoidAsync( - "navigator.geolocation.clearWatch", id - ); - } + where TComponent : class; + + /// + /// Source generated implementation of window.navigator.geolocation.watchPosition. + /// + /// + /// The calling Razor (or Blazor) component. + /// Expects the name of a "JSInvokableAttribute" C# method with the following System.Action{GeolocationPosition}". + /// Expects the name of a "JSInvokableAttribute" C# method with the following System.Action{GeolocationPositionError}". + /// The PositionOptions value. + double WatchPosition( + TComponent component, + string onSuccessCallbackMethodName, + string? onErrorCallbackMethodName = null, + PositionOptions? options = null) + where TComponent : class; } + ``` The generator will also produce the corresponding APIs object types. For example, the Geolocation API defines the following: +- `GeolocationService` - `PositionOptions` - `GeolocationCoordinates` - `GeolocationPosition` - `GeolocationPositionError` +```csharp +namespace Microsoft.JSInterop; + +/// +internal sealed class GeolocationService : IGeolocationService +{ + private readonly IJSInProcessRuntime _javaScript = null; + + public GeolocationService(IJSInProcessRuntime javaScript) + { + _javaScript = javaScript; + } + + /// + void IGeolocationService.ClearWatch(double watchId) + { + _javaScript.InvokeVoid("window.navigator.geolocation.clearWatch", watchId); + } + + /// + void IGeolocationService.GetCurrentPosition( + TComponent component, + string onSuccessCallbackMethodName, + string? onErrorCallbackMethodName, + PositionOptions? options) + { + _javaScript.InvokeVoid("blazorators.getCurrentPosition", DotNetObjectReference.Create(component), onSuccessCallbackMethodName, onErrorCallbackMethodName, options); + } + + /// + double IGeolocationService.WatchPosition( + TComponent component, + string onSuccessCallbackMethodName, + string? onErrorCallbackMethodName, + PositionOptions? options) + { + return _javaScript.Invoke("blazorators.watchPosition", new object[4] + { + DotNetObjectReference.Create(component), + onSuccessCallbackMethodName, + onErrorCallbackMethodName, + options + }); + } +} +``` + ```csharp using System.Text.Json.Serialization; -namespace Microsoft.JSInterop.Extensions; +namespace Microsoft.JSInterop; /// -/// See +/// Source-generated object representing an ideally immutable GeolocationPosition value. /// -public record GeolocationPosition( - [property: JsonPropertyName("coords")] GeolocationCoordinates Coordinates, - [property: JsonPropertyName("timestamp")] DOMTimeStamp TimeStamp -); +public class GeolocationPosition +{ + /// + /// Source-generated property representing the GeolocationPosition.coords value. + /// + [JsonPropertyName("coords")] + public GeolocationCoordinates Coords + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationPosition.timestamp value. + /// + [JsonPropertyName("timestamp")] + public long Timestamp + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationPosition.timestamp value, + /// converted as a in UTC. + /// + [JsonIgnore] + public DateTime TimestampAsUtcDateTime => Timestamp.ToDateTimeFromUnix(); +} /// -/// See +/// Source-generated object representing an ideally immutable GeolocationCoordinates value. /// -public record GeolocationCoordinates( - [property: JsonPropertyName("latitude")] double Latitude, - [property: JsonPropertyName("longitude")] double Longitude, - [property: JsonPropertyName("altitude")] double Altitude, - [property: JsonPropertyName("altitudeAccuracy")] double? AltitudeAccuracy, - [property: JsonPropertyName("heading")] double? Heading, - [property: JsonPropertyName("speed")] double Speed -); +public class GeolocationCoordinates +{ + /// + /// Source-generated property representing the GeolocationCoordinates.accuracy value. + /// + [JsonPropertyName("accuracy")] + public double Accuracy + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationCoordinates.altitude value. + /// + [JsonPropertyName("altitude")] + public double? Altitude + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationCoordinates.altitudeAccuracy value. + /// + [JsonPropertyName("altitudeAccuracy")] + public double? AltitudeAccuracy + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationCoordinates.heading value. + /// + [JsonPropertyName("heading")] + public double? Heading + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationCoordinates.latitude value. + /// + [JsonPropertyName("latitude")] + public double Latitude + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationCoordinates.longitude value. + /// + [JsonPropertyName("longitude")] + public double Longitude + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationCoordinates.speed value. + /// + [JsonPropertyName("speed")] + public double? Speed + { + get; + set; + } +} /// -/// See +/// Source-generated object representing an ideally immutable GeolocationPositionError value. /// -public record GeolocationPositionError( - [property: JsonPropertyName("code")] short Code, - [property: JsonPropertyName("message")] string Message -); +public class GeolocationPositionError +{ + /// + /// Source-generated property representing the GeolocationPositionError.code value. + /// + [JsonPropertyName("code")] + public double Code + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationPositionError.message value. + /// + [JsonPropertyName("message")] + public string Message + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationPositionError.PERMISSION_DENIED value. + /// + [JsonPropertyName("PERMISSION_DENIED")] + public double PERMISSION_DENIED + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationPositionError.POSITION_UNAVAILABLE value. + /// + [JsonPropertyName("POSITION_UNAVAILABLE")] + public double POSITION_UNAVAILABLE + { + get; + set; + } + + /// + /// Source-generated property representing the GeolocationPositionError.TIMEOUT value. + /// + [JsonPropertyName("TIMEOUT")] + public double TIMEOUT + { + get; + set; + } +} // Additional models omitted for brevity... ``` diff --git a/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor b/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor index 04b0397..5a131c3 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor +++ b/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor @@ -1,6 +1,6 @@ @page "/geolocation" -@inject IGeolocation Geolocation +@inject IGeolocationService Geolocation Geolocation

@@ -15,7 +15,7 @@ { : - @(_position.TimestampAsDateTime.ToLocalTime().ToString()) + @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) } @@ -37,30 +37,29 @@ } @code { - const string GeolocationOptionsKey = "geolocation-options"; readonly JsonSerializerOptions _opts = new() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - readonly PositionOptions _options = new() { EnableHighAccuracy = true, MaximumAge = 0, Timeout = 15_000 }; + GeolocationPosition? _position; GeolocationPositionError? _positionError; bool _isLoading = true; protected override void OnInitialized() => Geolocation.GetCurrentPosition( - this, - nameof(OnPositionRecieved), - nameof(OnPositionError), - _options); + component: this, + onSuccessCallbackMethodName: nameof(OnPositionRecieved), + onErrorCallbackMethodName: nameof(OnPositionError), + options: _options); [JSInvokable] public void OnPositionRecieved(GeolocationPosition position) diff --git a/samples/Blazor.ExampleConsumer/Pages/Counter.razor b/samples/Blazor.ExampleConsumer/Pages/Counter.razor index 738350e..276041b 100644 --- a/samples/Blazor.ExampleConsumer/Pages/Counter.razor +++ b/samples/Blazor.ExampleConsumer/Pages/Counter.razor @@ -1,5 +1,5 @@ @page "/counter" -@inject IStorage LocalStorage +@inject IStorageService LocalStorage @inject NavigationManager Nav @implements IDisposable diff --git a/src/Blazor.Geolocation.Server/AsynchronousGeolocationExtensions.cs b/src/Blazor.Geolocation.Server/IGeolocationService.cs similarity index 60% rename from src/Blazor.Geolocation.Server/AsynchronousGeolocationExtensions.cs rename to src/Blazor.Geolocation.Server/IGeolocationService.cs index be53bcd..0672cc0 100644 --- a/src/Blazor.Geolocation.Server/AsynchronousGeolocationExtensions.cs +++ b/src/Blazor.Geolocation.Server/IGeolocationService.cs @@ -8,6 +8,8 @@ namespace Microsoft.JSInterop; Implementation = "window.navigator.geolocation", HostingModel = BlazorHostingModel.Server, Url = "https://developer.mozilla.org/docs/Web/API/Geolocation")] -internal static partial class AsynchronousGeolocationExtensions +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IGeolocationService +#pragma warning restore CS1591 // The XML comments are source generated { } \ No newline at end of file diff --git a/src/Blazor.Geolocation.Server/README.md b/src/Blazor.Geolocation.Server/README.md index 501b4fd..f7f0ac8 100644 --- a/src/Blazor.Geolocation.Server/README.md +++ b/src/Blazor.Geolocation.Server/README.md @@ -4,7 +4,7 @@ The [`Blazor.Geolocation.Server`](https://www.nuget.org/packages/Blazor.Geolocat ## Get started -After the NuGet package is added as a reference, call the `AddGeolocationServices` method to register the `IGeolocation` service type. +After the NuGet package is added as a reference, call the `AddGeolocationServices` method to register the `IGeolocationService` service type. ```csharp var builder = WebApplication.CreateBuilder(args); @@ -27,7 +27,7 @@ app.MapFallbackToPage("/_Host"); app.Run(); ``` -Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `IGeolocation` type. The interface takes the following shape: +Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `IGeolocationService` type. The interface takes the following shape: ```csharp #nullable enable @@ -36,7 +36,7 @@ namespace Microsoft.JSInterop; /// /// Source generated interface definition of the Geolocation type. /// -public interface IGeolocation +public interface IGeolocationService { /// /// Source generated implementation of window.navigator.geolocation.clearWatch. diff --git a/src/Blazor.Geolocation.WebAssembly/SynchronousGeolocationExtensions.cs b/src/Blazor.Geolocation.WebAssembly/IGeolocationService.cs similarity index 57% rename from src/Blazor.Geolocation.WebAssembly/SynchronousGeolocationExtensions.cs rename to src/Blazor.Geolocation.WebAssembly/IGeolocationService.cs index a229a3d..4be9c60 100644 --- a/src/Blazor.Geolocation.WebAssembly/SynchronousGeolocationExtensions.cs +++ b/src/Blazor.Geolocation.WebAssembly/IGeolocationService.cs @@ -7,6 +7,8 @@ namespace Microsoft.JSInterop; TypeName = "Geolocation", Implementation = "window.navigator.geolocation", Url = "https://developer.mozilla.org/docs/Web/API/Geolocation")] -internal static partial class SynchronousGeolocationExtensions +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IGeolocationService +#pragma warning restore CS1591 // The XML comments are source generated { } \ No newline at end of file diff --git a/src/Blazor.Geolocation.WebAssembly/README.md b/src/Blazor.Geolocation.WebAssembly/README.md index 83eeb0d..4febcff 100644 --- a/src/Blazor.Geolocation.WebAssembly/README.md +++ b/src/Blazor.Geolocation.WebAssembly/README.md @@ -4,7 +4,7 @@ The [`Blazor.Geolocation.WebAssembly`](https://www.nuget.org/packages/Blazor.Geo ## Get started -After the NuGet package is added as a reference, call the `AddGeolocationServices` method to register the `IGeolocation` service type. +After the NuGet package is added as a reference, call the `AddGeolocationServices` method to register the `IGeolocationService` service type. ```csharp using Microsoft.AspNetCore.Components.Web; @@ -25,7 +25,7 @@ builder.Services.AddGeolocationServices(); await builder.Build().RunAsync(); ``` -Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `IGeolocation` type. The interface takes the following shape: +Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `IGeolocationService` type. The interface takes the following shape: ```csharp #nullable enable @@ -34,7 +34,7 @@ namespace Microsoft.JSInterop; /// /// Source generated interface definition of the Geolocation type. /// -public interface IGeolocation +public interface IGeolocationService { /// /// Source generated implementation of window.navigator.geolocation.clearWatch. diff --git a/src/Blazor.LocalStorage.Server/AsynchronousLocalStorageExtensions.cs b/src/Blazor.LocalStorage.Server/IStorageService.cs similarity index 62% rename from src/Blazor.LocalStorage.Server/AsynchronousLocalStorageExtensions.cs rename to src/Blazor.LocalStorage.Server/IStorageService.cs index fd330b7..2f42204 100644 --- a/src/Blazor.LocalStorage.Server/AsynchronousLocalStorageExtensions.cs +++ b/src/Blazor.LocalStorage.Server/IStorageService.cs @@ -3,15 +3,14 @@ namespace Microsoft.JSInterop; -/// -/// Source generated extension methods on the implementation. -/// [JSAutoInterop( TypeName = "Storage", Implementation = "window.localStorage", HostingModel = BlazorHostingModel.Server, OnlyGeneratePureJS = true, Url = "https://developer.mozilla.org/docs/Web/API/Window/localStorage")] -internal static partial class AsynchronousLocalStorageExtensions +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IStorageService +#pragma warning restore CS1591 // The XML comments are source generated { } \ No newline at end of file diff --git a/src/Blazor.LocalStorage.Server/README.md b/src/Blazor.LocalStorage.Server/README.md index fc79d7a..82f64ef 100644 --- a/src/Blazor.LocalStorage.Server/README.md +++ b/src/Blazor.LocalStorage.Server/README.md @@ -4,7 +4,7 @@ The [`Blazor.LocalStorage.Server`](https://www.nuget.org/packages/Blazor.LocalSt ## Get started -After the NuGet package is added as a reference, call the `AddLocalStorageServices` method to register the `ILocalStorage` service type. +After the NuGet package is added as a reference, call the `AddLocalStorageServices` method to register the `IStorageService` service type. ```csharp var builder = WebApplication.CreateBuilder(args); @@ -27,7 +27,7 @@ app.MapFallbackToPage("/_Host"); app.Run(); ``` -Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `ILocalStorage` type. The interface takes the following shape: +Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `IStorageService` type. The interface takes the following shape: ```csharp using Blazor.Serialization.Extensions; @@ -39,7 +39,7 @@ namespace Microsoft.JSInterop; /// /// Source generated interface definition of the Storage type. /// -public interface IStorage +public interface IStorageService { /// /// Source generated implementation of window.localStorage.clear. diff --git a/src/Blazor.LocalStorage.WebAssembly/SynchronousLocalStorageExtensions.cs b/src/Blazor.LocalStorage.WebAssembly/IStorageService.cs similarity index 63% rename from src/Blazor.LocalStorage.WebAssembly/SynchronousLocalStorageExtensions.cs rename to src/Blazor.LocalStorage.WebAssembly/IStorageService.cs index 5e99a64..8fce66d 100644 --- a/src/Blazor.LocalStorage.WebAssembly/SynchronousLocalStorageExtensions.cs +++ b/src/Blazor.LocalStorage.WebAssembly/IStorageService.cs @@ -3,9 +3,6 @@ namespace Microsoft.JSInterop; -/// -/// Source generated extension methods on the implementation. -/// [JSAutoGenericInterop( TypeName = "Storage", Implementation = "window.localStorage", @@ -15,6 +12,8 @@ namespace Microsoft.JSInterop; "getItem", "setItem:value" })] -internal static partial class SynchronousLocalStorageExtensions +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +public partial interface IStorageService +#pragma warning restore CS1591 // The XML comments are source generated { } \ No newline at end of file diff --git a/src/Blazor.LocalStorage.WebAssembly/README.md b/src/Blazor.LocalStorage.WebAssembly/README.md index f8069c9..05bc3f9 100644 --- a/src/Blazor.LocalStorage.WebAssembly/README.md +++ b/src/Blazor.LocalStorage.WebAssembly/README.md @@ -4,7 +4,7 @@ The [`Blazor.LocalStorage.WebAssembly`](https://www.nuget.org/packages/Blazor.Lo ## Get started -After the NuGet package is added as a reference, call the `AddLocalStorageServices` method to register the `ILocalStorage` service type. +After the NuGet package is added as a reference, call the `AddLocalStorageServices` method to register the `IStorageService` service type. ```csharp using Microsoft.AspNetCore.Components.Web; @@ -25,7 +25,7 @@ builder.Services.AddLocalStorageServices(); await builder.Build().RunAsync(); ``` -Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `ILocalStorage` type. The interface takes the following shape: +Anywhere needed within your Razor component, or Blazor client code — either `@inject` or `[Inject]` the `IStorageService` type. The interface takes the following shape: ```csharp using Blazor.Serialization.Extensions; @@ -37,7 +37,7 @@ namespace Microsoft.JSInterop; /// /// Source generated interface definition of the Storage type. /// -public interface IStorage +public interface IStorageService { /// /// Source generated implementation of window.localStorage.clear. diff --git a/src/Blazor.SourceGenerators/Builders/MethodBuilderDetails.cs b/src/Blazor.SourceGenerators/Builders/MethodBuilderDetails.cs index fafc0cd..75cd942 100644 --- a/src/Blazor.SourceGenerators/Builders/MethodBuilderDetails.cs +++ b/src/Blazor.SourceGenerators/Builders/MethodBuilderDetails.cs @@ -18,14 +18,9 @@ internal readonly record struct MethodBuilderDetails( string? GenericTypeArgs) { /// - /// A value representing the generic return type, "TResult". + /// A value representing the generic return type, "TValue". /// - internal const string GenericReturnType = "TResult"; - - /// - /// A value representing the generic return type, "TArg". - /// - internal const string GenericArgumentType = "TArg"; + internal static readonly string GenericTypeValue = "TValue"; /// /// A value representing the generic component type, "TComponent". @@ -44,8 +39,8 @@ internal static MethodBuilderDetails Create(CSharpMethod method, GeneratorOption var containsGenericParameters = method.ParameterDefinitions.Any(p => p.IsGenericParameter(method.RawName, options)); var genericTypeArgs = isGenericReturnType - ? ToGenericTypeArgument(GenericReturnType) - : containsGenericParameters ? ToGenericTypeArgument(GenericArgumentType) : null; + ? ToGenericTypeArgument(GenericTypeValue) + : containsGenericParameters ? ToGenericTypeArgument(GenericTypeValue) : null; var fullyQualifiedJavaScriptIdentifier = method.JavaScriptMethodDependency?.InvokableMethodName; fullyQualifiedJavaScriptIdentifier ??= options.Implementation is not null diff --git a/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs b/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs index cf669ab..3f7ff72 100644 --- a/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs +++ b/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs @@ -17,12 +17,16 @@ internal sealed class SourceBuilder private Indentation _indentation = new(0); private string? _implementationName; private string? _interfaceName; + private bool _isService; internal int IndentationLevel => _indentation.Level; - internal string ImplementationName => _implementationName ??= _options.Implementation!.ToImplementationName(); - internal string InterfaceName => _interfaceName ??= $"I{_options.TypeName}"; + internal string ImplementationName => _implementationName ??= + $"{_options.Implementation.ToImplementationName(_isService)}"; + internal string InterfaceName => _interfaceName ??= + _options.TypeName.ToInterfaceName(_isService); - internal SourceBuilder(GeneratorOptions options) => _options = options; + internal SourceBuilder(GeneratorOptions options, bool isService = true) => + (_options, _isService) = (options, isService); internal SourceBuilder AppendCopyRightHeader() { @@ -64,20 +68,12 @@ internal SourceBuilder AppendNamespace(string namespaceString, bool isNullableCo return this; } - internal SourceBuilder AppendStaticPartialClassDeclaration( - string className, string? accessModifier) - { - _builder.Append($"{accessModifier ?? "internal"} static partial class {className}{_newLine}"); - - return this; - } - internal SourceBuilder AppendPublicInterfaceDeclaration() { _builder.Append($"/// {_newLine}"); _builder.Append($"/// Source generated interface definition of the {_options.TypeName} type.{_newLine}"); _builder.Append($"/// {_newLine}"); - _builder.Append($"public interface I{_options.TypeName}{_newLine}"); + _builder.Append($"public partial interface {InterfaceName}{_newLine}"); return this; } @@ -85,7 +81,7 @@ internal SourceBuilder AppendPublicInterfaceDeclaration() internal SourceBuilder AppendInternalImplementationDeclaration() { _builder.Append($"/// {_newLine}"); - _builder.Append($"internal class {ImplementationName} : {InterfaceName}{_newLine}"); + _builder.Append($"internal sealed class {ImplementationName} : {InterfaceName}{_newLine}"); return this; } diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs b/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs index 14ea63a..92a0af8 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs @@ -60,6 +60,14 @@ internal string ToClassString() builder.Append("using System.Text.Json.Serialization;\r\n\r\n"); builder.Append("namespace Microsoft.JSInterop;\r\n\r\n"); + + builder.Append( + $"/// \r\n"); + builder.Append( + $"/// Source-generated object representing an ideally immutable {TypeName} value.\r\n"); + builder.Append( + $"/// \r\n"); + builder.Append($"public class {TypeName}\r\n{{\r\n"); var memberCount = Properties.Count; @@ -73,6 +81,12 @@ in Properties.Select((kvp, index) => (index, kvp))) var statementTerminator = member.IsNullable ? " = default!;" : ""; var csharpMemberName = memberName.CapitalizeFirstLetter(); + builder.Append( + $" /// \r\n"); + builder.Append( + $" /// Source-generated property representing the {TypeName}.{memberName} value.\r\n"); + builder.Append( + $" /// \r\n"); builder.Append( $" [JsonPropertyName(\"{memberName}\")]\r\n"); builder.Append( @@ -81,11 +95,21 @@ in Properties.Select((kvp, index) => (index, kvp))) // Add readonly property for converting DOMTimeStamp (long) to DateTime. if (member.RawTypeName is "DOMTimeStamp" or "DOMTimeStamp | null") { + builder.Append( + $" /// \r\n"); + builder.Append( + $" /// Source-generated property representing the {TypeName}.{memberName} value, \r\n"); + + builder.Append( + $" /// converted as a in UTC.\r\n"); + builder.Append( + $" /// \r\n"); + var nullable = member.IsNullable ? "?" : ""; builder.Append( $" [JsonIgnore]\r\n"); builder.Append( - $" public DateTime{nullable} {csharpMemberName}AsDateTime => {csharpMemberName}.ToDateTimeFromUnix();\r\n"); + $" public DateTime{nullable} {csharpMemberName}AsUtcDateTime => {csharpMemberName}.ToDateTimeFromUnix();\r\n"); } } diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpExtensionObject.cs b/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs similarity index 57% rename from src/Blazor.SourceGenerators/CSharp/CSharpExtensionObject.cs rename to src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs index 22c23fb..3591579 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpExtensionObject.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs @@ -5,7 +5,7 @@ namespace Blazor.SourceGenerators.CSharp; -internal sealed partial record CSharpExtensionObject(string RawTypeName) +internal sealed partial record CSharpTopLevelObject(string RawTypeName) : ICSharpDependencyGraphObject { public List? Properties { get; init; } = new(); @@ -37,204 +37,6 @@ in DependentTypes public int MemberCount => Properties!.Count + Methods!.Count; - internal string ToStaticPartialClassString( - GeneratorOptions options, - string existingClassName, - string? namespaceString, - bool? isPublic = null) - { - var builder = new SourceBuilder(options) - .AppendCopyRightHeader() - .AppendUsingDeclarations() - .AppendNamespace(namespaceString ?? "Microsoft.JSInterop") - .AppendStaticPartialClassDeclaration(existingClassName, isPublic.GetValueOrDefault() ? "public" : null) - .AppendOpeningCurlyBrace() - .IncreaseIndentation(); - - var methodLevel = builder.IndentationLevel; - - // Add methods. - foreach (var (index, method) in (Methods ?? new List()).Select()) - { - var details = MethodBuilderDetails.Create(method, options); - builder.ResetIndentiationTo(methodLevel); - - if (method.IsPureJavaScriptInvocation) - { - builder.AppendTripleSlashMethodComments(method) - .AppendRaw($"public static {details.ReturnType} {details.CSharpMethodName}{details.Suffix}{details.GenericTypeArgs}(", postIncreaseIndentation: true) - .AppendRaw($"this {details.ExtendingType} javaScript", appendNewLine: false); - - if (method.ParameterDefinitions.Count > 0) - { - builder.AppendRaw(","); - foreach (var (pi, parameter) in method.ParameterDefinitions.Select()) - { - var isGenericType = parameter.IsGenericParameter(method.RawName, options); - if (pi.IsLast) - { - if (details.IsSerializable) - { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},"); - builder.AppendRaw($"JsonSerializerOptions? options = null) =>"); - } - else - { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType)}) =>"); - } - } - else - { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},"); - } - } - - if (details.IsVoid) - { - builder.AppendRaw($"javaScript.InvokeVoid{details.Suffix}("); - } - else - { - builder.AppendRaw($"javaScript.Invoke{details.Suffix}<{details.BareType}>("); - } - - builder.IncreaseIndentation() - .AppendRaw($"\"{details.FullyQualifiedJavaScriptIdentifier}\","); - - // Write method body / expression, and arguments to javaScript.Invoke* - foreach (var (ai, parameter) in method.ParameterDefinitions.Select()) - { - var isGenericType = parameter.IsGenericParameter(method.RawName, options); - if (ai.IsLast) - { - if (details.IsGenericReturnType) - { - builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)})"); - builder.AppendRaw($".FromJson{details.GenericTypeArgs}(options);"); - } - else - { - builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)});"); - } - - if (!index.IsLast) builder.AppendLine(); - } - else - { - builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)},"); - } - } - - builder.DecreaseIndentation(); - } - else - { - builder.AppendRaw(") =>"); - if (details.IsVoid) - { - builder.AppendRaw($"javaScript.InvokeVoid{details.Suffix}(\"{details.FullyQualifiedJavaScriptIdentifier}\");"); - builder.AppendLine(); - } - else - { - builder.AppendRaw($"javaScript.Invoke{details.Suffix}<{details.BareType}>(\"{details.FullyQualifiedJavaScriptIdentifier}\");"); - builder.AppendLine(); - } - } - } - else if (options.OnlyGeneratePureJS is false) - { - var genericTypeArgs = details.GenericTypeArgs ?? - MethodBuilderDetails.ToGenericTypeArgument( - MethodBuilderDetails.GenericComponentType); - - builder.AppendTripleSlashMethodComments(method) - .AppendRaw( - $"public static {details.ReturnType} {details.CSharpMethodName}{details.Suffix}{genericTypeArgs}(", - postIncreaseIndentation: true) - .AppendRaw($"this {details.ExtendingType} javaScript,") - .AppendRaw($"TComponent component", appendNewLine: false, postIncreaseIndentation: true); - - if (method.ParameterDefinitions.Count > 0) - { - builder.AppendRaw(","); - foreach (var (pi, parameter) in method.ParameterDefinitions.Select()) - { - var isGenericType = parameter.IsGenericParameter(method.RawName, options); - if (pi.IsLast) - { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType)}) where TComponent : class =>"); - } - else - { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},"); - } - } - - if (details.IsVoid) - { - builder.AppendRaw($"javaScript.InvokeVoid{details.Suffix}("); - } - else - { - builder.AppendRaw($"javaScript.Invoke{details.Suffix}<{details.BareType}>("); - } - - builder.IncreaseIndentation() - .AppendRaw($"\"{details.FullyQualifiedJavaScriptIdentifier}\","); - - builder.AppendRaw($"DotNetObjectReference.Create(component),"); - - // Write method body / expression, and arguments to javaScript.Invoke* - foreach (var (ai, parameter) in method.ParameterDefinitions.Select()) - { - var isGenericType = parameter.IsGenericParameter(method.RawName, options); - if (ai.IsLast) - { - builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)});"); - - if (!index.IsLast) builder.AppendLine(); - } - else - { - builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)},"); - } - } - - builder.DecreaseIndentation(); - } - } - } - - // Add properties. - foreach (var (index, property) in (Properties ?? new List()).Select()) - { - if (index.IsFirst) builder.AppendLine(); - if (property.IsIndexer) continue; - - builder.ResetIndentiationTo(methodLevel); - - var details = PropertyBuilderDetails.Create(property, options); - - builder.AppendTripleSlashPropertyComments(details.Property) - .AppendRaw($"public static {details.ReturnType} {details.CSharpPropertyName}(", postIncreaseIndentation: true) - .AppendRaw($"this {details.ExtendingType} javaScript) =>", postIncreaseIndentation: true) - .AppendRaw($"javaScript.Invoke{details.Suffix}{details.GenericTypeArgs}(", postIncreaseIndentation: true) - .AppendRaw($"\"eval\", \"{details.FullyQualifiedJavaScriptIdentifier}\");"); - - if (!index.IsLast) - { - builder.AppendLine(); - } - } - - builder.ResetIndentiationTo(0); - builder.AppendClosingCurlyBrace(); - - var staticPartialClassDefinition = TryFormatCSharpSourceText(builder.ToSourceCodeString()); - return staticPartialClassDefinition; - } - internal string ToInterfaceString( GeneratorOptions options, string? namespaceString) @@ -393,7 +195,8 @@ internal string ToImplementationString( if (method.IsPureJavaScriptInvocation) { - builder.AppendEmptyTripleSlashInheritdocComments() + var memberName = $"{details.CSharpMethodName}{details.Suffix}"; + builder.AppendTripleSlashInheritdocComments(builder.InterfaceName, memberName) .AppendRaw( $"{details.ReturnType} {builder.InterfaceName}.{details.CSharpMethodName}{details.Suffix}{details.GenericTypeArgs}(", appendNewLine: false, @@ -402,7 +205,7 @@ internal string ToImplementationString( if (method.ParameterDefinitions.Count > 0) { var genericTypeParameterConstraint = details.IsGenericReturnType - ? $" where {MethodBuilderDetails.GenericReturnType} : default" + ? $" where {MethodBuilderDetails.GenericTypeValue} : default" : ""; foreach (var (pi, parameter) in method.ParameterDefinitions.Select()) @@ -412,7 +215,7 @@ internal string ToImplementationString( { if (details.IsSerializable) { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType, true)},"); + builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},"); builder.AppendRaw($"JsonSerializerOptions? options){genericTypeParameterConstraint} =>"); } else @@ -428,45 +231,55 @@ internal string ToImplementationString( if (details.IsVoid) { - builder.AppendRaw( - $"_javaScript.{details.CSharpMethodName}{details.Suffix}(", - postIncreaseIndentation: true); + builder.AppendRaw($"_javaScript.InvokeVoid{details.Suffix}(", postIncreaseIndentation: true); } else { - builder.AppendRaw( - $"_javaScript.{details.CSharpMethodName}{details.Suffix}{details.GenericTypeArgs}(", - postIncreaseIndentation: true); + builder.AppendRaw($"_javaScript.Invoke{details.Suffix}<{details.BareType}>(", postIncreaseIndentation: true); } + builder.IncreaseIndentation() + .AppendRaw($"\"{details.FullyQualifiedJavaScriptIdentifier}\","); + // Write method body / expression, and arguments to javaScript.Invoke* foreach (var (ai, parameter) in method.ParameterDefinitions.Select()) { + var isGenericType = parameter.IsGenericParameter(method.RawName, options); if (ai.IsLast) { - if (details.IsSerializable) + if (details.IsGenericReturnType) { - builder.AppendRaw($"{parameter.ToArgumentString(false)},"); - builder.AppendRaw($"options);"); + // Overridden to control explicitly + builder.AppendRaw($"{parameter.ToArgumentString(toJson: false)})"); + builder.AppendRaw($".FromJson{details.GenericTypeArgs}(options);"); } else { - builder.AppendRaw($"{parameter.ToArgumentString(false)});"); + builder.AppendRaw($"{parameter.ToArgumentString(details.ContainsGenericParameters)});"); } - if (!index.IsLast) - { - builder.AppendLine(); - } + if (!index.IsLast) builder.AppendLine(); } else { - builder.AppendRaw($"{parameter.ToArgumentString(false)},"); + builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)},"); } } + + builder.DecreaseIndentation(); } else { - builder.AppendRaw($") => _javaScript.{details.CSharpMethodName}{details.Suffix}();"); + builder.AppendRaw(") =>"); + if (details.IsVoid) + { + builder.AppendRaw($"_javaScript.InvokeVoid{details.Suffix}(\"{details.FullyQualifiedJavaScriptIdentifier}\");"); + builder.AppendLine(); + } + else + { + builder.AppendRaw($"_javaScript.Invoke{details.Suffix}<{details.BareType}>(\"{details.FullyQualifiedJavaScriptIdentifier}\");"); + builder.AppendLine(); + } } } else if (options.OnlyGeneratePureJS is false) @@ -475,7 +288,8 @@ internal string ToImplementationString( MethodBuilderDetails.ToGenericTypeArgument( MethodBuilderDetails.GenericComponentType); - builder.AppendEmptyTripleSlashInheritdocComments() + var memberName = $"{details.CSharpMethodName}{details.Suffix}"; + builder.AppendTripleSlashInheritdocComments(builder.InterfaceName, memberName) .AppendRaw( $"{details.ReturnType} {builder.InterfaceName}.{details.CSharpMethodName}{details.Suffix}{genericTypeArgs}(", postIncreaseIndentation: true) @@ -483,59 +297,51 @@ internal string ToImplementationString( if (method.ParameterDefinitions.Count > 0) { - builder.AppendRaw(","); - - var genericTypeParameterConstraint = " where TComponent : class"; + builder.AppendRaw( + ", ", false, false, true); foreach (var (pi, parameter) in method.ParameterDefinitions.Select()) { - var isGenericType = parameter.IsGenericParameter(method.RawName, options); if (pi.IsLast) { - builder.AppendRaw($"{parameter.ToParameterString(false, true)}){genericTypeParameterConstraint} =>"); + builder.AppendRaw($"{parameter.ToParameterString(false, true)}) where TComponent : class =>"); } else { - builder.AppendRaw($"{parameter.ToParameterString(isGenericType, true)},"); + builder.AppendRaw($"{parameter.ToParameterString(false, true)},"); } } if (details.IsVoid) { - builder.AppendRaw( - $"_javaScript.{details.CSharpMethodName}{details.Suffix}(", - postIncreaseIndentation: true); + builder.AppendRaw($"_javaScript.InvokeVoid{details.Suffix}("); } else { - builder.AppendRaw( - $"_javaScript.{details.CSharpMethodName}{details.Suffix}{details.GenericTypeArgs}(", - postIncreaseIndentation: true); + builder.AppendRaw($"_javaScript.Invoke{details.Suffix}<{details.BareType}>("); } + builder.IncreaseIndentation() + .AppendRaw($"\"{details.FullyQualifiedJavaScriptIdentifier}\","); + + builder.AppendRaw($"DotNetObjectReference.Create(component),"); + + // Write method body / expression, and arguments to javaScript.Invoke* foreach (var (ai, parameter) in method.ParameterDefinitions.Select()) { + var isGenericType = parameter.IsGenericParameter(method.RawName, options); if (ai.IsLast) { - builder.AppendRaw($"{parameter.ToArgumentString(false)});"); + builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)});"); - if (!index.IsLast) - { - builder.AppendLine(); - } + if (!index.IsLast) builder.AppendLine(); } else { - if (ai.IsFirst) - { - builder.AppendRaw("component,"); - } - builder.AppendRaw($"{parameter.ToArgumentString(false)},"); + builder.AppendRaw($"{parameter.ToArgumentString(isGenericType)},"); } } - } - else - { - builder.AppendRaw($") => _javaScript.{details.CSharpMethodName}{details.Suffix}();"); + + builder.DecreaseIndentation(); } } } @@ -550,12 +356,10 @@ internal string ToImplementationString( var details = PropertyBuilderDetails.Create(property, options); - var expression = details.Property.IsReadonly - ? $"_javaScript.{details.CSharpPropertyName}();" - : "throw new System.NotImplementedException();"; - - builder.AppendEmptyTripleSlashInheritdocComments() - .AppendRaw($"{details.ReturnType} {builder.InterfaceName}.{details.CSharpPropertyName} => {expression}"); + builder.AppendTripleSlashInheritdocComments(builder.InterfaceName, details.CSharpPropertyName) + .AppendRaw($"{details.ReturnType} {builder.InterfaceName}.{details.CSharpPropertyName} =>", postIncreaseIndentation: true) + .AppendRaw($"_javaScript.Invoke{details.Suffix}{details.GenericTypeArgs}(", postIncreaseIndentation: true) + .AppendRaw($"\"eval\", \"{details.FullyQualifiedJavaScriptIdentifier}\");"); if (!index.IsLast) { @@ -575,12 +379,13 @@ internal string ToServiceCollectionExtensions( string implementation) { var addExpression = options.IsWebAssembly - ? @" services.AddSingleton(serviceProvider => + ? @"services.AddSingleton(serviceProvider => (IJSInProcessRuntime)serviceProvider.GetRequiredService()) " : "services"; - var typeName = $"I{options.TypeName}"; + var @interface = options.TypeName.ToInterfaceName(); + var nonService = options.Implementation.ToImplementationName(false); var extensions = $@"// Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License: // https://github.com/IEvangelist/blazorators/blob/main/LICENSE @@ -591,14 +396,14 @@ internal string ToServiceCollectionExtensions( namespace Microsoft.Extensions.DependencyInjection; /// -public static class {implementation}ServiceCollectionExtensions +public static class {nonService}ServiceCollectionExtensions {{ /// - /// Adds the service to the service collection. + /// Adds the service to the service collection. /// - public static IServiceCollection Add{implementation}Services( + public static IServiceCollection Add{nonService}Services( this IServiceCollection services) => - {addExpression}.AddSingleton<{typeName}, {implementation}>(); + {addExpression}.AddSingleton<{@interface}, {implementation}>(); }} "; diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpType.cs b/src/Blazor.SourceGenerators/CSharp/CSharpType.cs index baea9c4..88c6848 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpType.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpType.cs @@ -1,6 +1,8 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. +using Blazor.SourceGenerators.Builders; + namespace Blazor.SourceGenerators.CSharp; internal record CSharpType( @@ -53,8 +55,8 @@ public string ToParameterString(bool isGenericType = false, bool overrideNullabi if (isGenericType) { return IsNullable - ? $"TArg? {ToArgumentString()}" - : $"TArg {ToArgumentString()}"; + ? $"{MethodBuilderDetails.GenericTypeValue}? {ToArgumentString()}" + : $"{MethodBuilderDetails.GenericTypeValue} {ToArgumentString()}"; } var isCallback = ActionDeclation is not null; diff --git a/src/Blazor.SourceGenerators/Extensions/CSharpMethodExtensions.cs b/src/Blazor.SourceGenerators/Extensions/CSharpMethodExtensions.cs index 9ebd3bf..888559e 100644 --- a/src/Blazor.SourceGenerators/Extensions/CSharpMethodExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/CSharpMethodExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. +using Blazor.SourceGenerators.Builders; + namespace Blazor.SourceGenerators.Extensions; internal static class CSharpMethodExtensions @@ -53,8 +55,8 @@ internal static (string ReturnType, string BareType) GetMethodTypes( method.IsReturnTypeNullable ? "?" : ""; return options.IsWebAssembly - ? ($"TResult{nullable}", primitiveType) - : ($"ValueTask", primitiveType); + ? ($"{MethodBuilderDetails.GenericTypeValue}{nullable}", primitiveType) + : ($"ValueTask<{MethodBuilderDetails.GenericTypeValue}{nullable}>", primitiveType); } if (options.IsWebAssembly) diff --git a/src/Blazor.SourceGenerators/Extensions/StringExtensions.cs b/src/Blazor.SourceGenerators/Extensions/StringExtensions.cs index 5cb8c53..2f187c3 100644 --- a/src/Blazor.SourceGenerators/Extensions/StringExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/StringExtensions.cs @@ -13,9 +13,21 @@ internal static string LowerCaseFirstLetter(this string name) => internal static string ToGeneratedFileName(this string name) => $"{name}.g.cs"; - internal static string ToImplementationName(this string pathFromWindow) => - (pathFromWindow.Contains(".") - ? pathFromWindow.Substring(pathFromWindow.LastIndexOf(".") + 1) - : pathFromWindow) - .CapitalizeFirstLetter(); + internal static string ToImplementationName(this string implementation, bool isService = true) + { + var impl = (implementation.Contains(".") + ? implementation.Substring(implementation.LastIndexOf(".") + 1) + : implementation).CapitalizeFirstLetter(); + + return $"{impl}{(isService ? "Service" : "")}"; + } + + internal static string ToInterfaceName(this string typeName, bool isService = true) + { + var type = (typeName.Contains(".") + ? typeName.Substring(typeName.LastIndexOf(".") + 1) + : typeName).CapitalizeFirstLetter(); + + return $"I{type}{(isService ? "Service" : "")}"; + } } diff --git a/src/Blazor.SourceGenerators/ClassDeclarationDetails.cs b/src/Blazor.SourceGenerators/InterfaceDeclarationDetails.cs similarity index 65% rename from src/Blazor.SourceGenerators/ClassDeclarationDetails.cs rename to src/Blazor.SourceGenerators/InterfaceDeclarationDetails.cs index 4bb7185..2b9497b 100644 --- a/src/Blazor.SourceGenerators/ClassDeclarationDetails.cs +++ b/src/Blazor.SourceGenerators/InterfaceDeclarationDetails.cs @@ -3,7 +3,7 @@ namespace Blazor.SourceGenerators; -record class ClassDeclarationDetails( +record class InterfaceDeclarationDetails( GeneratorOptions Options, - ClassDeclarationSyntax ClassDeclaration, + InterfaceDeclarationSyntax InterfaceDeclaration, AttributeSyntax InteropAttribute); diff --git a/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs b/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs index 376227a..460decf 100644 --- a/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs +++ b/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs @@ -43,37 +43,10 @@ public void Execute(GeneratorExecutionContext context) return; } - foreach (var (options, classDeclaration, attribute) in receiver.ClassDeclarations) + foreach (var (options, classDeclaration, attribute) in receiver.InterfaceDeclarations) { - if (options.TypeName is null) + if (options is null || IsDiaganostic(options, context, attribute)) { - context.ReportDiagnostic( - Diagnostic.Create( - Descriptors.TypeNameRequiredDiagnostic, - attribute.GetLocation())); - - continue; - } - - if (options.Implementation is null) - { - context.ReportDiagnostic( - Diagnostic.Create( - Descriptors.PathFromWindowRequiredDiagnostic, - attribute.GetLocation())); - - continue; - } - - if (options.SupportsGenerics && - context.Compilation.ReferencedAssemblyNames.Any(ai => - ai.Name.Equals("Blazor.Serialization", StringComparison.OrdinalIgnoreCase)) is false) - { - context.ReportDiagnostic( - Diagnostic.Create( - Descriptors.MissingBlazorSerializationPackageReferenceDiagnostic, - attribute.GetLocation())); - continue; } @@ -85,12 +58,12 @@ public void Execute(GeneratorExecutionContext context) var model = context.Compilation.GetSemanticModel(classDeclaration.SyntaxTree); var symbol = model.GetDeclaredSymbol(classDeclaration); - if (symbol is not ITypeSymbol typeSymbol || !typeSymbol.IsStatic) + if (symbol is not ITypeSymbol typeSymbol || typeSymbol.IsStatic) { continue; } - var result = _libDomParser.ParseStaticType(options.TypeName!); + var result = _libDomParser.ParseTargetType(options.TypeName!); if (result.Status == ParserResultStatus.SuccessfullyParsed && result.Value is not null) { @@ -112,28 +85,20 @@ public void Execute(GeneratorExecutionContext context) _ => null }; - // Source generate the internal extension methods - context.AddSource( - typeSymbol.Name.ToGeneratedFileName(), - SourceText.From( - staticObject.ToStaticPartialClassString( - options, - classDeclaration.Identifier.ValueText, - namespaceString), - Encoding.UTF8)); + var @interface = + options.TypeName.ToInterfaceName(); + var implementation = + options.Implementation.ToImplementationName(); // Source generate the public interface context.AddSource( - $"I{options.TypeName}".ToGeneratedFileName(), + $"{@interface}".ToGeneratedFileName(), SourceText.From( staticObject.ToInterfaceString( options, namespaceString), Encoding.UTF8)); - var implementation = - options.Implementation.ToImplementationName(); - // Source generate the internal implementation context.AddSource( $"{implementation}".ToGeneratedFileName(), @@ -145,7 +110,7 @@ public void Execute(GeneratorExecutionContext context) // Source generate the service collection DI extension context.AddSource( - $"{implementation}ServiceCollectionExtensions".ToGeneratedFileName(), + $"{options.Implementation.ToImplementationName(false)}ServiceCollectionExtensions".ToGeneratedFileName(), SourceText.From( staticObject.ToServiceCollectionExtensions( options, @@ -154,4 +119,41 @@ public void Execute(GeneratorExecutionContext context) } } } + + static bool IsDiaganostic(GeneratorOptions options, GeneratorExecutionContext context, AttributeSyntax attribute) + { + if (options.TypeName is null) + { + context.ReportDiagnostic( + Diagnostic.Create( + Descriptors.TypeNameRequiredDiagnostic, + attribute.GetLocation())); + + return true; + } + + if (options.Implementation is null) + { + context.ReportDiagnostic( + Diagnostic.Create( + Descriptors.PathFromWindowRequiredDiagnostic, + attribute.GetLocation())); + + return true; + } + + if (options.SupportsGenerics && + context.Compilation.ReferencedAssemblyNames.Any(ai => + ai.Name.Equals("Blazor.Serialization", StringComparison.OrdinalIgnoreCase)) is false) + { + context.ReportDiagnostic( + Diagnostic.Create( + Descriptors.MissingBlazorSerializationPackageReferenceDiagnostic, + attribute.GetLocation())); + + return true; + } + + return false; + } } diff --git a/src/Blazor.SourceGenerators/JavaScriptInteropSyntaxContextReceiver.cs b/src/Blazor.SourceGenerators/JavaScriptInteropSyntaxContextReceiver.cs index 18fd732..bd7e04c 100644 --- a/src/Blazor.SourceGenerators/JavaScriptInteropSyntaxContextReceiver.cs +++ b/src/Blazor.SourceGenerators/JavaScriptInteropSyntaxContextReceiver.cs @@ -7,14 +7,14 @@ internal class JavaScriptInteropSyntaxContextReceiver : ISyntaxContextReceiver { internal static ISyntaxContextReceiver Create() => new JavaScriptInteropSyntaxContextReceiver(); - public HashSet ClassDeclarations { get; } = new(); + public HashSet InterfaceDeclarations { get; } = new(); public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { - if (context.Node is ClassDeclarationSyntax classDeclaration && - classDeclaration.AttributeLists.Count > 0) + if (context.Node is InterfaceDeclarationSyntax interfaceDeclaration && + interfaceDeclaration.AttributeLists.Count > 0) { - foreach (var attributeListSyntax in classDeclaration.AttributeLists) + foreach (var attributeListSyntax in interfaceDeclaration.AttributeLists) { foreach (var attributeSyntax in attributeListSyntax.Attributes) { @@ -27,10 +27,10 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) if (isAutoInterop || isAutoGenericInterop) { - ClassDeclarations.Add( + InterfaceDeclarations.Add( new( Options: attributeSyntax.GetGeneratorOptions(isAutoGenericInterop), - ClassDeclaration: classDeclaration, + InterfaceDeclaration: interfaceDeclaration, InteropAttribute: attributeSyntax)); } } diff --git a/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs b/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs index d4be18a..9057751 100644 --- a/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs +++ b/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs @@ -17,8 +17,8 @@ /// internal sealed record GeneratorOptions( bool SupportsGenerics, - string? TypeName = null, - string? Implementation = null, + string TypeName = null!, + string Implementation = null!, bool OnlyGeneratePureJS = false, string? Url = null, string[]? GenericMethodDescriptors = null, diff --git a/src/Blazor.SourceGenerators/Parsers/LibDomParser.Interfaces.cs b/src/Blazor.SourceGenerators/Parsers/LibDomParser.Interfaces.cs index a2bccdf..b8822d3 100644 --- a/src/Blazor.SourceGenerators/Parsers/LibDomParser.Interfaces.cs +++ b/src/Blazor.SourceGenerators/Parsers/LibDomParser.Interfaces.cs @@ -109,9 +109,9 @@ internal sealed partial class LibDomParser return cSharpObject; } - internal CSharpExtensionObject? ToExtensionObject(string typeScriptTypeDeclaration) + internal CSharpTopLevelObject? ToTopLevelObject(string typeScriptTypeDeclaration) { - CSharpExtensionObject? extensionObject = null; + CSharpTopLevelObject? topLevelObject = null; var lineTokens = typeScriptTypeDeclaration.Split(new[] { '\n' }); foreach (var (index, segment) in lineTokens.Select((s, i) => (i, s))) @@ -121,7 +121,7 @@ internal sealed partial class LibDomParser var typeName = InterfaceTypeNameRegex.GetMatchGroupValue(segment, "TypeName"); if (typeName is not null) { - extensionObject = new(typeName); + topLevelObject = new(typeName); continue; } else @@ -130,7 +130,7 @@ internal sealed partial class LibDomParser } } - if (extensionObject is null) + if (topLevelObject is null) { break; } @@ -162,12 +162,15 @@ internal sealed partial class LibDomParser ParseParameters( methodName, parameters, - obj => extensionObject.DependentTypes![obj.TypeName] = obj); + obj => topLevelObject.DependentTypes![obj.TypeName] = obj); CSharpMethod cSharpMethod = - new(methodName, CleanseReturnType(returnType), parameterDefinitions, javaScriptMethod); + new(methodName, + CleanseReturnType(returnType), + parameterDefinitions, + javaScriptMethod); - extensionObject.Methods!.Add(cSharpMethod); + topLevelObject.Methods!.Add(cSharpMethod); continue; } @@ -187,8 +190,13 @@ internal sealed partial class LibDomParser name = name.Replace("?", "").Replace("readonly ", ""); - CSharpProperty cSharpProperty = new(name, type, isNullable, isReadonly); - extensionObject.Properties!.Add(cSharpProperty); + CSharpProperty cSharpProperty = + new(name, + type, + isNullable, + isReadonly); + + topLevelObject.Properties!.Add(cSharpProperty); var mappedType = cSharpProperty.MappedTypeName; @@ -201,7 +209,7 @@ internal sealed partial class LibDomParser var obj = ToObject(typeScriptDefinitionText); if (obj is not null) { - extensionObject.DependentTypes![obj.TypeName] = obj; + topLevelObject.DependentTypes![obj.TypeName] = obj; } } @@ -209,7 +217,7 @@ internal sealed partial class LibDomParser } } - return extensionObject; + return topLevelObject; } internal CSharpAction? ToAction(string typeScriptTypeDeclaration) diff --git a/src/Blazor.SourceGenerators/Parsers/LibDomParser.cs b/src/Blazor.SourceGenerators/Parsers/LibDomParser.cs index fc7d3cd..444a01d 100644 --- a/src/Blazor.SourceGenerators/Parsers/LibDomParser.cs +++ b/src/Blazor.SourceGenerators/Parsers/LibDomParser.cs @@ -7,9 +7,9 @@ internal sealed partial class LibDomParser { private readonly LibDomReader _reader = new(); - public ParserResult ParseStaticType(string typeName) + public ParserResult ParseTargetType(string typeName) { - ParserResult result = new(ParserResultStatus.Unknown); + ParserResult result = new(ParserResultStatus.Unknown); if (_reader.TryGetDeclaration(typeName, out var typeScriptDefinitionText) && typeScriptDefinitionText is not null) @@ -19,7 +19,7 @@ public ParserResult ParseStaticType(string typeName) result = result with { Status = ParserResultStatus.SuccessfullyParsed, - Value = ToExtensionObject(typeScriptDefinitionText) + Value = ToTopLevelObject(typeScriptDefinitionText) }; } catch (Exception ex) diff --git a/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoGenericInteropAttribute.cs b/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoGenericInteropAttribute.cs index 7d0c771..af8607a 100644 --- a/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoGenericInteropAttribute.cs +++ b/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoGenericInteropAttribute.cs @@ -36,7 +36,7 @@ public class JSAutoGenericInteropAttribute : JSAutoInteropAttribute /// /// new[] /// { - /// ""getItem"", // Serializes the return type of getItem as TResult + /// ""getItem"", // Serializes the return type of getItem as TValue /// ""setItem:value"" // Serializes the value parameter of the setItem TValue /// } /// diff --git a/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoInteropAttribute.cs b/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoInteropAttribute.cs index dc1e75b..b0ccbf0 100644 --- a/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoInteropAttribute.cs +++ b/src/Blazor.SourceGenerators/Source/SourceCode.JSAutoInteropAttribute.cs @@ -21,7 +21,10 @@ static partial class SourceCode /// /// This will source generate all the extension methods for the IJSInProcessRuntime type for the localStorage APIs. /// -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +[AttributeUsage( + AttributeTargets.Interface, + Inherited = false, + AllowMultiple = false)] public class JSAutoInteropAttribute : Attribute { /// diff --git a/tests/Blazor.SourceGenerators.Tests/LibDomParserInterfacesTests.cs b/tests/Blazor.SourceGenerators.Tests/LibDomParserInterfacesTests.cs index 7556bcd..0fa777c 100644 --- a/tests/Blazor.SourceGenerators.Tests/LibDomParserInterfacesTests.cs +++ b/tests/Blazor.SourceGenerators.Tests/LibDomParserInterfacesTests.cs @@ -66,7 +66,7 @@ public void CorrectlyConvertsTypeScriptInterfaceToCSharpExtensionObject() watchPosition(successCallback: PositionCallback, errorCallback?: PositionErrorCallback | null, options?: PositionOptions): number; }"; var sut = new LibDomParser(); - var actual = sut.ToExtensionObject(text); + var actual = sut.ToTopLevelObject(text); Assert.NotNull(actual); diff --git a/tests/Blazor.SourceGenerators.Tests/LibDomParserTests.cs b/tests/Blazor.SourceGenerators.Tests/LibDomParserTests.cs index cec7a1d..3311f4f 100644 --- a/tests/Blazor.SourceGenerators.Tests/LibDomParserTests.cs +++ b/tests/Blazor.SourceGenerators.Tests/LibDomParserTests.cs @@ -12,7 +12,7 @@ public class LibDomParserTests public void ParseStaticObjectCorrectly() { var sut = new LibDomParser(); - var parserResult = sut.ParseStaticType("Geolocation"); + var parserResult = sut.ParseTargetType("Geolocation"); Assert.Equal(ParserResultStatus.SuccessfullyParsed, parserResult.Status);