diff --git a/Directory.Packages.props b/Directory.Packages.props index 298f5ba6..2d63211f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,16 +5,16 @@ - - - + + + - - - - - - + + + + + + diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor b/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor index 4d89d350..d119a1d2 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor +++ b/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor @@ -1,7 +1,5 @@ @page "/geolocation" -@inject IGeolocationService Geolocation - Geolocation

Geolocation diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor.cs b/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor.cs index 048d70a2..e278fdc8 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor.cs +++ b/samples/Blazor.ExampleConsumer/Components/Pages/ClientPosition.razor.cs @@ -3,7 +3,7 @@ namespace Blazor.ExampleConsumer.Components.Pages; -public sealed partial class ClientPosition +public sealed partial class ClientPosition(IGeolocationService geolocation) { readonly JsonSerializerOptions _opts = new() { @@ -23,7 +23,7 @@ public sealed partial class ClientPosition bool _isLoading = true; protected override void OnInitialized() => - Geolocation.GetCurrentPosition( + geolocation.GetCurrentPosition( component: this, onSuccessCallbackMethodName: nameof(OnPositionReceived), onErrorCallbackMethodName: nameof(OnPositionError), diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/ListenToMe.razor.cs b/samples/Blazor.ExampleConsumer/Components/Pages/ListenToMe.razor.cs index 45cb8452..bc55caa5 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/ListenToMe.razor.cs +++ b/samples/Blazor.ExampleConsumer/Components/Pages/ListenToMe.razor.cs @@ -3,7 +3,9 @@ namespace Blazor.ExampleConsumer.Components.Pages; -public sealed partial class ListenToMe : IDisposable +public sealed partial class ListenToMe( + ISpeechRecognitionService speechRecognition, + ISessionStorageService sessionStorage) : IDisposable { const string TranscriptKey = "listen-to-me-page-transcript"; @@ -12,20 +14,14 @@ public sealed partial class ListenToMe : IDisposable SpeechRecognitionErrorEvent? _errorEvent; string? _transcript; - [Inject] - public ISpeechRecognitionService SpeechRecognition { get; set; } = null!; - - [Inject] - public ISessionStorageService SessionStorage { get; set; } = null!; - protected override void OnInitialized() => - _transcript = SessionStorage.GetItem(TranscriptKey); + _transcript = sessionStorage.GetItem(TranscriptKey); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await SpeechRecognition.InitializeModuleAsync(); + await speechRecognition.InitializeModuleAsync(); } } @@ -33,14 +29,14 @@ void OnRecognizeSpeechClick() { if (_isRecognizingSpeech) { - SpeechRecognition.CancelSpeechRecognition(false); + speechRecognition.CancelSpeechRecognition(false); } else { var bcp47Tag = CurrentUICulture.Name; _recognitionSubscription?.Dispose(); - _recognitionSubscription = SpeechRecognition.RecognizeSpeech( + _recognitionSubscription = speechRecognition.RecognizeSpeech( bcp47Tag, OnRecognized, OnError, @@ -75,7 +71,7 @@ void OnRecognized(string transcript) _ => $"{_transcript.Trim()} {transcript}".Trim() }; - SessionStorage.SetItem(TranscriptKey, _transcript); + sessionStorage.SetItem(TranscriptKey, _transcript); StateHasChanged(); } diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor b/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor index b96d0ae3..4d263ae5 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor +++ b/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor @@ -46,13 +46,13 @@ Speak - - - \ No newline at end of file diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor.cs b/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor.cs index ae8e1073..ad624525 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor.cs +++ b/samples/Blazor.ExampleConsumer/Components/Pages/ReadToMe.razor.cs @@ -5,16 +5,19 @@ namespace Blazor.ExampleConsumer.Components.Pages; -public sealed partial class ReadToMe : IDisposable +public sealed partial class ReadToMe( + ISpeechSynthesisService speechSynthesis, + ILocalStorageService localStorage, + ISessionStorageService sessionStorage) : IDisposable { const string PreferredVoiceKey = "preferred-voice"; const string PreferredSpeedKey = "preferred-speed"; const string TextKey = "read-to-me-text"; string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types."; - SpeechSynthesisVoice[] _voices = Array.Empty(); + SpeechSynthesisVoice[] _voices = []; readonly IList _voiceSpeeds = - Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); + [..Enumerable.Range(0, 12).Select(i => (i + 1) * .25)]; double _voiceSpeed = 1.5; string? _selectedVoice; string? _elapsedTimeMessage = null; @@ -29,34 +32,22 @@ public sealed partial class ReadToMe : IDisposable : null }; - [Inject] - public ISpeechSynthesisService SpeechSynthesis { get; set; } = null!; - - [Inject] - public ILocalStorageService LocalStorage { get; set; } = null!; - - [Inject] - public ISessionStorageService SessionStorage { get; set; } = null!; - - [Inject] - public ILogger Logger { get; set; } = null!; - protected override async Task OnInitializedAsync() { await GetVoicesAsync(); - SpeechSynthesis.OnVoicesChanged(() => GetVoicesAsync(true)); + speechSynthesis.OnVoicesChanged(() => GetVoicesAsync(true)); - if (LocalStorage.GetItem(PreferredVoiceKey) + if (localStorage.GetItem(PreferredVoiceKey) is { Length: > 0 } voice) { _selectedVoice = voice; } - if (LocalStorage.GetItem(PreferredSpeedKey) + if (localStorage.GetItem(PreferredSpeedKey) is double speed && speed > 0) { _voiceSpeed = speed; } - if (SessionStorage.GetItem(TextKey) + if (sessionStorage.GetItem(TextKey) is { Length: > 0 } text) { _text = text; @@ -65,7 +56,7 @@ protected override async Task OnInitializedAsync() async Task GetVoicesAsync(bool isFromCallback = false) { - _voices = await SpeechSynthesis.GetVoicesAsync(); + _voices = await speechSynthesis.GetVoicesAsync(); if (_voices is { } && isFromCallback) { StateHasChanged(); @@ -78,7 +69,7 @@ void OnVoiceSpeedChange(ChangeEventArgs args) => _voiceSpeed = double.TryParse(args.Value?.ToString() ?? "1.5", out var speed) ? speed : 1.5; - void Speak() => SpeechSynthesis.Speak( + void Speak() => speechSynthesis.Speak( Utterance, elapsedTime => { @@ -90,10 +81,10 @@ void Speak() => SpeechSynthesis.Speak( void IDisposable.Dispose() { - LocalStorage.SetItem(PreferredVoiceKey, _selectedVoice); - LocalStorage.SetItem(PreferredSpeedKey, _voiceSpeed); - SessionStorage.SetItem(TextKey, _text); + localStorage.SetItem(PreferredVoiceKey, _selectedVoice); + localStorage.SetItem(PreferredSpeedKey, _voiceSpeed); + sessionStorage.SetItem(TextKey, _text); - SpeechSynthesis.UnsubscribeFromVoicesChanged(); + speechSynthesis.UnsubscribeFromVoicesChanged(); } } diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/TodoList.razor.cs b/samples/Blazor.ExampleConsumer/Components/Pages/TodoList.razor.cs index b42034fa..ca9fc2a0 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/TodoList.razor.cs +++ b/samples/Blazor.ExampleConsumer/Components/Pages/TodoList.razor.cs @@ -1,28 +1,21 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. -using System.Diagnostics.CodeAnalysis; -using Blazor.ExampleConsumer.Models; -using Microsoft.AspNetCore.Components.Web; - namespace Blazor.ExampleConsumer.Components.Pages; -public sealed partial class TodoList +public sealed partial class TodoList(ILocalStorageService localStorage) { readonly Dictionary _localStorageItems = []; HashSet _todos = []; string? _todoValue; - [Inject] - public ILocalStorageService LocalStorage { get; set; } = null!; - protected override void OnInitialized() => UpdateTodoItems(); void UpdateTodoItems() { var todos = GetLocalStorageKeys() .Where(key => key.StartsWith(TodoItem.IdPrefix)) - .Select(key => LocalStorage.GetItem(key)) + .Select(key => localStorage.GetItem(key)) .Where(todo => todo is not null) .ToHashSet() ?? []; @@ -56,7 +49,7 @@ bool TryGet(string key, [NotNullWhen(true)] out T? value) { try { - value = LocalStorage.GetItem(key); + value = localStorage.GetItem(key); return value is not null; } catch @@ -69,10 +62,10 @@ bool TryGet(string key, [NotNullWhen(true)] out T? value) IEnumerable GetLocalStorageKeys() { - var length = LocalStorage.Length; + var length = localStorage.Length; for (var i = 0; i < length; ++i) { - if (LocalStorage.Key(i) is { Length: > 0 } key) + if (localStorage.Key(i) is { Length: > 0 } key) { yield return key; } @@ -84,7 +77,7 @@ void AddNewTodo() if (_todoValue is not null) { var todo = new TodoItem(_todoValue, false); - LocalStorage.SetItem(todo.Id, todo); + localStorage.SetItem(todo.Id, todo); UpdateTodoItems(); _todoValue = null; } @@ -100,14 +93,14 @@ void OnKeyUp(KeyboardEventArgs args) void Delete(TodoItem todo) { - LocalStorage.RemoveItem(todo.Id); + localStorage.RemoveItem(todo.Id); _todos.RemoveWhere(t => t.Id == todo.Id); _localStorageItems.Remove(todo.Id); } void ClearAll() { - LocalStorage.Clear(); + localStorage.Clear(); _todos.Clear(); _localStorageItems.Clear(); } diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor b/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor index 41fe02c2..dc00573d 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor +++ b/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor @@ -1,7 +1,5 @@ @page "/track" -@inject IGeolocationService Geolocation - Track

Geolocation diff --git a/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor.cs b/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor.cs index 90f88e24..77b6c860 100644 --- a/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor.cs +++ b/samples/Blazor.ExampleConsumer/Components/Pages/Track.razor.cs @@ -3,7 +3,7 @@ namespace Blazor.ExampleConsumer.Components.Pages; -public partial class Track : IDisposable +public sealed partial class Track(IGeolocationService geolocation) : IDisposable { readonly JsonSerializerOptions _opts = new() { @@ -24,14 +24,14 @@ public partial class Track : IDisposable bool _isLoading = true; protected override void OnInitialized() => - _watchId = Geolocation.WatchPosition( + _watchId = geolocation.WatchPosition( component: this, - onSuccessCallbackMethodName: nameof(OnPositionRecieved), + onSuccessCallbackMethodName: nameof(OnPositionReceived), onErrorCallbackMethodName: nameof(OnPositionError), options: _options); [JSInvokable] - public void OnPositionRecieved(GeolocationPosition position) + public void OnPositionReceived(GeolocationPosition position) { _isLoading = false; _position = position; @@ -46,5 +46,5 @@ public void OnPositionError(GeolocationPositionError positionError) StateHasChanged(); } - public void Dispose() => Geolocation.ClearWatch(_watchId); + public void Dispose() => geolocation.ClearWatch(_watchId); } diff --git a/samples/Blazor.ExampleConsumer/GlobalUsings.cs b/samples/Blazor.ExampleConsumer/GlobalUsings.cs index 90801335..435318b3 100644 --- a/samples/Blazor.ExampleConsumer/GlobalUsings.cs +++ b/samples/Blazor.ExampleConsumer/GlobalUsings.cs @@ -1,8 +1,13 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. +global using System.Diagnostics.CodeAnalysis; global using System.Text.Json; global using System.Text.Json.Serialization; +global using Blazor.ExampleConsumer.Components; +global using Blazor.ExampleConsumer.Models; global using Microsoft.AspNetCore.Components; +global using Microsoft.AspNetCore.Components.Web; +global using Microsoft.AspNetCore.Components.WebAssembly.Hosting; global using Microsoft.JSInterop; global using static System.Globalization.CultureInfo; diff --git a/samples/Blazor.ExampleConsumer/Program.cs b/samples/Blazor.ExampleConsumer/Program.cs index e1e070fa..9194b4e3 100644 --- a/samples/Blazor.ExampleConsumer/Program.cs +++ b/samples/Blazor.ExampleConsumer/Program.cs @@ -1,10 +1,6 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. -using Blazor.ExampleConsumer.Components; -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"); diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs index 4e5ff215..67ded38c 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs +++ b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs @@ -10,7 +10,7 @@ public sealed partial class ReadToMe : IAsyncDisposable const string TextKey = "read-to-me-text"; string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types."; - SpeechSynthesisVoice[] _voices = Array.Empty(); + SpeechSynthesisVoice[] _voices = []; readonly IList _voiceSpeeds = Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); double _voiceSpeed = 1.5; diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Track.razor b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor index 920482d4..67c2b280 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/Track.razor +++ b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor @@ -1,7 +1,5 @@ @page "/track" -@inject IGeolocationService Geolocation - Track

Geolocation diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs index 91f921e5..15b6b71f 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs +++ b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs @@ -3,7 +3,7 @@ namespace BlazorServer.ExampleConsumer.Pages; -public sealed partial class Track : IAsyncDisposable +public sealed partial class Track(IGeolocationService geolocation) : IAsyncDisposable { readonly JsonSerializerOptions _opts = new() { @@ -30,15 +30,15 @@ protected override async Task OnAfterRenderAsync(bool firstRender) return; } - _watchId = await Geolocation.WatchPositionAsync( + _watchId = await geolocation.WatchPositionAsync( component: this, - onSuccessCallbackMethodName: nameof(OnPositionRecieved), + onSuccessCallbackMethodName: nameof(OnPositionReceived), onErrorCallbackMethodName: nameof(OnPositionError), options: _options); } [JSInvokable] - public void OnPositionRecieved(GeolocationPosition position) + public void OnPositionReceived(GeolocationPosition position) { _isLoading = false; _position = position; @@ -54,5 +54,5 @@ public void OnPositionError(GeolocationPositionError positionError) } public async ValueTask DisposeAsync() => - await Geolocation.ClearWatchAsync(_watchId); + await geolocation.ClearWatchAsync(_watchId); } diff --git a/src/Blazor.LocalStorage.WebAssembly/ILocalStorageService.cs b/src/Blazor.LocalStorage.WebAssembly/ILocalStorageService.cs index aebd0850..22e4c731 100644 --- a/src/Blazor.LocalStorage.WebAssembly/ILocalStorageService.cs +++ b/src/Blazor.LocalStorage.WebAssembly/ILocalStorageService.cs @@ -7,11 +7,11 @@ namespace Microsoft.JSInterop; TypeName = "Storage", Implementation = "window.localStorage", Url = "https://developer.mozilla.org/docs/Web/API/Window/localStorage", - GenericMethodDescriptors = new[] - { + GenericMethodDescriptors = + [ "getItem", "setItem:value" - })] + ])] public partial interface ILocalStorageService { } \ No newline at end of file diff --git a/src/Blazor.LocalStorage/ILocalStorageService.cs b/src/Blazor.LocalStorage/ILocalStorageService.cs index 06c1d9ff..08bdd239 100644 --- a/src/Blazor.LocalStorage/ILocalStorageService.cs +++ b/src/Blazor.LocalStorage/ILocalStorageService.cs @@ -9,10 +9,10 @@ namespace Microsoft.JSInterop; HostingModel = BlazorHostingModel.Server, OnlyGeneratePureJS = true, Url = "https://developer.mozilla.org/docs/Web/API/Window/localStorage", - TypeDeclarationSources = new[] - { + TypeDeclarationSources = + [ "https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.dom.d.ts" - })] + ])] public partial interface ILocalStorageService { } \ No newline at end of file diff --git a/src/Blazor.SessionStorage.WebAssembly/ISessionStorageService.cs b/src/Blazor.SessionStorage.WebAssembly/ISessionStorageService.cs index 09bf4475..7da2f96d 100644 --- a/src/Blazor.SessionStorage.WebAssembly/ISessionStorageService.cs +++ b/src/Blazor.SessionStorage.WebAssembly/ISessionStorageService.cs @@ -8,11 +8,11 @@ namespace Microsoft.JSInterop; TypeName = "Storage", Implementation = "window.sessionStorage", Url = "https://developer.mozilla.org/docs/Web/API/Window/sessionStorage", - GenericMethodDescriptors = new[] - { + GenericMethodDescriptors = + [ "getItem", "setItem:value" - })] + ])] public partial interface ISessionStorageService { } \ No newline at end of file diff --git a/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs b/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs index aed6ab50..7b8cce27 100644 --- a/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs +++ b/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs @@ -17,8 +17,6 @@ internal sealed class SourceBuilder private readonly bool _isService; private Indentation _indentation = new(0); - private string? _implementationName; - private string? _interfaceName; /// /// Gets or sets the set of fields used by the source builder. @@ -38,16 +36,18 @@ internal sealed class SourceBuilder /// /// Gets the implementation name, which is lazily initialized the first time it is accessed. /// - internal string ImplementationName => _implementationName ??= + internal string ImplementationName => field ??= _options.Implementation.ToImplementationName(_isService); /// /// Gets the interface name, which is lazily initialized the first time it is accessed. /// - internal string InterfaceName => _interfaceName ??= + internal string InterfaceName => field ??= _options.Implementation.ToInterfaceName(_isService); +#pragma warning disable CS9264 // Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes. internal SourceBuilder(GeneratorOptions options, bool isService = true) +#pragma warning restore CS9264 // Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes. { _options = options; _isService = isService; diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs b/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs index b33001b8..1b68f42d 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpObject.cs @@ -32,7 +32,7 @@ in this.GetAllDependencies() } return result.Select(kvp => (kvp.Key, kvp.Value)) - .Concat(new[] { (TypeName, Object: this) }) + .Concat([(TypeName, this)]) .ToImmutableHashSet(); } } diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs b/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs index 2cad3a0b..fb167e4d 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs @@ -184,7 +184,7 @@ internal string ToInterfaceString( } // Properties - foreach (var (index, property) in (Properties ?? new List()).Select()) + foreach (var (index, property) in (Properties ?? []).Select()) { if (index.IsFirst) builder.AppendLine(); if (property.IsIndexer) continue; @@ -230,7 +230,7 @@ internal string ToImplementationString( builder.AppendConditionalDelegateCallbackMethods(Methods); // Methods - foreach (var (index, method) in (Methods ?? new List()).Select()) + foreach (var (index, method) in (Methods ?? []).Select()) { var details = MethodBuilderDetails.Create(method, options); builder.ResetIndentiationTo(methodLevel); @@ -469,7 +469,7 @@ internal string ToImplementationString( } // Properties - foreach (var (index, property) in (Properties ?? new List()).Select()) + foreach (var (index, property) in (Properties ?? []).Select()) { if (index.IsFirst) builder.AppendLine(); if (property.IsIndexer) continue; @@ -547,7 +547,7 @@ static string TryFormatCSharpSourceText(string csharpSourceText) } catch (Exception ex) { - Console.WriteLine(ex); + Trace.WriteLine(ex); return csharpSourceText; } diff --git a/src/Blazor.SourceGenerators/Extensions/CSharpDependencyGraphExtensions.cs b/src/Blazor.SourceGenerators/Extensions/CSharpDependencyGraphExtensions.cs index 8e4d2384..1e9e80bf 100644 --- a/src/Blazor.SourceGenerators/Extensions/CSharpDependencyGraphExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/CSharpDependencyGraphExtensions.cs @@ -12,7 +12,7 @@ internal static class CSharpDependencyGraphExtensions foreach (var kvp in dependencyGraphObject.DependentTypes?.Flatten( obj => obj.Value.DependentTypes) - ?? Enumerable.Empty>()) + ?? []) { map[kvp.Key] = kvp.Value; } diff --git a/src/Blazor.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/Blazor.SourceGenerators/Extensions/EnumerableExtensions.cs index f6a80c22..2ec2f853 100644 --- a/src/Blazor.SourceGenerators/Extensions/EnumerableExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -10,6 +10,6 @@ internal static IEnumerable Flatten( Func> childSelector) => source?.SelectMany( child => childSelector(child).Flatten(childSelector)) - .Concat(source ?? Enumerable.Empty()) - ?? Enumerable.Empty(); + .Concat(source ?? []) + ?? []; } diff --git a/src/Blazor.SourceGenerators/Extensions/GeneratorExecutionContextExtensions.cs b/src/Blazor.SourceGenerators/Extensions/SourceProductionContextExtensions.cs similarity index 77% rename from src/Blazor.SourceGenerators/Extensions/GeneratorExecutionContextExtensions.cs rename to src/Blazor.SourceGenerators/Extensions/SourceProductionContextExtensions.cs index b289efcd..1efad486 100644 --- a/src/Blazor.SourceGenerators/Extensions/GeneratorExecutionContextExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/SourceProductionContextExtensions.cs @@ -3,10 +3,10 @@ namespace Blazor.SourceGenerators.Extensions; -internal static class GeneratorExecutionContextExtensions +internal static class SourceProductionContextExtensions { - internal static GeneratorExecutionContext AddDependentTypesSource( - this GeneratorExecutionContext context, + internal static SourceProductionContext AddDependentTypesSource( + this SourceProductionContext context, CSharpTopLevelObject topLevelObject) { foreach (var (type, dependentObj) in @@ -21,8 +21,8 @@ internal static GeneratorExecutionContext AddDependentTypesSource( return context; } - internal static GeneratorExecutionContext AddInterfaceSource( - this GeneratorExecutionContext context, + internal static SourceProductionContext AddInterfaceSource( + this SourceProductionContext context, CSharpTopLevelObject topLevelObject, string @interface, GeneratorOptions options, @@ -39,8 +39,8 @@ internal static GeneratorExecutionContext AddInterfaceSource( return context; } - internal static GeneratorExecutionContext AddImplementationSource( - this GeneratorExecutionContext context, + internal static SourceProductionContext AddImplementationSource( + this SourceProductionContext context, CSharpTopLevelObject topLevelObject, string implementation, GeneratorOptions options, @@ -57,8 +57,8 @@ internal static GeneratorExecutionContext AddImplementationSource( return context; } - internal static GeneratorExecutionContext AddDependencyInjectionExtensionsSource( - this GeneratorExecutionContext context, + internal static SourceProductionContext AddDependencyInjectionExtensionsSource( + this SourceProductionContext context, CSharpTopLevelObject topLevelObject, string implementation, GeneratorOptions options) diff --git a/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs b/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs index c72829b8..f0f36657 100644 --- a/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs +++ b/src/Blazor.SourceGenerators/JavaScriptInteropGenerator.cs @@ -1,10 +1,13 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. +using Microsoft.CodeAnalysis; +using System; + namespace Blazor.SourceGenerators; -[Generator] -internal sealed partial class JavaScriptInteropGenerator : ISourceGenerator +[Generator(LanguageNames.CSharp)] +internal sealed partial class JavaScriptInteropGenerator : IIncrementalGenerator { private readonly HashSet<(string FileName, string SourceCode)> _sourceCodeToAdd = [ @@ -14,7 +17,7 @@ internal sealed partial class JavaScriptInteropGenerator : ISourceGenerator (nameof(JSAutoGenericInteropAttribute).ToGeneratedFileName(), JSAutoGenericInteropAttribute), ]; - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { #if DEBUG if (!System.Diagnostics.Debugger.IsAttached) @@ -23,45 +26,121 @@ public void Initialize(GeneratorInitializationContext context) } #endif - // Register a syntax receiver that will be created for each generation pass - context.RegisterForSyntaxNotifications( - JavaScriptInteropSyntaxContextReceiver.Create); + // Add constant source files. + context.RegisterPostInitializationOutput(c => + { + // Add source from text. + foreach (var (fileName, sourceCode) in _sourceCodeToAdd) + { + c.AddSource(fileName, + SourceText.From(sourceCode, Encoding.UTF8)); + } + }); + + var provider = context.SyntaxProvider.CreateSyntaxProvider( + predicate: static (s, _) => IsSyntaxTargetForGeneration(s), + transform: (ctx, _) => GetSemanticTargetForGeneration(ctx)) + .Where(static m => m is not null); + + var compilationAndInterfaces = + context.CompilationProvider.Combine(provider.Collect()); + + context.RegisterSourceOutput( + compilationAndInterfaces, + (ctx, tuple) => Execute(ctx, tuple.Left, tuple.Right)); + } - public void Execute(GeneratorExecutionContext context) + private static bool IsSyntaxTargetForGeneration(SyntaxNode node) { - // Add source from text. - foreach (var (fileName, sourceCode) in _sourceCodeToAdd) + if (node is InterfaceDeclarationSyntax interfaceDeclaration && + interfaceDeclaration.AttributeLists.Count > 0) { - context.AddSource(fileName, - SourceText.From(sourceCode, Encoding.UTF8)); + foreach (var attributeListSyntax in interfaceDeclaration.AttributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var name = attributeSyntax.Name.ToString(); + + var isAutoInterop = + nameof(JSAutoInteropAttribute).Contains(name); + var isAutoGenericInterop = + nameof(JSAutoGenericInteropAttribute).Contains(name); + + return isAutoInterop || isAutoGenericInterop; + } + } + } + + return false; + } + + private static InterfaceDeclarationDetails? GetSemanticTargetForGeneration(GeneratorSyntaxContext context) + { + var interfaceDeclaration = (InterfaceDeclarationSyntax)context.Node; + + foreach (var attributeListSyntax in interfaceDeclaration.AttributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var name = attributeSyntax.Name.ToString(); + + var isAutoInterop = + nameof(JSAutoInteropAttribute).Contains(name); + var isAutoGenericInterop = + nameof(JSAutoGenericInteropAttribute).Contains(name); + + if (isAutoInterop || isAutoGenericInterop) + { + return new( + Options: attributeSyntax.GetGeneratorOptions(isAutoGenericInterop), + InterfaceDeclaration: interfaceDeclaration, + InteropAttribute: attributeSyntax); + } + } } + return null; + } - if (context.SyntaxContextReceiver is not JavaScriptInteropSyntaxContextReceiver receiver) + private static void Execute( + SourceProductionContext context, + Compilation compilation, + ImmutableArray interfaceDeclarations) + { + // Not sure if/when this will be needed. + _ = compilation; + + if (interfaceDeclarations.IsDefaultOrEmpty) { return; } - foreach (var (options, classDeclaration, attribute) in receiver.InterfaceDeclarations) + foreach (var tuple in interfaceDeclarations.Distinct()) { - if (options is null || IsDiagnosticError(options, context, attribute)) + if (tuple is null) { continue; } - var isPartial = classDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)); - if (!isPartial) + var (options, interfaceDeclaration, attribute) = tuple; + + if (options is null || IsDiagnosticError(options, context, attribute)) { continue; } - var model = context.Compilation.GetSemanticModel(classDeclaration.SyntaxTree); - var symbol = model.GetDeclaredSymbol(classDeclaration); - if (symbol is not ITypeSymbol typeSymbol || typeSymbol.IsStatic) + var isPartial = interfaceDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)); + if (!isPartial) { continue; } + var @namespace = interfaceDeclaration.Ancestors() + .OfType() + .FirstOrDefault(); + + var containingNamespace = @namespace?.Name.ToString(); + foreach (var parser in options.Parsers) { var result = parser.ParseTargetType(options.TypeName!); @@ -69,9 +148,9 @@ public void Execute(GeneratorExecutionContext context) result.Value is not null) { var namespaceString = - (typeSymbol.ContainingNamespace.ToDisplayString(), classDeclaration.Parent) switch + (containingNamespace, interfaceDeclaration.Parent) switch { - (string { Length: > 0 } containingNamespace, _) => containingNamespace, + (string { Length: > 0 } ns, _) => ns, (_, BaseNamespaceDeclarationSyntax namespaceDeclaration) => namespaceDeclaration.Name.ToString(), _ => null }; @@ -81,6 +160,7 @@ public void Execute(GeneratorExecutionContext context) options.Implementation.ToImplementationName(); var topLevelObject = result.Value; + context.AddDependentTypesSource(topLevelObject) .AddInterfaceSource(topLevelObject, @interface, options, namespaceString) .AddImplementationSource(topLevelObject, implementation, options, namespaceString) @@ -90,7 +170,7 @@ public void Execute(GeneratorExecutionContext context) } } - static bool IsDiagnosticError(GeneratorOptions options, GeneratorExecutionContext context, AttributeSyntax attribute) + static bool IsDiagnosticError(GeneratorOptions options, SourceProductionContext context, AttributeSyntax attribute) { if (options.TypeName is null) { @@ -112,17 +192,17 @@ static bool IsDiagnosticError(GeneratorOptions options, GeneratorExecutionContex 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; - } + //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 deleted file mode 100644 index 60f44a2f..00000000 --- a/src/Blazor.SourceGenerators/JavaScriptInteropSyntaxContextReceiver.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace Blazor.SourceGenerators; - -internal class JavaScriptInteropSyntaxContextReceiver : ISyntaxContextReceiver -{ - internal static ISyntaxContextReceiver Create() => new JavaScriptInteropSyntaxContextReceiver(); - - public HashSet InterfaceDeclarations { get; } = []; - - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) - { - if (context.Node is InterfaceDeclarationSyntax interfaceDeclaration && - interfaceDeclaration.AttributeLists.Count > 0) - { - foreach (var attributeListSyntax in interfaceDeclaration.AttributeLists) - { - foreach (var attributeSyntax in attributeListSyntax.Attributes) - { - var name = attributeSyntax.Name.ToString(); - - var isAutoInterop = - nameof(JSAutoInteropAttribute).Contains(name); - var isAutoGenericInterop = - nameof(JSAutoGenericInteropAttribute).Contains(name); - - if (isAutoInterop || isAutoGenericInterop) - { - InterfaceDeclarations.Add( - new( - Options: attributeSyntax.GetGeneratorOptions(isAutoGenericInterop), - InterfaceDeclaration: interfaceDeclaration, - InteropAttribute: attributeSyntax)); - } - } - } - } - } -} diff --git a/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs b/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs index e658665b..ae14dcc4 100644 --- a/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs +++ b/src/Blazor.SourceGenerators/Options/GeneratorOptions.cs @@ -4,7 +4,7 @@ /// /// The options used (and parsed from the JSAutoInteropAttribute) to source-generate JavaScript interop. /// -/// Indicates wether the generator supports generics. +/// Indicates whether the generator supports generics. /// The type name that corresponds to the lib.dom.d.ts interface. For example, "Geolocation" /// The path from the window object. For example, "window.navigator.geolocation" (or "navigator.geolocation") /// Whether to generate only pure JavaScript functions that do not require callbacks. For example, Geolocation.clearWatch is consider pure, but Geolocation.watchPosition is not. @@ -28,29 +28,29 @@ internal sealed record GeneratorOptions( string[]? TypeDeclarationSources = null, bool IsWebAssembly = true) { - ISet? _parsers; - /// /// Get instance maps its /// into a set of parsers. /// When is null, or empty, - /// the default lib.dom.d.ts parser is used. + /// the default lib.dom.d.ts parser is used. /// +#pragma warning disable CS9264 // Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes. internal ISet Parsers +#pragma warning restore CS9264 // Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes. { get { - _parsers ??= new HashSet(); + field ??= new HashSet(); foreach (var source in TypeDeclarationSources?.Select(TypeDeclarationReader.Factory) ?.Select(reader => new TypeDeclarationParser(reader)) - ?? new[] { TypeDeclarationParser.Default }) + ?? [TypeDeclarationParser.Default]) { - _parsers.Add(source); + field.Add(source); } - return _parsers; + return field; } } } diff --git a/src/Blazor.SourceGenerators/Parsers/TypeDeclarationParser.Interfaces.cs b/src/Blazor.SourceGenerators/Parsers/TypeDeclarationParser.Interfaces.cs index 9f8f01b7..a86d8ea2 100644 --- a/src/Blazor.SourceGenerators/Parsers/TypeDeclarationParser.Interfaces.cs +++ b/src/Blazor.SourceGenerators/Parsers/TypeDeclarationParser.Interfaces.cs @@ -9,7 +9,7 @@ internal sealed partial class TypeDeclarationParser { CSharpObject? cSharpObject = null; - var lineTokens = typeScriptTypeDeclaration.Split(new[] { '\n' }); + var lineTokens = typeScriptTypeDeclaration.Split(['\n']); foreach (var (index, segment) in lineTokens.Select((s, i) => (i, s))) { if (index == 0) @@ -118,7 +118,7 @@ internal sealed partial class TypeDeclarationParser { CSharpTopLevelObject? topLevelObject = null; - var lineTokens = typeScriptTypeDeclaration.Split(new[] { '\n' }); + var lineTokens = typeScriptTypeDeclaration.Split(['\n']); foreach (var (index, segment) in lineTokens.Select((s, i) => (i, s))) { if (index == 0) @@ -282,7 +282,7 @@ private CSharpMethod ToMethod( { CSharpAction? cSharpAction = null; - var lineTokens = typeScriptTypeDeclaration.Split(new[] { '\n' }); + var lineTokens = typeScriptTypeDeclaration.Split(['\n']); foreach (var (index, segment) in lineTokens.Select((s, i) => (i, s))) { if (index == 0) @@ -364,7 +364,7 @@ internal static string CleanseReturnType(string returnType) // Example input: // "(someCallback: CallbackType, someId?: number | null)" var trimmedParameters = parametersString.Replace("(", "").Replace(")", ""); - var parameterLineTokenizer = trimmedParameters.Split(new[] { ':', ',', }); + var parameterLineTokenizer = trimmedParameters.Split([':', ',',]); JavaScriptMethod? javaScriptMethod = new(rawName); foreach (var parameterPair in parameterLineTokenizer.Where(t => t.Length > 0).Chunk(2)) diff --git a/src/Blazor.SourceGenerators/Readers/TypeDeclarationReader.cs b/src/Blazor.SourceGenerators/Readers/TypeDeclarationReader.cs index 0b3002bc..fd393e5d 100644 --- a/src/Blazor.SourceGenerators/Readers/TypeDeclarationReader.cs +++ b/src/Blazor.SourceGenerators/Readers/TypeDeclarationReader.cs @@ -7,16 +7,15 @@ internal sealed partial class TypeDeclarationReader { readonly Lazy _typeDeclarationText; - IDictionary? _typeDeclarationMap; - IDictionary? _typeAliasMap; - private IDictionary TypeDeclarationMap => - _typeDeclarationMap ??= ReadTypeDeclarationMap(_typeDeclarationText.Value); + field ??= ReadTypeDeclarationMap(_typeDeclarationText.Value); private IDictionary TypeAliasMap => - _typeAliasMap ??= ReadTypeAliasMap(_typeDeclarationText.Value); + field ??= ReadTypeAliasMap(_typeDeclarationText.Value); +#pragma warning disable CS9264 // Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes. private TypeDeclarationReader() +#pragma warning restore CS9264 // Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes. { _typeDeclarationText = new Lazy( valueFactory: () => GetEmbeddedResourceText()); @@ -46,7 +45,7 @@ IDictionary ReadTypeDeclarationMap(string typeDeclarations) } catch (Exception ex) { - Console.WriteLine($"Error initializing lib dom parser. {ex}"); + Trace.WriteLine($"Error initializing lib dom parser. {ex}"); } return map; @@ -76,7 +75,7 @@ IDictionary ReadTypeAliasMap(string typeDeclarations) } catch (Exception ex) { - Console.WriteLine($"Error initializing lib dom parser. {ex}"); + Trace.WriteLine($"Error initializing lib dom parser. {ex}"); } return map; diff --git a/src/Blazor.SpeechSynthesis.WebAssembly/Extensions/SpeechSynthesisServiceExtensions.cs b/src/Blazor.SpeechSynthesis.WebAssembly/Extensions/SpeechSynthesisServiceExtensions.cs index ec8c5bc1..9262cc2c 100644 --- a/src/Blazor.SpeechSynthesis.WebAssembly/Extensions/SpeechSynthesisServiceExtensions.cs +++ b/src/Blazor.SpeechSynthesis.WebAssembly/Extensions/SpeechSynthesisServiceExtensions.cs @@ -27,7 +27,7 @@ public static void Speak( SpeechSynthesisUtterance utterance, Action onUtteranceEnded) { - if (service is SpeechSynthesisService and { _javaScript: { } } svc) + if (service is SpeechSynthesisService and { _javaScript: { } } _) { s_utteranceEndedCallbackRegistry[utterance.Text] = onUtteranceEnded; service.Speak(utterance); diff --git a/src/Blazor.SpeechSynthesis.WebAssembly/ISpeechSynthesisService.cs b/src/Blazor.SpeechSynthesis.WebAssembly/ISpeechSynthesisService.cs index d403c855..72692586 100644 --- a/src/Blazor.SpeechSynthesis.WebAssembly/ISpeechSynthesisService.cs +++ b/src/Blazor.SpeechSynthesis.WebAssembly/ISpeechSynthesisService.cs @@ -8,10 +8,10 @@ namespace Microsoft.JSInterop; Implementation = "window.speechSynthesis", Url = "https://developer.mozilla.org/docs/Web/API/SpeechSynthesis", OnlyGeneratePureJS = true, - PureJavaScriptOverrides = new[] - { + PureJavaScriptOverrides = + [ "getVoices" - })] + ])] public partial interface ISpeechSynthesisService { } \ No newline at end of file diff --git a/src/Blazor.SpeechSynthesis/ISpeechSynthesisService.cs b/src/Blazor.SpeechSynthesis/ISpeechSynthesisService.cs index 38a2e099..5e84d448 100644 --- a/src/Blazor.SpeechSynthesis/ISpeechSynthesisService.cs +++ b/src/Blazor.SpeechSynthesis/ISpeechSynthesisService.cs @@ -9,10 +9,10 @@ namespace Microsoft.JSInterop; HostingModel = BlazorHostingModel.Server, Url = "https://developer.mozilla.org/docs/Web/API/SpeechSynthesis", OnlyGeneratePureJS = true, - PureJavaScriptOverrides = new[] - { + PureJavaScriptOverrides = + [ "getVoices" - })] + ])] public partial interface ISpeechSynthesisService { } \ No newline at end of file diff --git a/tests/Blazor.SourceGenerators.Tests/GeneratorBaseUnitTests.cs b/tests/Blazor.SourceGenerators.Tests/GeneratorBaseUnitTests.cs index 3d6d4bd8..b4451cce 100644 --- a/tests/Blazor.SourceGenerators.Tests/GeneratorBaseUnitTests.cs +++ b/tests/Blazor.SourceGenerators.Tests/GeneratorBaseUnitTests.cs @@ -13,10 +13,10 @@ namespace Blazor.SourceGenerators.Tests; /// public abstract class GeneratorBaseUnitTests { - public abstract IEnumerable SourceGenerators { get; } + public abstract IIncrementalGenerator[] SourceGenerators { get; } public static Compilation GetCompilation(string sourceCode) => - GetCompilation(new[] { CSharpSyntaxTree.ParseText(sourceCode) }); + GetCompilation([CSharpSyntaxTree.ParseText(sourceCode)]); public static Compilation GetCompilation(IEnumerable syntaxTrees) { @@ -26,7 +26,7 @@ public static Compilation GetCompilation(IEnumerable syntaxTrees) assemblyName: "TestAssembly", syntaxTrees: syntaxTrees, options: options) - .WithReferenceAssemblies(ReferenceAssemblyKind.Net60); + .WithReferenceAssemblies(ReferenceAssemblyKind.Net80); return compilation; } @@ -34,7 +34,7 @@ public static Compilation GetCompilation(IEnumerable syntaxTrees) public GeneratorDriverRunResult GetRunResult(string sourceCode) { var compilation = GetCompilation(sourceCode); - var driver = CSharpGeneratorDriver.Create(SourceGenerators); + var driver = CSharpGeneratorDriver.Create(incrementalGenerators: SourceGenerators); return driver .RunGenerators(compilation) .GetRunResult(); diff --git a/tests/Blazor.SourceGenerators.Tests/JavaScriptInteropTests.cs b/tests/Blazor.SourceGenerators.Tests/JavaScriptInteropTests.cs index f5a0cabb..b5c123a6 100644 --- a/tests/Blazor.SourceGenerators.Tests/JavaScriptInteropTests.cs +++ b/tests/Blazor.SourceGenerators.Tests/JavaScriptInteropTests.cs @@ -8,8 +8,8 @@ namespace Blazor.SourceGenerators.Tests; public class JavaScriptInteropTests : GeneratorBaseUnitTests { - public override IEnumerable SourceGenerators => - new[] { new JavaScriptInteropGenerator() }; + public override IIncrementalGenerator[] SourceGenerators => + [new JavaScriptInteropGenerator()]; public SyntaxTree GetGeneratedTree(string sourceCode) {