From 0607cb6bca2b9e36a19a4aa1ff43f355a148ee1f Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:26:48 +0300 Subject: [PATCH 01/75] bump --- src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs | 3 +++ src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs | 3 +++ src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs | 6 ++++++ src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs | 6 ++++++ src/cs/Directory.Build.props | 2 +- 5 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs new file mode 100644 index 00000000..b0d5b187 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs @@ -0,0 +1,3 @@ +namespace Bootsharp.Publish.Test; + +public class ExportClassTest : EmitTest { } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs new file mode 100644 index 00000000..425f19bf --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs @@ -0,0 +1,3 @@ +namespace Bootsharp.Publish.Test; + +public class ImportClassTest : EmitTest { } diff --git a/src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs new file mode 100644 index 00000000..b1df3501 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish; + +internal sealed class ExportClassGenerator +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs new file mode 100644 index 00000000..c43f0971 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish; + +internal sealed class ImportClassGenerator +{ + +} diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 93cd2b4f..27f9164b 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.1.3 + 0.2.0-alpha.0 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From d80b0e6c23fdddc3caaf6ebb5df1fcdcf6ac09f8 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:36:05 +0300 Subject: [PATCH 02/75] etc --- src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs b/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs index d95a1e38..d7f0586f 100644 --- a/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs +++ b/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs @@ -1,9 +1,7 @@ -using System.Diagnostics.CodeAnalysis; using System.Text; namespace Bootsharp.Publish; -[ExcludeFromCodeCoverage(Justification = "How to merge coverage from multiple OS?")] internal sealed class InternalPatcher (string dotnet, string runtime, string native) { private const string url = From df9a344de75dcb4ca6279624c6de55fe64fc7f08 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:46:24 +0300 Subject: [PATCH 03/75] iteration --- .../Bootsharp.Publish.Test/Emit/EmitTest.cs | 19 +++++++++-- .../Bootsharp.Publish.Test/Emit/EventTest.cs | 6 ++++ .../Emit/ExportClassTest.cs | 3 -- .../Bootsharp.Publish.Test/Emit/ExportTest.cs | 6 ++++ .../Emit/FunctionTest.cs | 6 ++++ .../Emit/ImportClassTest.cs | 3 -- .../Bootsharp.Publish.Test/Emit/ImportTest.cs | 3 ++ .../Emit/InvokableTest.cs | 6 ++++ .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 32 +++++++++++++++---- .../Bootsharp.Publish/Emit/EventGenerator.cs | 10 ++++++ .../Emit/ExportClassGenerator.cs | 6 ---- .../Bootsharp.Publish/Emit/ExportGenerator.cs | 10 ++++++ .../Emit/FunctionGenerator.cs | 10 ++++++ .../Emit/ImportClassGenerator.cs | 6 ---- .../Bootsharp.Publish/Emit/ImportGenerator.cs | 10 ++++++ .../Emit/InteropExportGenerator.cs | 4 +++ .../Emit/InteropImportGenerator.cs | 5 +++ .../Emit/InvokableGenerator.cs | 12 +++++++ .../Emit/SerializerGenerator.cs | 5 +++ src/cs/Bootsharp/Build/Bootsharp.targets | 24 ++++++++++++-- 20 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/EventGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs index 5b3003b4..6213b6d0 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs @@ -2,10 +2,20 @@ public class EmitTest : TaskTest { + protected string GeneratedInvokables => ReadProjectFile(invokablesPath); + protected string GeneratedFunctions => ReadProjectFile(functionsPath); + protected string GeneratedEvents => ReadProjectFile(eventsPath); + protected string GeneratedExports => ReadProjectFile(exportsPath); + protected string GeneratedImports => ReadProjectFile(importsPath); protected string GeneratedInteropExports => ReadProjectFile(interopExportsPath); protected string GeneratedInteropImports => ReadProjectFile(interopImportsPath); protected string GeneratedSerializer => ReadProjectFile(serializerPath); + private string invokablesPath => $"{Project.Root}/Invokables.g.cs"; + private string functionsPath => $"{Project.Root}/Functions.g.cs"; + private string eventsPath => $"{Project.Root}/Events.g.cs"; + private string exportsPath => $"{Project.Root}/Exports.g.cs"; + private string importsPath => $"{Project.Root}/Imports.g.cs"; private string interopExportsPath => $"{Project.Root}/InteropExports.g.cs"; private string interopImportsPath => $"{Project.Root}/InteropImports.g.cs"; private string serializerPath => $"{Project.Root}/SerializerContext.g.cs"; @@ -15,8 +25,13 @@ public class EmitTest : TaskTest private BootsharpEmit CreateTask () => new() { InspectedDirectory = Project.Root, EntryAssemblyName = LastAddedAssemblyName ?? "System.Runtime.dll", - ExportsFilePath = interopExportsPath, - ImportsFilePath = interopImportsPath, + InvokablesFilePath = invokablesPath, + FunctionsFilePath = functionsPath, + EventsFilePath = eventsPath, + ExportsFilePath = exportsPath, + ImportsFilePath = importsPath, + InteropExportsFilePath = interopExportsPath, + InteropImportsFilePath = interopImportsPath, SerializerFilePath = serializerPath, BuildEngine = Engine }; diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs new file mode 100644 index 00000000..d2a29351 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class EventTest +{ + +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs deleted file mode 100644 index b0d5b187..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/ExportClassTest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class ExportClassTest : EmitTest { } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs new file mode 100644 index 00000000..98dfda80 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class ExportTest : EmitTest +{ + +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs new file mode 100644 index 00000000..f3deb709 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class FunctionTest +{ + +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs deleted file mode 100644 index 425f19bf..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/ImportClassTest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class ImportClassTest : EmitTest { } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs new file mode 100644 index 00000000..20f8c63d --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs @@ -0,0 +1,3 @@ +namespace Bootsharp.Publish.Test; + +public class ImportTest : EmitTest { } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs new file mode 100644 index 00000000..5e64443b --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class InvokableTest +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 1c953d3c..167e7bea 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -9,16 +9,26 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { [Required] public required string InspectedDirectory { get; set; } [Required] public required string EntryAssemblyName { get; set; } + [Required] public required string InvokablesFilePath { get; set; } + [Required] public required string FunctionsFilePath { get; set; } + [Required] public required string EventsFilePath { get; set; } [Required] public required string ExportsFilePath { get; set; } [Required] public required string ImportsFilePath { get; set; } + [Required] public required string InteropExportsFilePath { get; set; } + [Required] public required string InteropImportsFilePath { get; set; } [Required] public required string SerializerFilePath { get; set; } public override bool Execute () { var spaceBuilder = CreateNamespaceBuilder(); using var inspection = InspectAssemblies(spaceBuilder); + GenerateInvokables(inspection); + GenerateFunctions(inspection); + GenerateEvents(inspection); GenerateExports(inspection); GenerateImports(inspection); + GenerateInteropExports(inspection); + GenerateInteropImports(inspection); GenerateSerializer(inspection); return true; } @@ -38,20 +48,30 @@ private AssemblyInspection InspectAssemblies (NamespaceBuilder spaceBuilder) return inspection; } - private void GenerateExports (AssemblyInspection inspection) + private void GenerateInvokables (AssemblyInspection inspection) { } + + private void GenerateFunctions (AssemblyInspection inspection) { } + + private void GenerateEvents (AssemblyInspection inspection) { } + + private void GenerateExports (AssemblyInspection inspection) { } + + private void GenerateImports (AssemblyInspection inspection) { } + + private void GenerateInteropExports (AssemblyInspection inspection) { var generator = new InteropExportGenerator(); var content = generator.Generate(inspection); - Directory.CreateDirectory(Path.GetDirectoryName(ExportsFilePath)!); - File.WriteAllText(ExportsFilePath, content); + Directory.CreateDirectory(Path.GetDirectoryName(InteropExportsFilePath)!); + File.WriteAllText(InteropExportsFilePath, content); } - private void GenerateImports (AssemblyInspection inspection) + private void GenerateInteropImports (AssemblyInspection inspection) { var generator = new InteropImportGenerator(EntryAssemblyName); var content = generator.Generate(inspection); - Directory.CreateDirectory(Path.GetDirectoryName(ImportsFilePath)!); - File.WriteAllText(ImportsFilePath, content); + Directory.CreateDirectory(Path.GetDirectoryName(InteropImportsFilePath)!); + File.WriteAllText(InteropImportsFilePath, content); } private void GenerateSerializer (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs b/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs new file mode 100644 index 00000000..5dc384e1 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +/// +/// Generates interceptors for methods +/// in the solution (except methods generated by ). +/// +public class EventGenerator +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs deleted file mode 100644 index b1df3501..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ExportClassGenerator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish; - -internal sealed class ExportClassGenerator -{ - -} diff --git a/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs new file mode 100644 index 00000000..cc5a83f1 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +/// +/// Generates implementation classes for interfaces specified +/// under Bootsharp's . +/// +internal sealed class ExportGenerator +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs b/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs new file mode 100644 index 00000000..0914dd3b --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +/// +/// Generates interceptors for methods +/// in the solution (except methods generated by ). +/// +public class FunctionGenerator +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs deleted file mode 100644 index c43f0971..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ImportClassGenerator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish; - -internal sealed class ImportClassGenerator -{ - -} diff --git a/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs new file mode 100644 index 00000000..50714ba2 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +/// +/// Generates implementation classes for interfaces specified +/// under Bootsharp's . +/// +internal sealed class ImportGenerator +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/InteropExportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropExportGenerator.cs index fdc98156..7bd2f5c8 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropExportGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropExportGenerator.cs @@ -1,5 +1,9 @@ namespace Bootsharp.Publish; +/// +/// For all the methods in the solution, +/// generates C# -> JS bindings to be picked by DotNet's source generators. +/// internal sealed class InteropExportGenerator { public string Generate (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/InteropImportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropImportGenerator.cs index 9ffff22c..14dab858 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropImportGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropImportGenerator.cs @@ -1,5 +1,10 @@ namespace Bootsharp.Publish; +/// +/// For all the and +/// methods in the solution, +/// generates JS -> C# bindings to be picked by DotNet's source generators. +/// internal sealed class InteropImportGenerator (string entryAssembly) { public string Generate (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs new file mode 100644 index 00000000..b58ffbe7 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs @@ -0,0 +1,12 @@ +namespace Bootsharp.Publish; + +/// +/// Generates hints for DotNet to not trim classes with +/// methods (they're used by +/// generated interop bindings, which is not obvious for DotNet), except +/// methods generated by . +/// +public class InvokableGenerator +{ + +} diff --git a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs index 383dadab..489f7024 100644 --- a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs @@ -1,5 +1,10 @@ namespace Bootsharp.Publish; +/// +/// Generates hints for all the types used in interop to be picked by +/// DotNet's JSON serializer source generator. Required for the serializer to +/// work without using reflection (which is required to support trimming). +/// internal sealed class SerializerGenerator { private readonly HashSet attributes = []; diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 5be9df60..110426df 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -5,8 +5,13 @@ $(BootsharpRoot)/js $(BootsharpRoot)/tasks/Bootsharp.Publish.dll $(IntermediateOutputPath)bootsharp - $(BootsharpIntermediateDirectory)/InteropExports.g.cs - $(BootsharpIntermediateDirectory)/InteropImports.g.cs + $(BootsharpIntermediateDirectory)/Invokables.g.cs + $(BootsharpIntermediateDirectory)/Functions.g.cs + $(BootsharpIntermediateDirectory)/Events.g.cs + $(BootsharpIntermediateDirectory)/Exports.g.cs + $(BootsharpIntermediateDirectory)/Imports.g.cs + $(BootsharpIntermediateDirectory)/InteropExports.g.cs + $(BootsharpIntermediateDirectory)/InteropImports.g.cs $(BootsharpIntermediateDirectory)/SerializerContext.g.cs $(AssemblyName).dll @@ -61,15 +66,30 @@ + + + + + + + + + + From 1059829e266b87b849e0af08a95660f75e847640 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 7 Jan 2024 17:38:38 +0300 Subject: [PATCH 04/75] iteration --- src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs | 4 ++-- src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs | 2 +- src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs | 4 ++-- src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs | 5 ++++- src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs index d2a29351..0fcd3ebe 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs @@ -1,6 +1,6 @@ namespace Bootsharp.Publish.Test; -public class EventTest +public class EventTest : EmitTest { - + protected override string TestedContent => GeneratedEvents; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs index 98dfda80..2fe72d31 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs @@ -2,5 +2,5 @@ public class ExportTest : EmitTest { - + protected override string TestedContent => GeneratedExports; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs index f3deb709..a3082b68 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs @@ -1,6 +1,6 @@ namespace Bootsharp.Publish.Test; -public class FunctionTest +public class FunctionTest : EmitTest { - + protected override string TestedContent => GeneratedFunctions; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs index 20f8c63d..79c42dfd 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs @@ -1,3 +1,6 @@ namespace Bootsharp.Publish.Test; -public class ImportTest : EmitTest { } +public class ImportTest : EmitTest +{ + protected override string TestedContent => GeneratedImports; +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs index 5e64443b..9dd4761d 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs @@ -1,6 +1,6 @@ namespace Bootsharp.Publish.Test; -public class InvokableTest +public class InvokableTest : EmitTest { - + protected override string TestedContent => GeneratedInvokables; } From 98e47cae224471e2fc7d876b043e5218d526ee92 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 7 Jan 2024 17:51:36 +0300 Subject: [PATCH 05/75] iteration --- .../Bootsharp.Publish.Test/Emit/EventTest.cs | 15 ++++++ .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 50 +++++++++++++++---- .../Bootsharp.Publish/Emit/EventGenerator.cs | 4 +- .../Bootsharp.Publish/Emit/ExportGenerator.cs | 2 +- .../Emit/FunctionGenerator.cs | 4 +- .../Bootsharp.Publish/Emit/ImportGenerator.cs | 2 +- .../Emit/InvokableGenerator.cs | 4 +- 7 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs index 0fcd3ebe..ccd6c718 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs @@ -3,4 +3,19 @@ public class EventTest : EmitTest { protected override string TestedContent => GeneratedEvents; + + [Fact] + public void WhenNothingInspectedNothingIsGenerated () + { + Execute(); + Assert.Empty(TestedContent); + } + + [Fact] + public void WhenNoEventsNothingIsGenerated () + { + AddAssembly(WithClass("[JSFunction] public static void Foo () {}")); + Execute(); + Assert.Empty(TestedContent); + } } diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 167e7bea..44b519b6 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -48,37 +48,65 @@ private AssemblyInspection InspectAssemblies (NamespaceBuilder spaceBuilder) return inspection; } - private void GenerateInvokables (AssemblyInspection inspection) { } + private void GenerateInvokables (AssemblyInspection inspection) + { + var generator = new InvokableGenerator(); + var content = generator.Generate(inspection); + WriteGenerated(InvokablesFilePath, content); + } - private void GenerateFunctions (AssemblyInspection inspection) { } + private void GenerateFunctions (AssemblyInspection inspection) + { + var generator = new FunctionGenerator(); + var content = generator.Generate(inspection); + WriteGenerated(FunctionsFilePath, content); + } - private void GenerateEvents (AssemblyInspection inspection) { } + private void GenerateEvents (AssemblyInspection inspection) + { + var generator = new EventGenerator(); + var content = generator.Generate(inspection); + WriteGenerated(EventsFilePath, content); + } - private void GenerateExports (AssemblyInspection inspection) { } + private void GenerateExports (AssemblyInspection inspection) + { + var generator = new ExportGenerator(); + var content = generator.Generate(inspection); + WriteGenerated(ExportsFilePath, content); + } - private void GenerateImports (AssemblyInspection inspection) { } + private void GenerateImports (AssemblyInspection inspection) + { + var generator = new ImportGenerator(); + var content = generator.Generate(inspection); + WriteGenerated(ImportsFilePath, content); + } private void GenerateInteropExports (AssemblyInspection inspection) { var generator = new InteropExportGenerator(); var content = generator.Generate(inspection); - Directory.CreateDirectory(Path.GetDirectoryName(InteropExportsFilePath)!); - File.WriteAllText(InteropExportsFilePath, content); + WriteGenerated(InteropExportsFilePath, content); } private void GenerateInteropImports (AssemblyInspection inspection) { var generator = new InteropImportGenerator(EntryAssemblyName); var content = generator.Generate(inspection); - Directory.CreateDirectory(Path.GetDirectoryName(InteropImportsFilePath)!); - File.WriteAllText(InteropImportsFilePath, content); + WriteGenerated(InteropImportsFilePath, content); } private void GenerateSerializer (AssemblyInspection inspection) { var generator = new SerializerGenerator(); var content = generator.Generate(inspection); - Directory.CreateDirectory(Path.GetDirectoryName(SerializerFilePath)!); - File.WriteAllText(SerializerFilePath, content); + WriteGenerated(SerializerFilePath, content); + } + + private static void WriteGenerated (string path, string content) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, content); } } diff --git a/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs b/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs index 5dc384e1..9f76229e 100644 --- a/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs @@ -4,7 +4,7 @@ /// Generates interceptors for methods /// in the solution (except methods generated by ). /// -public class EventGenerator +internal sealed class EventGenerator { - + public string Generate (AssemblyInspection inspection) => ""; } diff --git a/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs index cc5a83f1..ba3627d6 100644 --- a/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs @@ -6,5 +6,5 @@ /// internal sealed class ExportGenerator { - + public string Generate (AssemblyInspection inspection) => ""; } diff --git a/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs b/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs index 0914dd3b..5234f759 100644 --- a/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs @@ -4,7 +4,7 @@ /// Generates interceptors for methods /// in the solution (except methods generated by ). /// -public class FunctionGenerator +internal sealed class FunctionGenerator { - + public string Generate (AssemblyInspection inspection) => ""; } diff --git a/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs index 50714ba2..e634ae4e 100644 --- a/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs @@ -6,5 +6,5 @@ /// internal sealed class ImportGenerator { - + public string Generate (AssemblyInspection inspection) => ""; } diff --git a/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs index b58ffbe7..e04f8a1c 100644 --- a/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs @@ -6,7 +6,7 @@ /// generated interop bindings, which is not obvious for DotNet), except /// methods generated by . /// -public class InvokableGenerator +internal sealed class InvokableGenerator { - + public string Generate (AssemblyInspection inspection) => ""; } From f61c62f76bf440360a850920112824c2be66d6c8 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 7 Jan 2024 20:06:11 +0300 Subject: [PATCH 06/75] iteration --- src/cs/Bootsharp.Common/Bootsharp.Common.csproj | 2 -- .../Emit/{ => ForDotNet}/InteropExportGenerator.cs | 0 .../Emit/{ => ForDotNet}/InteropImportGenerator.cs | 0 .../Emit/{ => ForDotNet}/SerializerGenerator.cs | 0 4 files changed, 2 deletions(-) rename src/cs/Bootsharp.Publish/Emit/{ => ForDotNet}/InteropExportGenerator.cs (100%) rename src/cs/Bootsharp.Publish/Emit/{ => ForDotNet}/InteropImportGenerator.cs (100%) rename src/cs/Bootsharp.Publish/Emit/{ => ForDotNet}/SerializerGenerator.cs (100%) diff --git a/src/cs/Bootsharp.Common/Bootsharp.Common.csproj b/src/cs/Bootsharp.Common/Bootsharp.Common.csproj index b52f6d7a..76a68d46 100644 --- a/src/cs/Bootsharp.Common/Bootsharp.Common.csproj +++ b/src/cs/Bootsharp.Common/Bootsharp.Common.csproj @@ -12,8 +12,6 @@ - diff --git a/src/cs/Bootsharp.Publish/Emit/InteropExportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropExportGenerator.cs similarity index 100% rename from src/cs/Bootsharp.Publish/Emit/InteropExportGenerator.cs rename to src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropExportGenerator.cs diff --git a/src/cs/Bootsharp.Publish/Emit/InteropImportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropImportGenerator.cs similarity index 100% rename from src/cs/Bootsharp.Publish/Emit/InteropImportGenerator.cs rename to src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropImportGenerator.cs diff --git a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ForDotNet/SerializerGenerator.cs similarity index 100% rename from src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs rename to src/cs/Bootsharp.Publish/Emit/ForDotNet/SerializerGenerator.cs From 8bf4e0183869864405241ee554e0bf0cfd248445 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 7 Jan 2024 23:16:11 +0300 Subject: [PATCH 07/75] remove source gen --- src/cs/.scripts/cover.ps1 | 1 - src/cs/Bootsharp.Common.Test/BindingTest.cs | 9 +- .../Bootsharp.Common.Test.csproj | 3 - src/cs/Bootsharp.Common.Test/ExceptionTest.cs | 12 + src/cs/Bootsharp.Common.Test/FunctionTest.cs | 28 -- .../{ => Exceptions}/Error.cs | 0 .../Exceptions/NotIntercepted.cs | 9 + src/cs/Bootsharp.Common/Interop/Function.cs | 40 --- .../{Interop => }/Serializer.cs | 0 .../Bootsharp.Generate.Test.csproj | 41 --- .../Emitters/EventTest.cs | 73 ----- .../Emitters/ExportTest.cs | 121 -------- .../Emitters/FunctionTest.cs | 229 ---------------- .../Emitters/ImportTest.cs | 258 ------------------ .../Emitters/InvokableTest.cs | 47 ---- .../Bootsharp.Generate.Test/GeneratorTest.cs | 161 ----------- src/cs/Bootsharp.Generate.Test/Verifier.cs | 16 -- .../Bootsharp.Generate.csproj | 15 - src/cs/Bootsharp.Generate/Common.cs | 143 ---------- .../Emitters/BindingEmitter.cs | 102 ------- .../Bootsharp.Generate/Emitters/ExportType.cs | 65 ----- .../Bootsharp.Generate/Emitters/ImportType.cs | 63 ----- .../Emitters/PartialClass.cs | 72 ----- .../Emitters/PartialMethod.cs | 23 -- src/cs/Bootsharp.Generate/SourceGenerator.cs | 45 --- src/cs/Bootsharp.Generate/SyntaxReceiver.cs | 42 --- .../Bootsharp.Publish.Test/Emit/EmitTest.cs | 24 +- .../Bootsharp.Publish.Test/Emit/EventTest.cs | 21 -- .../Emit/FunctionTest.cs | 6 - .../Emit/InteropExportTest.cs | 186 ------------- .../Emit/InteropImportTest.cs | 213 --------------- .../Emit/InvokableTest.cs | 6 - .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 56 ++-- .../Emit/DependenciesGenerator.cs | 10 + .../Bootsharp.Publish/Emit/EventGenerator.cs | 10 - .../Emit/ForDotNet/InteropExportGenerator.cs | 82 ------ .../Emit/ForDotNet/InteropImportGenerator.cs | 83 ------ ...onGenerator.cs => InterceptorGenerator.cs} | 6 +- .../Emit/InteropGenerator.cs | 9 + .../Emit/InvokableGenerator.cs | 12 - .../{ForDotNet => }/SerializerGenerator.cs | 0 src/cs/Bootsharp.sln | 12 - src/cs/Bootsharp/Build/Bootsharp.targets | 34 +-- 43 files changed, 90 insertions(+), 2298 deletions(-) create mode 100644 src/cs/Bootsharp.Common.Test/ExceptionTest.cs delete mode 100644 src/cs/Bootsharp.Common.Test/FunctionTest.cs rename src/cs/Bootsharp.Common/{ => Exceptions}/Error.cs (100%) create mode 100644 src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs delete mode 100644 src/cs/Bootsharp.Common/Interop/Function.cs rename src/cs/Bootsharp.Common/{Interop => }/Serializer.cs (100%) delete mode 100644 src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj delete mode 100644 src/cs/Bootsharp.Generate.Test/Emitters/EventTest.cs delete mode 100644 src/cs/Bootsharp.Generate.Test/Emitters/ExportTest.cs delete mode 100644 src/cs/Bootsharp.Generate.Test/Emitters/FunctionTest.cs delete mode 100644 src/cs/Bootsharp.Generate.Test/Emitters/ImportTest.cs delete mode 100644 src/cs/Bootsharp.Generate.Test/Emitters/InvokableTest.cs delete mode 100644 src/cs/Bootsharp.Generate.Test/GeneratorTest.cs delete mode 100644 src/cs/Bootsharp.Generate.Test/Verifier.cs delete mode 100644 src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj delete mode 100644 src/cs/Bootsharp.Generate/Common.cs delete mode 100644 src/cs/Bootsharp.Generate/Emitters/BindingEmitter.cs delete mode 100644 src/cs/Bootsharp.Generate/Emitters/ExportType.cs delete mode 100644 src/cs/Bootsharp.Generate/Emitters/ImportType.cs delete mode 100644 src/cs/Bootsharp.Generate/Emitters/PartialClass.cs delete mode 100644 src/cs/Bootsharp.Generate/Emitters/PartialMethod.cs delete mode 100644 src/cs/Bootsharp.Generate/SourceGenerator.cs delete mode 100644 src/cs/Bootsharp.Generate/SyntaxReceiver.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InteropExportTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InteropImportTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/EventGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropExportGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropImportGenerator.cs rename src/cs/Bootsharp.Publish/Emit/{FunctionGenerator.cs => InterceptorGenerator.cs} (60%) create mode 100644 src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs rename src/cs/Bootsharp.Publish/Emit/{ForDotNet => }/SerializerGenerator.cs (100%) diff --git a/src/cs/.scripts/cover.ps1 b/src/cs/.scripts/cover.ps1 index 5fe991b2..4b07125f 100644 --- a/src/cs/.scripts/cover.ps1 +++ b/src/cs/.scripts/cover.ps1 @@ -2,7 +2,6 @@ try { $out = "../.cover/" $json = "../.cover/coverage.json" dotnet test Bootsharp.Common.Test/Bootsharp.Common.Test.csproj /p:CollectCoverage=true /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out - dotnet test Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj /p:CollectCoverage=true /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out /p:MergeWith=$json dotnet test Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj /p:CollectCoverage=true /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out /p:MergeWith=$json dotnet test Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj /p:CollectCoverage=true /p:CoverletOutputFormat="json%2copencover" /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out /p:MergeWith=$json reportgenerator "-reports:*/*.xml" "-targetdir:.cover" -reporttypes:HTML diff --git a/src/cs/Bootsharp.Common.Test/BindingTest.cs b/src/cs/Bootsharp.Common.Test/BindingTest.cs index 18e58625..6cb3f72d 100644 --- a/src/cs/Bootsharp.Common.Test/BindingTest.cs +++ b/src/cs/Bootsharp.Common.Test/BindingTest.cs @@ -13,13 +13,16 @@ public void Records () [Fact] public void RegistersExports () { - Assert.Equal(typeof(IBackend), BindingRegistry.Exports[typeof(global::Backend.JSBackend)].Api); - Assert.IsType>(BindingRegistry.Exports[typeof(global::Backend.JSBackend)].Factory); + var binding = new ExportBinding(typeof(IBackend), default); + BindingRegistry.Register(typeof(Backend), binding); + Assert.Equal(typeof(IBackend), BindingRegistry.Exports[typeof(Backend)].Api); } [Fact] public void RegistersImports () { - Assert.IsType(BindingRegistry.Imports[typeof(IFrontend)].Implementation); + var binding = new ImportBinding(new Frontend()); + BindingRegistry.Register(typeof(IFrontend), binding); + Assert.IsType(BindingRegistry.Imports[typeof(IFrontend)].Implementation); } } diff --git a/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj b/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj index 36603de7..585af504 100644 --- a/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj +++ b/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj @@ -29,9 +29,6 @@ - diff --git a/src/cs/Bootsharp.Common.Test/ExceptionTest.cs b/src/cs/Bootsharp.Common.Test/ExceptionTest.cs new file mode 100644 index 00000000..9377e54f --- /dev/null +++ b/src/cs/Bootsharp.Common.Test/ExceptionTest.cs @@ -0,0 +1,12 @@ +namespace Bootsharp.Common.Test; + +public class ExceptionTest +{ + [Fact] + public void NotImplementedIncludesMethodName () + { + Assert.Contains("$Func", Assert.Throws(Func).Message); + } + + private static void Func () => throw new NotIntercepted(); +} diff --git a/src/cs/Bootsharp.Common.Test/FunctionTest.cs b/src/cs/Bootsharp.Common.Test/FunctionTest.cs deleted file mode 100644 index 7a02f78a..00000000 --- a/src/cs/Bootsharp.Common.Test/FunctionTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using static Bootsharp.Function; - -namespace Bootsharp.Common.Test; - -public class FunctionTest -{ - [Fact] - public void WhenEndpointNotFoundErrorIsThrown () - { - Assert.Contains("Endpoint 'foo' is not found.", - Assert.Throws(() => Get("foo")).Message); - } - - [Fact] - public void WhenFunctionTypeIsWrongErrorIsThrown () - { - Set("bar", null); - Assert.Contains("Endpoint 'bar' is not 'System.Action'.", - Assert.Throws(() => Get("bar")).Message); - } - - [Fact] - public void CanSetAndGetDelegate () - { - Set("echo", (int x, int y) => x + y); - Assert.Equal(15, Get>("echo")(6, 9)); - } -} diff --git a/src/cs/Bootsharp.Common/Error.cs b/src/cs/Bootsharp.Common/Exceptions/Error.cs similarity index 100% rename from src/cs/Bootsharp.Common/Error.cs rename to src/cs/Bootsharp.Common/Exceptions/Error.cs diff --git a/src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs b/src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs new file mode 100644 index 00000000..9756a992 --- /dev/null +++ b/src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs @@ -0,0 +1,9 @@ +using System.Runtime.CompilerServices; + +namespace Bootsharp; + +/// +/// Exception thrown when Bootsharp fails to intercept user-defined interop method. +/// +public sealed class NotIntercepted ([CallerMemberName] string name = "") : + Exception($"Bootsharp failed to intercept '${name}'."); diff --git a/src/cs/Bootsharp.Common/Interop/Function.cs b/src/cs/Bootsharp.Common/Interop/Function.cs deleted file mode 100644 index 880b1c1c..00000000 --- a/src/cs/Bootsharp.Common/Interop/Function.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Bootsharp; - -/// -/// Provides access to JavaScript function delegates via endpoints. -/// -/// -/// Endpoint is composed of JavaScript object and function names joined with dot. -/// In case host object is composed, object chain is joined with dots as well. -/// -public static class Function -{ - private static readonly Dictionary functions = new(); - - /// - /// Maps function delegate to specified endpoint. - /// - /// - /// Performed at 'PrepareBootsharp' build step (not in source generator). - /// - public static void Set (string endpoint, Delegate func) - { - functions[endpoint] = func; - } - - /// - /// Returns function delegate of specified endpoint and type. - /// - /// - /// Used in sources generated for partial - /// and methods. - /// - public static T Get (string endpoint) where T : Delegate - { - if (!functions.TryGetValue(endpoint, out var func)) - throw new Error($"Endpoint '{endpoint}' is not found."); - if (func is not T specific) - throw new Error($"Endpoint '{endpoint}' is not '{typeof(T)}'."); - return specific; - } -} diff --git a/src/cs/Bootsharp.Common/Interop/Serializer.cs b/src/cs/Bootsharp.Common/Serializer.cs similarity index 100% rename from src/cs/Bootsharp.Common/Interop/Serializer.cs rename to src/cs/Bootsharp.Common/Serializer.cs diff --git a/src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj b/src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj deleted file mode 100644 index 8b23a226..00000000 --- a/src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - net8.0 - enable - false - - - - https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json; - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/src/cs/Bootsharp.Generate.Test/Emitters/EventTest.cs b/src/cs/Bootsharp.Generate.Test/Emitters/EventTest.cs deleted file mode 100644 index 466ed93d..00000000 --- a/src/cs/Bootsharp.Generate.Test/Emitters/EventTest.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace Bootsharp.Generate.Test; - -public static class EventTest -{ - public static IEnumerable Data { get; } = new[] { - // Can generate event binding without namespace and arguments. - [ - """ - partial class Foo - { - [JSEvent] - partial void OnBar (); - } - """, - """ - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - partial void OnBar () => Get("Global.onBar")(); - } - """ - ], - // Can generate event binding with namespace and arguments. - [ - """ - namespace Space; - - public static partial class Foo - { - [JSEvent] - public static partial void OnBar (string a, int b); - } - """, - """ - namespace Space; - - public static partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Space.Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - public static partial void OnBar (global::System.String a, global::System.Int32 b) => Get>("Space.onBar")(a, b); - } - """ - ], - // Can generate event binding with serialized parameters. - new object[] { - """ - public record Info(string Baz); - - public static partial class Foo - { - [JSEvent] - public static partial void OnInfo (Info info); - } - """, - """ - public static partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - public static partial void OnInfo (global::Info info) => Get>("Global.onInfo")(Serialize(info)); - } - """ - } - }; -} diff --git a/src/cs/Bootsharp.Generate.Test/Emitters/ExportTest.cs b/src/cs/Bootsharp.Generate.Test/Emitters/ExportTest.cs deleted file mode 100644 index f67aea92..00000000 --- a/src/cs/Bootsharp.Generate.Test/Emitters/ExportTest.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace Bootsharp.Generate.Test; - -public static class ExportTest -{ - public static IEnumerable Data { get; } = new[] { - // Can export various APIs. - [ - """ - using System.Threading.Tasks; - - [assembly:JSExport(typeof(IFoo))] - - public readonly record struct Item(); - - public interface IFoo - { - void Foo (string? foo); - Task Bar (); - Item? Baz (); - Task Nya (); - string[] Far (int[] far); - } - """, - """ - namespace Foo; - - public class JSFoo - { - private static global::IFoo handler = null!; - - public JSFoo (global::IFoo handler) - { - JSFoo.handler = handler; - } - - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(JSFoo), new ExportBinding(typeof(global::IFoo), handler => new JSFoo((global::IFoo)handler))); - } - - [JSInvokable] public static void Foo (global::System.String? foo) => handler.Foo(foo); - [JSInvokable] public static global::System.Threading.Tasks.Task Bar () => handler.Bar(); - [JSInvokable] public static global::Item? Baz () => handler.Baz(); - [JSInvokable] public static global::System.Threading.Tasks.Task Nya () => handler.Nya(); - [JSInvokable] public static global::System.String[] Far (global::System.Int32[] far) => handler.Far(far); - } - """ - ], - // Can override name and invoke. - [ - """ - [assembly:JSExport(typeof(IFoo), NamePattern="Foo", NameReplacement="Bar", InvokePattern="(.+)", InvokeReplacement="$1/**/")] - - public interface IFoo - { - void Foo (string foo); - } - """, - """ - namespace Foo; - - public class JSFoo - { - private static global::IFoo handler = null!; - - public JSFoo (global::IFoo handler) - { - JSFoo.handler = handler; - } - - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(JSFoo), new ExportBinding(typeof(global::IFoo), handler => new JSFoo((global::IFoo)handler))); - } - - [JSInvokable] public static void Bar (global::System.String foo) => handler.Foo(foo)/**/; - } - """ - ], - // Can override namespace. - new object[] { - """ - [assembly:JSNamespace("Foo", "Bar")] - [assembly:JSExport(typeof(A.B.C.IFoo))] - - namespace A.B.C; - - public interface IFoo - { - void Foo (); - } - """, - """ - namespace Foo; - - public class JSFoo - { - private static global::A.B.C.IFoo handler = null!; - - public JSFoo (global::A.B.C.IFoo handler) - { - JSFoo.handler = handler; - } - - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(JSFoo), new ExportBinding(typeof(global::A.B.C.IFoo), handler => new JSFoo((global::A.B.C.IFoo)handler))); - } - - [JSInvokable] public static void Foo () => handler.Foo(); - } - """ - } - }; -} diff --git a/src/cs/Bootsharp.Generate.Test/Emitters/FunctionTest.cs b/src/cs/Bootsharp.Generate.Test/Emitters/FunctionTest.cs deleted file mode 100644 index 299a0cf0..00000000 --- a/src/cs/Bootsharp.Generate.Test/Emitters/FunctionTest.cs +++ /dev/null @@ -1,229 +0,0 @@ -namespace Bootsharp.Generate.Test; - -public static class FunctionTest -{ - public static IEnumerable Data { get; } = new[] { - // Can generate void binding under root namespace. - [ - """ - partial class Foo - { - [JSFunction] - partial void Bar (); - } - """, - """ - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - partial void Bar () => Get("Global.bar")(); - } - """ - ], - // Can generate void task binding under file-scoped namespace. - [ - """ - using System.Threading.Tasks; - - namespace File.Scoped; - - public static partial class Foo - { - [JSFunction] - private static partial Task BarAsync (string a, int b); - } - """, - """ - using System.Threading.Tasks; - - namespace File.Scoped; - - public static partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "File.Scoped.Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - private static partial global::System.Threading.Tasks.Task BarAsync (global::System.String a, global::System.Int32 b) => Get>("File.Scoped.barAsync")(a, b); - } - """ - ], - // Can generate value task binding. - [ - """ - using System.Threading.Tasks; - - namespace File.Scoped; - - public static partial class Foo - { - [JSFunction] - private static partial Task BarAsync (); - } - """, - """ - using System.Threading.Tasks; - - namespace File.Scoped; - - public static partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "File.Scoped.Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - private static partial global::System.Threading.Tasks.Task BarAsync () => Get>>("File.Scoped.barAsync")(); - } - """ - ], - // Can generate under classic namespace. - [ - """ - using System; - using System.Threading.Tasks; - - namespace Classic - { - partial class Foo - { - [JSFunction] - public partial DateTime GetTime (DateTime time); - [JSFunction] - public partial Task GetTimeAsync (DateTime time); - } - } - """, - """ - using System; - using System.Threading.Tasks; - - namespace Classic - { - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Classic.Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - public partial global::System.DateTime GetTime (global::System.DateTime time) => Get>("Classic.getTime")(time); - public partial global::System.Threading.Tasks.Task GetTimeAsync (global::System.DateTime time) => Get>>("Classic.getTimeAsync")(time); - } - } - """ - ], - // Can override namespace. - [ - """ - [assembly:JSNamespace(@"A\.B\.(\S+)", "$1")] - - namespace A.B.C; - - public partial class Foo - { - [JSFunction] - public static partial void OnFun (bool val); - } - """, - """ - namespace A.B.C; - - public partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "A.B.C.Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - public static partial void OnFun (global::System.Boolean val) => Get>("C.onFun")(val); - } - """ - ], - // Can generate void binding with serialized parameters. - [ - """ - using System.Threading.Tasks; - - public record Info(string Baz); - - partial class Foo - { - [JSFunction] - public partial Info Bar (Info info1, Info info2); - [JSFunction] - public partial Task BarAsync (Info info); - [JSFunction] - public partial Task BazAsync (); - } - """, - """ - using System.Threading.Tasks; - - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - public partial global::Info Bar (global::Info info1, global::Info info2) => Deserialize(Get>("Global.bar")(Serialize(info1), Serialize(info2))); - public async partial global::System.Threading.Tasks.Task BarAsync (global::Info info) => Deserialize(await Get>>("Global.barAsync")(Serialize(info))); - public async partial global::System.Threading.Tasks.Task BazAsync () => Deserialize(await Get>>("Global.bazAsync")()); - } - """ - ], - // Doesn't serialize types that can be transferred as-is. - [ - """ - using System; - using System.Threading.Tasks; - - partial class Foo - { - [JSFunction] - public partial Task Bar (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16); - [JSFunction] - public partial Task Baz (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16); - } - """, - """ - using System; - using System.Threading.Tasks; - - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - public partial global::System.Threading.Tasks.Task Bar (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, global::System.DateTime a10, global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => Get>>("Global.bar")(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); - public partial global::System.Threading.Tasks.Task Baz (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, global::System.DateTime? a10, global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16) => Get>>("Global.baz")(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); - } - """ - ], - // Special corner case when UsingDirectiveSyntax.Name is null. - new object[] { - """ - using x = (System.String, System.Boolean); - - partial class Foo - { - [JSFunction] - partial void Bar (); - } - """, - """ - using x = (System.String, System.Boolean); - - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - - partial void Bar () => Get("Global.bar")(); - } - """ - } - }; -} diff --git a/src/cs/Bootsharp.Generate.Test/Emitters/ImportTest.cs b/src/cs/Bootsharp.Generate.Test/Emitters/ImportTest.cs deleted file mode 100644 index 3dd8cd44..00000000 --- a/src/cs/Bootsharp.Generate.Test/Emitters/ImportTest.cs +++ /dev/null @@ -1,258 +0,0 @@ -namespace Bootsharp.Generate.Test; - -public static class ImportTest -{ - public static IEnumerable Data { get; } = new[] { - // Can import various APIs; also, when event parameters are set to null, event is not detected. - [ - """ - using System.Threading.Tasks; - - [assembly:JSImport(typeof(IFoo), EventPattern=null, EventReplacement=null)] - - public interface IFoo - { - void NotifyFoo (string foo); - bool Bar (); - Task Nya (); - Task Far (); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSFunction] public static void NotifyFoo (global::System.String foo) => Get>("Foo.notifyFoo")(foo); - [JSFunction] public static global::System.Boolean Bar () => Get>("Foo.bar")(); - [JSFunction] public static global::System.Threading.Tasks.Task Nya () => Get>("Foo.nya")(); - [JSFunction] public static global::System.Threading.Tasks.Task Far () => Get>>("Foo.far")(); - - void global::IFoo.NotifyFoo (global::System.String foo) => NotifyFoo(foo); - global::System.Boolean global::IFoo.Bar () => Bar(); - global::System.Threading.Tasks.Task global::IFoo.Nya () => Nya(); - global::System.Threading.Tasks.Task global::IFoo.Far () => Far(); - } - """ - ], - // Detects and overrides event methods with defaults. - new object[] { - """ - [assembly:JSImport(typeof(IFoo))] - - public interface IFoo - { - void NotifyFoo (string foo); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSEvent] public static void OnFoo (global::System.String foo) => Get>("Foo.onFoo")(foo); - - void global::IFoo.NotifyFoo (global::System.String foo) => OnFoo(foo); - } - """ - }, - // Can detect but not override event methods. - new object[] { - """ - [assembly:JSImport(typeof(IFoo), EventPattern=@"(^Notify)(\S+)", EventReplacement=null)] - - public interface IFoo - { - void NotifyFoo (string foo); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSEvent] public static void NotifyFoo (global::System.String foo) => Get>("Foo.notifyFoo")(foo); - - void global::IFoo.NotifyFoo (global::System.String foo) => NotifyFoo(foo); - } - """ - }, - // Can detect and override event methods. - new object[] { - """ - [assembly:JSImport(typeof(IFoo), EventPattern=@"(^Fire)(\S+)", EventReplacement="Handle$2")] - - public interface IFoo - { - void FireFoo (string foo); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSEvent] public static void HandleFoo (global::System.String foo) => Get>("Foo.handleFoo")(foo); - - void global::IFoo.FireFoo (global::System.String foo) => HandleFoo(foo); - } - """ - }, - // Can override name and invoke. - new object[] { - """ - [assembly:JSImport(typeof(IFoo), NamePattern="Nya(.+)", NameReplacement="Nah$1", InvokePattern="(.+)", InvokeReplacement="$1/**/")] - - public interface IFoo - { - void NyaFoo (string foo); - bool Bar (); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSFunction] public static void NahFoo (global::System.String foo) => Get>("Foo.nahFoo")(foo)/**/; - [JSFunction] public static global::System.Boolean Bar () => Get>("Foo.bar")()/**/; - - void global::IFoo.NyaFoo (global::System.String foo) => NahFoo(foo); - global::System.Boolean global::IFoo.Bar () => Bar(); - } - """ - }, - // When name and invoke don't have associated replacement parameter assigned, nothing is changed. - new object[] { - """ - [assembly:JSImport(typeof(IFoo), NamePattern="Foo", InvokePattern="(.+)")] - - public interface IFoo - { - void Foo (); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSFunction] public static void Foo () => Get("Foo.foo")(); - - void global::IFoo.Foo () => Foo(); - } - """ - }, - // Can override namespace. - new object[] { - """ - [assembly:JSNamespace("Foo", "Bar")] - [assembly:JSImport(typeof(A.B.C.IFoo))] - - namespace A.B.C; - - public interface IFoo - { - void F (); - } - """, - """ - namespace Foo; - - public class JSFoo : global::A.B.C.IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::A.B.C.IFoo), new ImportBinding(new JSFoo())); - } - - [JSFunction] public static void F () => Get("Bar.f")(); - - void global::A.B.C.IFoo.F () => F(); - } - """ - }, - // Can import with serialized parameters. - new object[] { - """ - using System.Threading.Tasks; - - [assembly:JSImport(typeof(IFoo))] - - public record Info(string Baz); - - public interface IFoo - { - void NotifyFoo (Info info1, Info info2); - Info Bar (); - Task Far (Info info); - } - """, - """ - namespace Foo; - - public class JSFoo : global::IFoo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo.JSFoo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof(global::IFoo), new ImportBinding(new JSFoo())); - } - - [JSEvent] public static void OnFoo (global::Info info1, global::Info info2) => Get>("Foo.onFoo")(Serialize(info1), Serialize(info2)); - [JSFunction] public static global::Info Bar () => Deserialize(Get>("Foo.bar")()); - [JSFunction] public static async global::System.Threading.Tasks.Task Far (global::Info info) => Deserialize(await Get>>("Foo.far")(Serialize(info))); - - void global::IFoo.NotifyFoo (global::Info info1, global::Info info2) => OnFoo(info1, info2); - global::Info global::IFoo.Bar () => Bar(); - global::System.Threading.Tasks.Task global::IFoo.Far (global::Info info) => Far(info); - } - """ - } - }; -} diff --git a/src/cs/Bootsharp.Generate.Test/Emitters/InvokableTest.cs b/src/cs/Bootsharp.Generate.Test/Emitters/InvokableTest.cs deleted file mode 100644 index ac2efe19..00000000 --- a/src/cs/Bootsharp.Generate.Test/Emitters/InvokableTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Bootsharp.Generate.Test; - -public static class InvokableTest -{ - public static IEnumerable Data { get; } = new[] { - // Generates dynamic dependencies registration. - [ - """ - partial class Foo - { - [JSInvokable] - public static void Bar () { } - } - """, - """ - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - } - """ - ], - // Generates dynamic dependencies registration under namespace. - new object[] { - """ - namespace Space; - - partial class Foo - { - [JSInvokable] - public static void Bar () { } - } - """, - """ - namespace Space; - - partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Space.Foo", "GeneratorTest")] - internal static void RegisterDynamicDependencies () { } - } - """ - } - }; -} diff --git a/src/cs/Bootsharp.Generate.Test/GeneratorTest.cs b/src/cs/Bootsharp.Generate.Test/GeneratorTest.cs deleted file mode 100644 index cdae9d34..00000000 --- a/src/cs/Bootsharp.Generate.Test/GeneratorTest.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System.Text; -using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; - -namespace Bootsharp.Generate.Test; - -public class GeneratorTest -{ - private static readonly List<(string file, string content)> sourceCache = []; - private readonly Verifier verifier = new(); - - [Fact] - public async Task WhenSourceIsEmptyNothingIsGenerated () - { - await Verify(""); - } - - [Fact] - public async Task NothingIsGeneratedWhenNoAttributes () - { - await Verify("partial class Foo { }"); - } - - [Fact] - public async Task WhenAttributeIsFromOtherNamespaceItsIgnored () - { - await Verify( - """ - [assembly:JSNamespace(@"Foo", "Bar")] - public class JSNamespaceAttribute : System.Attribute { public JSNamespaceAttribute (string _, string __) { } } - """); - } - - [Fact] - public async Task DoesntEmitDuplicateRegistrations () - { - verifier.TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck; - await Verify( - """ - partial class FunctionAfterInvokable - { - [JSInvokable] static void Bar () { } - [JSFunction] partial void Baz (); - } - partial class EventAfterInvokable - { - [JSInvokable] static void Bar () { } - [JSEvent] partial void Baz (); - } - partial class EventAfterFunction - { - [JSFunction] partial void Bar (); - [JSEvent] partial void Baz (); - } - """); - } - - [Fact] - public async Task DoesntAnalyzeGeneratedFiles () - { - // otherwise it'd pick files we generate in prepare build task - verifier.TestState.Sources.Add(("foo.g.cs", - """ - public static partial class Foo - { - [JSInvokable] public static void Bar () { } - [JSFunction] public static void Baz () { } - [JSEvent] public static void Nya () { } - } - """)); - await Verify(""); - } - - [Theory, MemberData(nameof(InvokableTest.Data), MemberType = typeof(InvokableTest))] - public Task PartialInvokableAreImplemented (string source, string expected) - => Verify(source, ("FooInvokable.g.cs", expected)); - - [Theory, MemberData(nameof(FunctionTest.Data), MemberType = typeof(FunctionTest))] - public Task PartialFunctionsAreImplemented (string source, string expected) - => Verify(source, ("FooFunctions.g.cs", expected)); - - [Theory, MemberData(nameof(EventTest.Data), MemberType = typeof(EventTest))] - public Task PartialEventsAreImplemented (string source, string expected) - => Verify(source, ("FooEvents.g.cs", expected)); - - [Theory, MemberData(nameof(ExportTest.Data), MemberType = typeof(ExportTest))] - public Task ExportTypesAreGenerated (string source, string expected) - => Verify(source, ("IFooExport.g.cs", expected)); - - [Theory, MemberData(nameof(ImportTest.Data), MemberType = typeof(ImportTest))] - public Task ImportTypesAreGenerated (string source, string expected) - => Verify(source, ("IFooImport.g.cs", expected)); - - private async Task Verify (string source, params (string file, string content)[] expected) - { - IncludeBootsharpSources(verifier.TestState.Sources); - verifier.TestCode = source; - for (int i = 0; i < expected.Length; i++) - { - IncludeCommonExpected(ref expected[i].content); - verifier.TestState.GeneratedSources.Add((typeof(SourceGenerator), expected[i].file, - SourceText.From(expected[i].content, Encoding.UTF8))); - } - await verifier.RunAsync(); - } - - private static void IncludeBootsharpSources (SourceFileList sources) - { - if (sourceCache.Count > 0) - { - foreach (var source in sourceCache) - sources.Add(source); - return; - } - var root = $"{Environment.CurrentDirectory}/../../../../Bootsharp.Common"; - foreach (var path in Directory.EnumerateFiles($"{root}/Attributes", "*.cs")) - sourceCache.Add((Path.GetFileName(path), File.ReadAllText(path))); - foreach (var path in Directory.EnumerateFiles($"{root}/Binding", "*.cs")) - sourceCache.Add((Path.GetFileName(path), File.ReadAllText(path))); - foreach (var path in Directory.EnumerateFiles($"{root}/Interop", "*.cs")) - sourceCache.Add((Path.GetFileName(path), File.ReadAllText(path))); - sourceCache.Add(("Error.cs", File.ReadAllText($"{root}/Error.cs"))); - sourceCache.Add(("DependencyInjection.cs", - """ - namespace Microsoft.Extensions.DependencyInjection; - public interface IServiceCollection - { - void AddSingleton(Type t, Func f); - void AddSingleton(Type t, object i); - } - public static class Extensions - { - public static object GetRequiredService(this IServiceProvider p, Type t) => default; - } - """)); - sourceCache.Add(("GlobalUsings.cs", - """ - global using System; - global using System.Collections.Generic; - global using System.IO; - global using System.Linq; - global using System.Threading.Tasks; - global using Bootsharp; - """)); - IncludeBootsharpSources(sources); - } - - private void IncludeCommonExpected (ref string expected) => expected = - $""" - #nullable enable - #pragma warning disable - using Bootsharp; - using static Bootsharp.Function; - using static Bootsharp.Serializer; - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - {expected} - #pragma warning restore - #nullable restore - """; -} diff --git a/src/cs/Bootsharp.Generate.Test/Verifier.cs b/src/cs/Bootsharp.Generate.Test/Verifier.cs deleted file mode 100644 index 0fcf9356..00000000 --- a/src/cs/Bootsharp.Generate.Test/Verifier.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Testing; -using Microsoft.CodeAnalysis.Testing; - -namespace Bootsharp.Generate.Test; - -public sealed class Verifier : CSharpSourceGeneratorTest - where T : IIncrementalGenerator, new() -{ - protected override string DefaultTestProjectName { get; } = "GeneratorTest"; - - public Verifier () => ReferenceAssemblies = ReferenceAssemblies.Net.Net80; - - protected override bool IsCompilerDiagnosticIncluded (Diagnostic diagnostic, CompilerDiagnostics _) => - diagnostic.Severity == DiagnosticSeverity.Error; -} diff --git a/src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj b/src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj deleted file mode 100644 index da86ad92..00000000 --- a/src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netstandard2.0 - preview - enable - true - false - - - - - - - diff --git a/src/cs/Bootsharp.Generate/Common.cs b/src/cs/Bootsharp.Generate/Common.cs deleted file mode 100644 index 9e2b2bb4..00000000 --- a/src/cs/Bootsharp.Generate/Common.cs +++ /dev/null @@ -1,143 +0,0 @@ -global using static Bootsharp.Generate.Common; -using System.Collections.Immutable; -using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis; - -namespace Bootsharp.Generate; - -internal static class Common -{ - public const string InvokableAttribute = "JSInvokable"; - public const string FunctionAttribute = "JSFunction"; - public const string EventAttribute = "JSEvent"; - public const string ExportAttribute = "JSExport"; - public const string ImportAttribute = "JSImport"; - public const string NamespaceAttribute = "JSNamespace"; - - public const string NamePatternArg = "NamePattern"; - public const string NameReplacementArg = "NameReplacement"; - public const string InvokePatternArg = "InvokePattern"; - public const string InvokeReplacementArg = "InvokeReplacement"; - public const string EventPatternArg = "EventPattern"; - public const string EventPatternReplacementArg = "EventReplacement"; - public const string DefaultEventPattern = @"(^Notify)(\S+)"; - public const string DefaultEventReplacement = "On$2"; - - public static string EmitCommon (string source) - => $""" - #nullable enable - #pragma warning disable - using Bootsharp; - using static Bootsharp.Function; - using static Bootsharp.Serializer; - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - {source} - #pragma warning restore - #nullable restore - """; - - public static string BuildBindingType (ITypeSymbol type) - { - return $"JS{type.Name.Substring(1)}"; - } - - public static string BuildBindingNamespace (ITypeSymbol type) - { - return type.Name.Substring(1); - } - - public static string BuildSyntax (ITypeSymbol type) - { - if (type.SpecialType == SpecialType.System_Void) return "void"; - if (type is IArrayTypeSymbol arrayType) return $"{BuildSyntax(arrayType.ElementType)}[]"; - var nullable = type.NullableAnnotation == NullableAnnotation.Annotated ? "?" : ""; - if (IsGeneric(type, out var args)) return BuildGeneric(type, args) + nullable; - return $"global::{ResolveTypeName(type)}{nullable}"; - - static string BuildGeneric (ITypeSymbol type, ImmutableArray args) - { - if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) return BuildSyntax(args[0]); - return $"global::{ResolveTypeName(type)}<{string.Join(", ", args.Select(BuildSyntax))}>"; - } - - static string ResolveTypeName (ITypeSymbol type) - { - if (type.ContainingNamespace.IsGlobalNamespace) return type.Name; - return string.Join(".", type.ContainingNamespace.ConstituentNamespaces) + "." + type.Name; - } - } - - public static bool IsGeneric (ITypeSymbol type, out ImmutableArray args) - { - args = type is INamedTypeSymbol { IsGenericType: true } named ? named.TypeArguments : default; - return args != default; - } - - public static string ConvertNamespace (string space, IAssemblySymbol assembly) - { - foreach (var attribute in assembly.GetAttributes().Where(IsNamespaceAttribute)) - space = Convert(space, attribute); - return space; - - static string Convert (string space, AttributeData attribute) - { - var pattern = (string)attribute.ConstructorArguments[0].Value; - var replacement = (string)attribute.ConstructorArguments[1].Value; - return Regex.Replace(space, pattern, replacement); - } - } - - public static string ConvertMethodName (string name, AttributeData attribute) - { - if (IsEvent(name, attribute)) return ConvertEventName(name, attribute); - var pattern = attribute.NamedArguments.FirstOrDefault(a => a.Key == NamePatternArg).Value.Value as string; - var replacement = attribute.NamedArguments.FirstOrDefault(a => a.Key == NameReplacementArg).Value.Value as string; - if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(replacement)) return name; - return Regex.Replace(name, pattern, replacement); - } - - public static bool IsEvent (string name, AttributeData attribute) - { - var pattern = default(string); - if (!attribute.NamedArguments.Any(a => a.Key == EventPatternArg)) pattern = DefaultEventPattern; - else pattern = attribute.NamedArguments.First(a => a.Key == EventPatternArg).Value.Value as string; - return !string.IsNullOrEmpty(pattern) && Regex.IsMatch(name, pattern); - } - - public static string ConvertEventName (string name, AttributeData attribute) - { - var pattern = default(string); - if (!attribute.NamedArguments.Any(a => a.Key == EventPatternArg)) pattern = DefaultEventPattern; - else pattern = attribute.NamedArguments.First(a => a.Key == EventPatternArg).Value.Value as string; - - var replacement = default(string); - if (!attribute.NamedArguments.Any(a => a.Key == EventPatternReplacementArg)) replacement = DefaultEventReplacement; - else replacement = attribute.NamedArguments.First(a => a.Key == EventPatternReplacementArg).Value.Value as string; - - if (string.IsNullOrEmpty(replacement)) return name; - return Regex.Replace(name, pattern, replacement); - } - - public static string ConvertMethodInvocation (string body, AttributeData attribute) - { - var pattern = attribute.NamedArguments.FirstOrDefault(a => a.Key == InvokePatternArg).Value.Value as string; - var replacement = attribute.NamedArguments.FirstOrDefault(a => a.Key == InvokeReplacementArg).Value.Value as string; - if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(replacement)) return body; - return Regex.Replace(body, pattern, replacement); - } - - public static bool IsExportAttribute (AttributeData attribute) => IsJSAttribute(attribute, ExportAttribute); - public static bool IsImportAttribute (AttributeData attribute) => IsJSAttribute(attribute, ImportAttribute); - public static bool IsNamespaceAttribute (AttributeData attribute) => IsJSAttribute(attribute, NamespaceAttribute); - - public static bool IsJSAttribute (AttributeData attribute, string name) => - attribute.AttributeClass!.ContainingNamespace.Name == "Bootsharp" && - attribute.AttributeClass.Name.StartsWith(name, StringComparison.Ordinal); - - public static string ToFirstLower (string value) - { - if (value.Length == 1) char.ToLowerInvariant(value[0]); - return char.ToLowerInvariant(value[0]) + value.Substring(1); - } -} diff --git a/src/cs/Bootsharp.Generate/Emitters/BindingEmitter.cs b/src/cs/Bootsharp.Generate/Emitters/BindingEmitter.cs deleted file mode 100644 index 98f22ca2..00000000 --- a/src/cs/Bootsharp.Generate/Emitters/BindingEmitter.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Bootsharp.Generate; - -internal class BindingEmitter (IMethodSymbol method, string space, string name) -{ - private bool @void, wait, shouldSerializeReturnType; - private ITypeSymbol returnType, taskResult; - - public void Emit (out string signature, out string body) - { - @void = method.ReturnsVoid; - returnType = method.ReturnType; - IsTaskWithResult(method.ReturnType, out taskResult); - shouldSerializeReturnType = ShouldSerialize(returnType); - wait = taskResult != null && shouldSerializeReturnType; - signature = EmitSignature(); - body = EmitBody(); - } - - private string EmitSignature () - { - var args = method.Parameters.Select(p => $"{BuildSyntax(p.Type)} {p.Name}"); - var sig = $"{BuildSyntax(method.ReturnType)} {name} ({string.Join(", ", args)})"; - if (wait) return sig = "async " + sig; - return sig; - } - - private string EmitBody () - { - var endpoint = $"{space}.{ToFirstLower(name)}"; - var delegateType = GetDelegateType(); - var args = GetArgs(); - var body = $"""Get<{delegateType}>("{endpoint}")({args})"""; - if (!shouldSerializeReturnType) return body; - var serialized = BuildSyntax(taskResult ?? returnType); - return $"Deserialize<{serialized}>({(wait ? "await " : "")}{body})"; - } - - private string GetDelegateType () - { - if (@void && method.Parameters.Length == 0) return "global::System.Action"; - var basename = @void ? "global::System.Action" : "global::System.Func"; - var args = method.Parameters.Select(GetDelegateArgType); - if (!@void) args = args.Append(GetDelegateReturnType()); - return $"{basename}<{string.Join(", ", args)}>"; - } - - private string GetDelegateArgType (IParameterSymbol param) => - ShouldSerialize(param.Type) ? "global::System.String" : BuildSyntax(param.Type); - - private string GetDelegateReturnType () => shouldSerializeReturnType - ? (taskResult != null ? "global::System.Threading.Tasks.Task" : "global::System.String") - : BuildSyntax(returnType); - - private string GetArgs () - { - if (method.Parameters.Length == 0) return ""; - var ids = method.Parameters.Select(GetArg); - return string.Join(", ", ids); - } - - private string GetArg (IParameterSymbol param) - { - return ShouldSerialize(param.Type) ? $"Serialize({param.Name})" : param.Name; - } - - // https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/import-export-interop - private static bool ShouldSerialize (ITypeSymbol type) - { - if (IsTaskWithResult(type, out var taskResult)) type = taskResult; - var array = type is IArrayTypeSymbol; - if (array) type = ((IArrayTypeSymbol)type).ElementType; - if (IsNullable(type, out var nullable)) type = nullable.TypeArguments.FirstOrDefault(); - if (array) return taskResult != null || !IsArrayTransferable(type); - return !IsStandaloneTransferable(type); - - static bool IsNullable (ITypeSymbol type, out INamedTypeSymbol nullable) - { - nullable = type as INamedTypeSymbol; - return $"{type.ContainingNamespace}.{type.MetadataName}" == typeof(Nullable<>).FullName; - } - - static bool IsStandaloneTransferable (ITypeSymbol type) => - Is(type) || Is(type) || Is(type) || Is(type) || Is(type) || - Is(type) || Is(type) || Is(type) || Is(type) || Is(type) || - Is(type) || Is(type) || Is(type) || Is(type) || - type.SpecialType == SpecialType.System_Void; - - static bool IsArrayTransferable (ITypeSymbol type) => - Is(type) || Is(type) || Is(type) || Is(type); - - static bool Is (ITypeSymbol type) => - $"{type.ContainingNamespace}.{type.MetadataName}" == typeof(T).FullName; - } - - private static bool IsTaskWithResult (ITypeSymbol type, out ITypeSymbol result) - { - return (result = $"{type.ContainingNamespace}.{type.MetadataName}" == typeof(Task<>).FullName - ? ((INamedTypeSymbol)type).TypeArguments[0] : null) != null; - } -} diff --git a/src/cs/Bootsharp.Generate/Emitters/ExportType.cs b/src/cs/Bootsharp.Generate/Emitters/ExportType.cs deleted file mode 100644 index 8b2daadd..00000000 --- a/src/cs/Bootsharp.Generate/Emitters/ExportType.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Bootsharp.Generate; - -internal sealed class ExportType (Compilation compilation, ITypeSymbol type, AttributeData attribute) -{ - public string Name { get; } = type.Name; - - public static IEnumerable Resolve (Compilation compilation) => - compilation.Assembly.GetAttributes().FirstOrDefault(IsExportAttribute) is { } attribute - ? attribute.ConstructorArguments[0].Values - .Select(v => v.Value).OfType() - .Where(t => t.TypeKind == TypeKind.Interface) - .Select(t => new ExportType(compilation, t, attribute)) - : Array.Empty(); - - public string EmitSource () - { - var specType = BuildSyntax(type); - var implType = BuildBindingType(type); - var space = BuildBindingNamespace(type); - return EmitCommon - ($$""" - namespace {{space}}; - - public class {{implType}} - { - private static {{specType}} handler = null!; - - public {{implType}} ({{specType}} handler) - { - {{implType}}.handler = handler; - } - - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "{{space}}.{{implType}}", "{{compilation.Assembly.Name}}")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof({{implType}}), new ExportBinding(typeof({{specType}}), handler => new {{implType}}(({{specType}})handler))); - } - - {{string.Join("\n ", type.GetMembers().OfType().Select(EmitMethod))}} - } - """); - - string EmitMethod (IMethodSymbol method) - { - return $"[{InvokableAttribute}] public static {EmitSignature()} => {EmitBody()};"; - - string EmitSignature () - { - var methodName = ConvertMethodName(method.Name, attribute); - var args = method.Parameters.Select(p => $"{BuildSyntax(p.Type)} {p.Name}"); - return $"{BuildSyntax(method.ReturnType)} {methodName} ({string.Join(", ", args)})"; - } - - string EmitBody () - { - var args = method.Parameters.Select(p => p.Name); - var body = $"handler.{method.Name}({string.Join(", ", args)})"; - return ConvertMethodInvocation(body, attribute); - } - } - } -} diff --git a/src/cs/Bootsharp.Generate/Emitters/ImportType.cs b/src/cs/Bootsharp.Generate/Emitters/ImportType.cs deleted file mode 100644 index 9edfd7f2..00000000 --- a/src/cs/Bootsharp.Generate/Emitters/ImportType.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Bootsharp.Generate; - -internal sealed class ImportType (Compilation compilation, ITypeSymbol type, AttributeData attribute) -{ - public string Name { get; } = type.Name; - - private string specType; - - public static IEnumerable Resolve (Compilation compilation) => - compilation.Assembly.GetAttributes().FirstOrDefault(IsImportAttribute) is { } attribute - ? attribute.ConstructorArguments[0].Values - .Select(v => v.Value).OfType() - .Where(t => t.TypeKind == TypeKind.Interface) - .Select(t => new ImportType(compilation, t, attribute)) - : Array.Empty(); - - public string EmitSource () - { - specType = BuildSyntax(type); - var implType = BuildBindingType(type); - var methods = type.GetMembers().OfType().ToArray(); - var space = BuildBindingNamespace(type); - return EmitCommon - ($$""" - namespace {{space}}; - - public class {{implType}} : {{specType}} - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "{{space}}.{{implType}}", "{{compilation.Assembly.Name}}")] - internal static void RegisterDynamicDependencies () - { - Bootsharp.BindingRegistry.Register(typeof({{specType}}), new ImportBinding(new {{implType}}())); - } - - {{string.Join("\n ", methods.Select(EmitBinding))}} - - {{string.Join("\n ", methods.Select(EmitSpec))}} - } - """); - } - - private string EmitBinding (IMethodSymbol method) - { - var @event = IsEvent(method.Name, attribute); - var space = ConvertNamespace(BuildBindingNamespace(method.ContainingType), compilation.Assembly); - var name = ConvertMethodName(method.Name, attribute); - new BindingEmitter(method, space, name).Emit(out var sig, out var body); - var attr = @event ? $"[{EventAttribute}]" : $"[{FunctionAttribute}]"; - return $"{attr} public static {sig} => {ConvertMethodInvocation(body, attribute)};"; - } - - private string EmitSpec (IMethodSymbol method) - { - var args = method.Parameters.Select(p => $"{BuildSyntax(p.Type)} {p.Name}"); - var sig = $"{BuildSyntax(method.ReturnType)} {specType}.{method.Name} ({string.Join(", ", args)})"; - var methodName = ConvertMethodName(method.Name, attribute); - var @params = method.Parameters.Select(p => p.Name); - return $"{sig} => {methodName}({string.Join(", ", @params)});"; - } -} diff --git a/src/cs/Bootsharp.Generate/Emitters/PartialClass.cs b/src/cs/Bootsharp.Generate/Emitters/PartialClass.cs deleted file mode 100644 index 3c07a1cb..00000000 --- a/src/cs/Bootsharp.Generate/Emitters/PartialClass.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Bootsharp.Generate; - -internal sealed class PartialClass ( - Compilation compilation, - ClassDeclarationSyntax syntax, - IReadOnlyList methods, - bool emitDependenciesRegistration -) -{ - public string Name { get; } = syntax.Identifier.ToString(); - - public string EmitSource () => EmitCommon( - EmitUsings() + - WrapNamespace( - EmitHeader() + - EmitDynamicDependenciesRegistration() + - EmitMethods() + - EmitFooter() - ) - ); - - private string EmitUsings () - { - var imports = syntax.SyntaxTree.GetRoot().DescendantNodesAndSelf() - .OfType().Where(u => u.Name?.ToString() != "Bootsharp"); - var result = string.Join("\n", imports); - return string.IsNullOrEmpty(result) ? "" : result + "\n\n"; - } - - private string EmitHeader () => $"{syntax.Modifiers} class {syntax.Identifier}\n{{"; - - private string EmitDynamicDependenciesRegistration () - { - if (!emitDependenciesRegistration) return ""; - var space = syntax.Parent is BaseNamespaceDeclarationSyntax decl ? $"{decl.Name}." : ""; - var fullClassName = space + syntax.Identifier; - var assemblyName = compilation.Assembly.Name; - return $$""" - - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "{{fullClassName}}", "{{assemblyName}}")] - internal static void RegisterDynamicDependencies () { } - """; - } - - private string EmitMethods () - { - if (methods.Count == 0) return ""; - var sources = methods.Select(m => " " + m.EmitSource(compilation)); - return "\n\n" + string.Join("\n", sources); - } - - private string EmitFooter () => "\n}"; - - private string WrapNamespace (string source) - { - if (syntax.Parent is NamespaceDeclarationSyntax space) - return $$""" - namespace {{space.Name}} - { - {{string.Join("\n", source.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) - .Select((s, i) => i > 0 && s.Length > 0 ? " " + s : s))}} - } - """; - if (syntax.Parent is FileScopedNamespaceDeclarationSyntax fileSpace) - return $"namespace {fileSpace.Name};\n\n{source}"; - return source; - } -} diff --git a/src/cs/Bootsharp.Generate/Emitters/PartialMethod.cs b/src/cs/Bootsharp.Generate/Emitters/PartialMethod.cs deleted file mode 100644 index 8a88cabd..00000000 --- a/src/cs/Bootsharp.Generate/Emitters/PartialMethod.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Bootsharp.Generate; - -internal sealed class PartialMethod (MethodDeclarationSyntax syntax) -{ - public string EmitSource (Compilation compilation) - { - var symbol = compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax)!; - var space = GetNamespace(symbol, compilation); - new BindingEmitter(symbol, space, symbol.Name).Emit(out var sig, out var body); - return $"{syntax.Modifiers} {sig} => {body};".Replace("partial async", "async partial"); - } - - private string GetNamespace (IMethodSymbol symbol, Compilation compilation) - { - var space = symbol.ContainingNamespace.IsGlobalNamespace ? "Global" - : string.Join(".", symbol.ContainingNamespace.ConstituentNamespaces); - return ConvertNamespace(space, symbol.ContainingAssembly); - } -} diff --git a/src/cs/Bootsharp.Generate/SourceGenerator.cs b/src/cs/Bootsharp.Generate/SourceGenerator.cs deleted file mode 100644 index ef7d3a65..00000000 --- a/src/cs/Bootsharp.Generate/SourceGenerator.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Bootsharp.Generate; - -[Generator(LanguageNames.CSharp)] -public sealed class SourceGenerator : IIncrementalGenerator -{ - public void Initialize (IncrementalGeneratorInitializationContext context) => context - .RegisterSourceOutput(context.CompilationProvider, Compile); - - private static void Compile (SourceProductionContext context, Compilation compilation) - { - CompileGlobal(context, compilation); - CompilePartial(context, compilation); - } - - private static void CompileGlobal (SourceProductionContext context, Compilation compilation) - { - foreach (var type in ExportType.Resolve(compilation)) - context.AddSource($"{type.Name}Export.g", type.EmitSource()); - foreach (var type in ImportType.Resolve(compilation)) - context.AddSource($"{type.Name}Import.g", type.EmitSource()); - } - - private static void CompilePartial (SourceProductionContext context, Compilation compilation) - { - var receiver = VisitNodes(compilation); - foreach (var @class in receiver.InvokableClasses) - context.AddSource($"{@class.Name}Invokable.g", @class.EmitSource()); - foreach (var @class in receiver.FunctionClasses) - context.AddSource($"{@class.Name}Functions.g", @class.EmitSource()); - foreach (var @class in receiver.EventClasses) - context.AddSource($"{@class.Name}Events.g", @class.EmitSource()); - } - - private static SyntaxReceiver VisitNodes (Compilation compilation) - { - var receiver = new SyntaxReceiver(); - foreach (var tree in compilation.SyntaxTrees) - if (!tree.FilePath.EndsWith(".g.cs")) - foreach (var node in tree.GetRoot().DescendantNodesAndSelf()) - receiver.VisitNode(node, compilation); - return receiver; - } -} diff --git a/src/cs/Bootsharp.Generate/SyntaxReceiver.cs b/src/cs/Bootsharp.Generate/SyntaxReceiver.cs deleted file mode 100644 index 47dda6ef..00000000 --- a/src/cs/Bootsharp.Generate/SyntaxReceiver.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Bootsharp.Generate; - -internal sealed class SyntaxReceiver -{ - public List InvokableClasses { get; } = []; - public List FunctionClasses { get; } = []; - public List EventClasses { get; } = []; - - public void VisitNode (SyntaxNode node, Compilation compilation) - { - if (node is ClassDeclarationSyntax classSyntax) - VisitClass(classSyntax, compilation); - } - - private void VisitClass (ClassDeclarationSyntax syntax, Compilation compilation) - { - var invokable = GetMethodsWithAttribute(syntax, InvokableAttribute); - if (invokable.Count > 0) InvokableClasses.Add(new(compilation, syntax, Array.Empty(), true)); - var functions = GetMethodsWithAttribute(syntax, FunctionAttribute); - if (functions.Count > 0) FunctionClasses.Add(new(compilation, syntax, functions, invokable.Count == 0)); - var events = GetMethodsWithAttribute(syntax, EventAttribute); - if (events.Count > 0) EventClasses.Add(new(compilation, syntax, events, invokable.Count + functions.Count == 0)); - } - - private List GetMethodsWithAttribute (ClassDeclarationSyntax syntax, string attribute) - { - return syntax.Members - .OfType() - .Where(s => HasAttribute(s, attribute)) - .Select(m => new PartialMethod(m)).ToList(); - } - - private bool HasAttribute (MethodDeclarationSyntax syntax, string attributeName) - { - return syntax.AttributeLists - .SelectMany(l => l.Attributes) - .Any(a => a.ToString().Contains(attributeName)); - } -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs index 6213b6d0..820ae134 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs @@ -2,37 +2,31 @@ public class EmitTest : TaskTest { - protected string GeneratedInvokables => ReadProjectFile(invokablesPath); - protected string GeneratedFunctions => ReadProjectFile(functionsPath); - protected string GeneratedEvents => ReadProjectFile(eventsPath); protected string GeneratedExports => ReadProjectFile(exportsPath); protected string GeneratedImports => ReadProjectFile(importsPath); - protected string GeneratedInteropExports => ReadProjectFile(interopExportsPath); - protected string GeneratedInteropImports => ReadProjectFile(interopImportsPath); + protected string GeneratedInterceptors => ReadProjectFile(interceptorsPath); + protected string GeneratedDependencies => ReadProjectFile(dependenciesPath); protected string GeneratedSerializer => ReadProjectFile(serializerPath); + protected string GeneratedInterop => ReadProjectFile(interopPath); - private string invokablesPath => $"{Project.Root}/Invokables.g.cs"; - private string functionsPath => $"{Project.Root}/Functions.g.cs"; - private string eventsPath => $"{Project.Root}/Events.g.cs"; private string exportsPath => $"{Project.Root}/Exports.g.cs"; private string importsPath => $"{Project.Root}/Imports.g.cs"; - private string interopExportsPath => $"{Project.Root}/InteropExports.g.cs"; - private string interopImportsPath => $"{Project.Root}/InteropImports.g.cs"; + private string interceptorsPath => $"{Project.Root}/Interceptors.g.cs"; + private string dependenciesPath => $"{Project.Root}/Dependencies.g.cs"; private string serializerPath => $"{Project.Root}/SerializerContext.g.cs"; + private string interopPath => $"{Project.Root}/Interop.g.cs"; public override void Execute () => CreateTask().Execute(); private BootsharpEmit CreateTask () => new() { InspectedDirectory = Project.Root, EntryAssemblyName = LastAddedAssemblyName ?? "System.Runtime.dll", - InvokablesFilePath = invokablesPath, - FunctionsFilePath = functionsPath, - EventsFilePath = eventsPath, ExportsFilePath = exportsPath, ImportsFilePath = importsPath, - InteropExportsFilePath = interopExportsPath, - InteropImportsFilePath = interopImportsPath, + InterceptorsFilePath = interceptorsPath, + DependenciesFilePath = dependenciesPath, SerializerFilePath = serializerPath, + InteropFilePath = interceptorsPath, BuildEngine = Engine }; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs deleted file mode 100644 index ccd6c718..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/EventTest.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class EventTest : EmitTest -{ - protected override string TestedContent => GeneratedEvents; - - [Fact] - public void WhenNothingInspectedNothingIsGenerated () - { - Execute(); - Assert.Empty(TestedContent); - } - - [Fact] - public void WhenNoEventsNothingIsGenerated () - { - AddAssembly(WithClass("[JSFunction] public static void Foo () {}")); - Execute(); - Assert.Empty(TestedContent); - } -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs deleted file mode 100644 index a3082b68..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/FunctionTest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class FunctionTest : EmitTest -{ - protected override string TestedContent => GeneratedFunctions; -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropExportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropExportTest.cs deleted file mode 100644 index 857df917..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropExportTest.cs +++ /dev/null @@ -1,186 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class InteropExportTest : EmitTest -{ - protected override string TestedContent => GeneratedInteropExports; - - [Fact] - public void WhenNothingInspectedNothingIsGenerated () - { - Execute(); - Assert.Empty(TestedContent); - } - - [Fact] - public void WhenNoInvokablesNothingIsGenerated () - { - AddAssembly(WithClass("[JSFunction] public static void Foo () {}")); - Execute(); - Assert.Empty(TestedContent); - } - - [Fact] - public void ExportsInvokableWithoutSpace () - { - AddAssembly(With( - """ - public class Foo - { - [JSInvokable] public static void Bar () {} - } - """)); - Execute(); - Contains( - """ - using System.Runtime.InteropServices.JavaScript; - using static Bootsharp.Serializer; - - namespace Bootsharp.Exports; - - public partial class Foo - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static void Bar () => global::Foo.Bar(); - } - """); - } - - [Fact] - public void ExportsInvokableFromMultipleSpaces () - { - AddAssembly(With( - """ - namespace Foo.Bar - { - public class Baz - { - [JSInvokable] public static void Nya () {} - } - } - namespace Foo - { - public class Baz - { - [JSInvokable] public static void Nya () {} - } - } - public class Baz - { - [JSInvokable] public static void Nya () {} - } - """)); - Execute(); - Contains( - """ - using System.Runtime.InteropServices.JavaScript; - using static Bootsharp.Serializer; - - namespace Bootsharp.Exports; - - public partial class Baz - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static void Nya () => global::Baz.Nya(); - } - public partial class Foo_Baz - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static void Nya () => global::Foo.Baz.Nya(); - } - public partial class Foo_Bar_Baz - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static void Nya () => global::Foo.Bar.Baz.Nya(); - } - """); - } - - [Fact] - public void ExportsMultipleInvokable () - { - AddAssembly(With( - """ - namespace Space; - - public record Info(string Foo); - - public class Foo - { - [JSInvokable] public static void Void () {} - [JSInvokable] public static Info WithArgs (Info a, int[] b) => default; - [JSInvokable] public static Task Async () => default; - [JSInvokable] public static Task AsyncWithArgs (Info? i) => default; - } - """)); - Execute(); - Contains( - """ - using System.Runtime.InteropServices.JavaScript; - using static Bootsharp.Serializer; - - namespace Bootsharp.Exports; - - public partial class Space_Foo - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static void Void () => global::Space.Foo.Void(); - [System.Runtime.InteropServices.JavaScript.JSExport] internal static global::System.String WithArgs (global::System.String a, global::System.Int32[] b) => Serialize(global::Space.Foo.WithArgs(Deserialize(a), b)); - [System.Runtime.InteropServices.JavaScript.JSExport] internal static global::System.Threading.Tasks.Task Async () => global::Space.Foo.Async(); - [System.Runtime.InteropServices.JavaScript.JSExport] internal static async global::System.Threading.Tasks.Task AsyncWithArgs (global::System.String? i) => Serialize(await global::Space.Foo.AsyncWithArgs(Deserialize(i))); - } - """); - } - - [Fact] - public void DoesntSerializeTypesThatShouldNotBeSerialized () - { - AddAssembly(With( - """ - public class Foo - { - [JSInvokable] public static Task Bar (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; - [JSInvokable] public static Task Baz (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; - } - """)); - Execute(); - Contains( - """ - using System.Runtime.InteropServices.JavaScript; - using static Bootsharp.Serializer; - - namespace Bootsharp.Exports; - - public partial class Foo - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static global::System.Threading.Tasks.Task Bar (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Foo.Bar(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); - [System.Runtime.InteropServices.JavaScript.JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Baz (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16) => global::Foo.Baz(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); - } - """); - } - - [Fact] - public void SerializeTypesThatShouldBeSerialized () - { - AddAssembly(With( - """ - namespace Space; - - public record Info; - - public class Foo - { - [JSInvokable] public static Task A () => default; - [JSInvokable] public static Task B () => default; - } - """)); - Execute(); - Contains( - """ - using System.Runtime.InteropServices.JavaScript; - using static Bootsharp.Serializer; - - namespace Bootsharp.Exports; - - public partial class Space_Foo - { - [System.Runtime.InteropServices.JavaScript.JSExport] internal static async global::System.Threading.Tasks.Task A () => Serialize(await global::Space.Foo.A()); - [System.Runtime.InteropServices.JavaScript.JSExport] internal static async global::System.Threading.Tasks.Task B () => Serialize(await global::Space.Foo.B()); - } - """); - } -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropImportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropImportTest.cs deleted file mode 100644 index d75cea70..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropImportTest.cs +++ /dev/null @@ -1,213 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class InteropImportTest : EmitTest -{ - protected override string TestedContent => GeneratedInteropImports; - - [Fact] - public void WhenNothingInspectedNothingIsGenerated () - { - Execute(); - Assert.Empty(TestedContent); - } - - [Fact] - public void WhenNoFunctionsOrEventsNothingIsGenerated () - { - AddAssembly(WithClass("[JSInvokable] public static void Foo () {}")); - Execute(); - Assert.Empty(TestedContent); - } - - [Fact] - public void ImportsWithoutSpace () - { - AddAssembly("asm.dll", With( - """ - public class Foo - { - [JSFunction] public static void Bar () {} - [JSEvent] public static void Baz () {} - } - """)); - Execute(); - Contains( - """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices.JavaScript; - - namespace Bootsharp.Imports; - - public partial class Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.Foo", "asm")] - internal static void RegisterDynamicDependencies () - { - Function.Set("Global.bar", Bar); - Function.Set("Global.baz", Baz); - } - [System.Runtime.InteropServices.JavaScript.JSImport("Global.barSerialized", "Bootsharp")] internal static partial void Bar (); - [System.Runtime.InteropServices.JavaScript.JSImport("Global.bazSerialized", "Bootsharp")] internal static partial void Baz (); - } - """); - } - - [Fact] - public void ImportsFromMultipleSpaces () - { - AddAssembly("asm.dll", With( - """ - namespace Foo.Bar - { - public class Baz - { - [JSFunction] public static void Nya () {} - [JSEvent] public static void Far () {} - } - } - namespace Foo - { - public class Baz - { - [JSFunction] public static void Nya () {} - [JSEvent] public static void Far () {} - } - } - public class Baz - { - [JSFunction] public static void Nya () {} - [JSEvent] public static void Far () {} - } - """)); - Execute(); - Contains( - """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices.JavaScript; - - namespace Bootsharp.Imports; - - public partial class Baz - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.Baz", "asm")] - internal static void RegisterDynamicDependencies () - { - Function.Set("Global.nya", Nya); - Function.Set("Global.far", Far); - } - [System.Runtime.InteropServices.JavaScript.JSImport("Global.nyaSerialized", "Bootsharp")] internal static partial void Nya (); - [System.Runtime.InteropServices.JavaScript.JSImport("Global.farSerialized", "Bootsharp")] internal static partial void Far (); - } - public partial class Foo_Baz - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.Foo_Baz", "asm")] - internal static void RegisterDynamicDependencies () - { - Function.Set("Foo.nya", Nya); - Function.Set("Foo.far", Far); - } - [System.Runtime.InteropServices.JavaScript.JSImport("Foo.nyaSerialized", "Bootsharp")] internal static partial void Nya (); - [System.Runtime.InteropServices.JavaScript.JSImport("Foo.farSerialized", "Bootsharp")] internal static partial void Far (); - } - public partial class Foo_Bar_Baz - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.Foo_Bar_Baz", "asm")] - internal static void RegisterDynamicDependencies () - { - Function.Set("Foo.Bar.nya", Nya); - Function.Set("Foo.Bar.far", Far); - } - [System.Runtime.InteropServices.JavaScript.JSImport("Foo.Bar.nyaSerialized", "Bootsharp")] internal static partial void Nya (); - [System.Runtime.InteropServices.JavaScript.JSImport("Foo.Bar.farSerialized", "Bootsharp")] internal static partial void Far (); - } - """); - } - - [Fact] - public void ImportsFunctionsAndEvents () - { - AddAssembly("asm.dll", With( - """ - namespace Space; - - public record Info(string Foo); - - public class Foo - { - [JSFunction] public static Info Bar (string a, int[] b) => default; - [JSFunction] public static Task Baz () => default; - [JSFunction] public static Task Nya (Info a) => default; - [JSEvent] public static void OnBar (Info? a, bool? b) {} - } - """)); - Execute(); - Contains( - """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices.JavaScript; - - namespace Bootsharp.Imports; - - public partial class Space_Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.Space_Foo", "asm")] - internal static void RegisterDynamicDependencies () - { - Function.Set("Space.bar", Bar); - Function.Set("Space.baz", Baz); - Function.Set("Space.nya", Nya); - Function.Set("Space.onBar", OnBar); - } - [System.Runtime.InteropServices.JavaScript.JSImport("Space.barSerialized", "Bootsharp")] internal static partial global::System.String Bar (global::System.String a, global::System.Int32[] b); - [System.Runtime.InteropServices.JavaScript.JSImport("Space.bazSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Baz (); - [System.Runtime.InteropServices.JavaScript.JSImport("Space.nyaSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Nya (global::System.String a); - [System.Runtime.InteropServices.JavaScript.JSImport("Space.onBarSerialized", "Bootsharp")] internal static partial void OnBar (global::System.String? a, global::System.Boolean? b); - } - """); - } - - [Fact] - public void DoesntSerializeTypesThatShouldNotBeSerialized () - { - AddAssembly("asm.dll", With( - """ - namespace Space; - - public class Foo - { - [JSFunction] public static Task Bar (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; - [JSFunction] public static Task Baz (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; - } - """)); - Execute(); - Contains( - """ - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices.JavaScript; - - namespace Bootsharp.Imports; - - public partial class Space_Foo - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.Space_Foo", "asm")] - internal static void RegisterDynamicDependencies () - { - Function.Set("Space.bar", Bar); - Function.Set("Space.baz", Baz); - } - [System.Runtime.InteropServices.JavaScript.JSImport("Space.barSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Bar (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16); - [System.Runtime.InteropServices.JavaScript.JSImport("Space.bazSerialized", "Bootsharp")] [return: JSMarshalAs>] internal static partial global::System.Threading.Tasks.Task Baz (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16); - } - """); - } -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs deleted file mode 100644 index 9dd4761d..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/InvokableTest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class InvokableTest : EmitTest -{ - protected override string TestedContent => GeneratedInvokables; -} diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 44b519b6..b1c8af02 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -9,27 +9,23 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { [Required] public required string InspectedDirectory { get; set; } [Required] public required string EntryAssemblyName { get; set; } - [Required] public required string InvokablesFilePath { get; set; } - [Required] public required string FunctionsFilePath { get; set; } - [Required] public required string EventsFilePath { get; set; } [Required] public required string ExportsFilePath { get; set; } [Required] public required string ImportsFilePath { get; set; } - [Required] public required string InteropExportsFilePath { get; set; } - [Required] public required string InteropImportsFilePath { get; set; } + [Required] public required string InterceptorsFilePath { get; set; } + [Required] public required string DependenciesFilePath { get; set; } [Required] public required string SerializerFilePath { get; set; } + [Required] public required string InteropFilePath { get; set; } public override bool Execute () { var spaceBuilder = CreateNamespaceBuilder(); using var inspection = InspectAssemblies(spaceBuilder); - GenerateInvokables(inspection); - GenerateFunctions(inspection); - GenerateEvents(inspection); GenerateExports(inspection); GenerateImports(inspection); - GenerateInteropExports(inspection); - GenerateInteropImports(inspection); + GenerateInterceptors(inspection); + GenerateDependencies(inspection); GenerateSerializer(inspection); + GenerateInterop(inspection); return true; } @@ -48,27 +44,6 @@ private AssemblyInspection InspectAssemblies (NamespaceBuilder spaceBuilder) return inspection; } - private void GenerateInvokables (AssemblyInspection inspection) - { - var generator = new InvokableGenerator(); - var content = generator.Generate(inspection); - WriteGenerated(InvokablesFilePath, content); - } - - private void GenerateFunctions (AssemblyInspection inspection) - { - var generator = new FunctionGenerator(); - var content = generator.Generate(inspection); - WriteGenerated(FunctionsFilePath, content); - } - - private void GenerateEvents (AssemblyInspection inspection) - { - var generator = new EventGenerator(); - var content = generator.Generate(inspection); - WriteGenerated(EventsFilePath, content); - } - private void GenerateExports (AssemblyInspection inspection) { var generator = new ExportGenerator(); @@ -83,18 +58,18 @@ private void GenerateImports (AssemblyInspection inspection) WriteGenerated(ImportsFilePath, content); } - private void GenerateInteropExports (AssemblyInspection inspection) + private void GenerateInterceptors (AssemblyInspection inspection) { - var generator = new InteropExportGenerator(); + var generator = new InterceptorGenerator(); var content = generator.Generate(inspection); - WriteGenerated(InteropExportsFilePath, content); + WriteGenerated(InterceptorsFilePath, content); } - private void GenerateInteropImports (AssemblyInspection inspection) + private void GenerateDependencies (AssemblyInspection inspection) { - var generator = new InteropImportGenerator(EntryAssemblyName); + var generator = new DependenciesGenerator(); var content = generator.Generate(inspection); - WriteGenerated(InteropImportsFilePath, content); + WriteGenerated(DependenciesFilePath, content); } private void GenerateSerializer (AssemblyInspection inspection) @@ -104,6 +79,13 @@ private void GenerateSerializer (AssemblyInspection inspection) WriteGenerated(SerializerFilePath, content); } + private void GenerateInterop (AssemblyInspection inspection) + { + var generator = new InteropGenerator(); + var content = generator.Generate(inspection); + WriteGenerated(InteropFilePath, content); + } + private static void WriteGenerated (string path, string content) { Directory.CreateDirectory(Path.GetDirectoryName(path)!); diff --git a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs new file mode 100644 index 00000000..08031bc6 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +/// +/// Generates hints for DotNet to not trim specified dynamic dependencies, ie +/// members that are not statically accessed in the user source code. +/// +internal sealed class DependenciesGenerator +{ + public string Generate (AssemblyInspection inspection) => ""; +} diff --git a/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs b/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs deleted file mode 100644 index 9f76229e..00000000 --- a/src/cs/Bootsharp.Publish/Emit/EventGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// Generates interceptors for methods -/// in the solution (except methods generated by ). -/// -internal sealed class EventGenerator -{ - public string Generate (AssemblyInspection inspection) => ""; -} diff --git a/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropExportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropExportGenerator.cs deleted file mode 100644 index 7bd2f5c8..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropExportGenerator.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// For all the methods in the solution, -/// generates C# -> JS bindings to be picked by DotNet's source generators. -/// -internal sealed class InteropExportGenerator -{ - public string Generate (AssemblyInspection inspection) - { - var bySpace = inspection.Methods - .Where(m => m.Type == MethodType.Invokable) - .GroupBy(i => i.Space).ToArray(); - return bySpace.Length == 0 ? "" : - $""" - #nullable enable - #pragma warning disable - - using System.Runtime.InteropServices.JavaScript; - using static Bootsharp.Serializer; - - namespace Bootsharp.Exports; - - {JoinLines(bySpace.Select(g => GenerateSpace(g.Key, g)), 0)} - - #pragma warning restore - #nullable restore - """; - } - - private string GenerateSpace (string space, IEnumerable invokable) => - $$""" - public partial class {{space.Replace('.', '_')}} - { - {{JoinLines(invokable.Select(GenerateExport))}} - } - """; - - private string GenerateExport (MethodMeta inv) - { - const string attr = "[System.Runtime.InteropServices.JavaScript.JSExport]"; - var date = MarshalAmbiguous(inv.ReturnValue.TypeSyntax, true); - var wait = inv.ReturnValue.Async && inv.ReturnValue.Serialized; - return $"{attr} {date}internal static {GenerateSignature(inv, wait)} => {GenerateBody(inv, wait)};"; - } - - private string GenerateSignature (MethodMeta inv, bool wait) - { - var args = string.Join(", ", inv.Arguments.Select(GenerateSignatureArg)); - var @return = inv.ReturnValue.Void ? "void" : (inv.ReturnValue.Serialized - ? $"global::System.String{(inv.ReturnValue.Nullable ? "?" : "")}" - : inv.ReturnValue.TypeSyntax); - if (inv.ReturnValue.Serialized && inv.ReturnValue.Async) - @return = $"global::System.Threading.Tasks.Task<{@return}>"; - var signature = $"{@return} {inv.Name} ({args})"; - if (wait) signature = $"async {signature}"; - return signature; - } - - private string GenerateBody (MethodMeta inv, bool wait) - { - var args = string.Join(", ", inv.Arguments.Select(GenerateBodyArg)); - var body = $"global::{inv.Space}.{inv.Name}({args})"; - if (wait) body = $"await {body}"; - if (inv.ReturnValue.Serialized) body = $"Serialize({body})"; - return body; - } - - private string GenerateSignatureArg (ArgumentMeta arg) - { - var type = arg.Value.Serialized - ? $"global::System.String{(arg.Value.Nullable ? "?" : "")}" - : arg.Value.TypeSyntax; - return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; - } - - private string GenerateBodyArg (ArgumentMeta arg) - { - if (!arg.Value.Serialized) return arg.Name; - return $"Deserialize<{arg.Value.TypeSyntax}>({arg.Name})"; - } -} diff --git a/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropImportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropImportGenerator.cs deleted file mode 100644 index 14dab858..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ForDotNet/InteropImportGenerator.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// For all the and -/// methods in the solution, -/// generates JS -> C# bindings to be picked by DotNet's source generators. -/// -internal sealed class InteropImportGenerator (string entryAssembly) -{ - public string Generate (AssemblyInspection inspection) - { - var bySpace = inspection.Methods - .Where(m => m.Type != MethodType.Invokable) - .GroupBy(i => i.Space).ToArray(); - return bySpace.Length == 0 ? "" : - $""" - #nullable enable - #pragma warning disable - - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices.JavaScript; - - namespace Bootsharp.Imports; - - {JoinLines(bySpace.Select(g => GenerateSpace(g.Key, g.ToArray())), 0)} - - #pragma warning restore - #nullable restore - """; - } - - private string GenerateSpace (string space, IReadOnlyList methods) - { - var name = space.Replace('.', '_'); - var asm = entryAssembly[..^4]; - return - $$""" - public partial class {{name}} - { - [ModuleInitializer] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Imports.{{name}}", "{{asm}}")] - internal static void RegisterDynamicDependencies () - { - {{JoinLines(methods.Select(GenerateFunctionAssign), 2)}} - } - {{JoinLines(methods.Select(GenerateImport))}} - } - """; - } - - private string GenerateFunctionAssign (MethodMeta method) - { - return $"""Function.Set("{BuildEndpoint(method, false)}", {method.Name});"""; - } - - private string GenerateImport (MethodMeta method) - { - var args = string.Join(", ", method.Arguments.Select(GenerateArg)); - var @return = method.ReturnValue.Void ? "void" : (method.ReturnValue.Serialized - ? $"global::System.String{(method.ReturnValue.Nullable ? "?" : "")}" - : method.ReturnValue.TypeSyntax); - if (method.ReturnValue.Serialized && method.ReturnValue.Async) - @return = $"global::System.Threading.Tasks.Task<{@return}>"; - var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{BuildEndpoint(method, true)}", "Bootsharp")]"""; - var date = MarshalAmbiguous(method.ReturnValue.TypeSyntax, true); - return $"{attr} {date}internal static partial {@return} {method.Name} ({args});"; - } - - private string GenerateArg (ArgumentMeta arg) - { - var type = arg.Value.Serialized - ? $"global::System.String{(arg.Value.Nullable ? "?" : "")}" - : arg.Value.TypeSyntax; - return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; - } - - private string BuildEndpoint (MethodMeta method, bool import) - { - var name = char.ToLowerInvariant(method.Name[0]) + method.Name[1..]; - return $"{method.JSSpace}.{name}{(import ? "Serialized" : "")}"; - } -} diff --git a/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs similarity index 60% rename from src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs rename to src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs index 5234f759..cdbc35da 100644 --- a/src/cs/Bootsharp.Publish/Emit/FunctionGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs @@ -1,10 +1,10 @@ namespace Bootsharp.Publish; /// -/// Generates interceptors for methods -/// in the solution (except methods generated by ). +/// Generates interceptors for and +/// interop methods declared by user. /// -internal sealed class FunctionGenerator +internal sealed class InterceptorGenerator { public string Generate (AssemblyInspection inspection) => ""; } diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs new file mode 100644 index 00000000..d12e04c8 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -0,0 +1,9 @@ +namespace Bootsharp.Publish; + +/// +/// Generates bindings to be picked by DotNet's interop source generator. +/// +internal sealed class InteropGenerator +{ + public string Generate (AssemblyInspection inspection) => ""; +} diff --git a/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs deleted file mode 100644 index e04f8a1c..00000000 --- a/src/cs/Bootsharp.Publish/Emit/InvokableGenerator.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// Generates hints for DotNet to not trim classes with -/// methods (they're used by -/// generated interop bindings, which is not obvious for DotNet), except -/// methods generated by . -/// -internal sealed class InvokableGenerator -{ - public string Generate (AssemblyInspection inspection) => ""; -} diff --git a/src/cs/Bootsharp.Publish/Emit/ForDotNet/SerializerGenerator.cs b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs similarity index 100% rename from src/cs/Bootsharp.Publish/Emit/ForDotNet/SerializerGenerator.cs rename to src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs diff --git a/src/cs/Bootsharp.sln b/src/cs/Bootsharp.sln index f9ed4695..70e5e32e 100644 --- a/src/cs/Bootsharp.sln +++ b/src/cs/Bootsharp.sln @@ -5,10 +5,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Common", "Bootsha EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Common.Test", "Bootsharp.Common.Test/Bootsharp.Common.Test.csproj", "{AEE52DF2-26C4-479B-B62D-14B58903BCE7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Generate", "Bootsharp.Generate/Bootsharp.Generate.csproj", "{F395C565-EAFD-4F89-A7DD-4CA12761A86B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Generate.Test", "Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj", "{B17E4577-6391-4669-9B12-083CA18ADF33}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Publish", "Bootsharp.Publish/Bootsharp.Publish.csproj", "{4CB73FC6-D64D-470A-8490-7604A292B973}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Publish.Test", "Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj", "{62571679-675C-4E85-AB3F-5C243920EE6B}" @@ -23,10 +19,6 @@ Global {8D23B7D8-3E5D-420D-80FF-6A437B03D303}.Release|Any CPU.Build.0 = Release|Any CPU {8D23B7D8-3E5D-420D-80FF-6A437B03D303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D23B7D8-3E5D-420D-80FF-6A437B03D303}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Release|Any CPU.Build.0 = Release|Any CPU - {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Debug|Any CPU.Build.0 = Debug|Any CPU {4CB73FC6-D64D-470A-8490-7604A292B973}.Release|Any CPU.ActiveCfg = Release|Any CPU {4CB73FC6-D64D-470A-8490-7604A292B973}.Release|Any CPU.Build.0 = Release|Any CPU {4CB73FC6-D64D-470A-8490-7604A292B973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -35,10 +27,6 @@ Global {AEE52DF2-26C4-479B-B62D-14B58903BCE7}.Release|Any CPU.Build.0 = Release|Any CPU {AEE52DF2-26C4-479B-B62D-14B58903BCE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEE52DF2-26C4-479B-B62D-14B58903BCE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B17E4577-6391-4669-9B12-083CA18ADF33}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B17E4577-6391-4669-9B12-083CA18ADF33}.Release|Any CPU.Build.0 = Release|Any CPU - {B17E4577-6391-4669-9B12-083CA18ADF33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B17E4577-6391-4669-9B12-083CA18ADF33}.Debug|Any CPU.Build.0 = Debug|Any CPU {62571679-675C-4E85-AB3F-5C243920EE6B}.Release|Any CPU.ActiveCfg = Release|Any CPU {62571679-675C-4E85-AB3F-5C243920EE6B}.Release|Any CPU.Build.0 = Release|Any CPU {62571679-675C-4E85-AB3F-5C243920EE6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 110426df..4cf46fec 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -5,14 +5,12 @@ $(BootsharpRoot)/js $(BootsharpRoot)/tasks/Bootsharp.Publish.dll $(IntermediateOutputPath)bootsharp - $(BootsharpIntermediateDirectory)/Invokables.g.cs - $(BootsharpIntermediateDirectory)/Functions.g.cs - $(BootsharpIntermediateDirectory)/Events.g.cs $(BootsharpIntermediateDirectory)/Exports.g.cs $(BootsharpIntermediateDirectory)/Imports.g.cs - $(BootsharpIntermediateDirectory)/InteropExports.g.cs - $(BootsharpIntermediateDirectory)/InteropImports.g.cs + $(BootsharpIntermediateDirectory)/Interceptors.g.cs + $(BootsharpIntermediateDirectory)/Dependencies.g.cs $(BootsharpIntermediateDirectory)/SerializerContext.g.cs + $(BootsharpIntermediateDirectory)/Interop.g.cs $(AssemblyName).dll @@ -66,31 +64,25 @@ + InterceptorsFilePath="$(BootsharpInterceptorsFilePath)" + DependenciesFilePath="$(BootsharpDependenciesFilePath)" + SerializerFilePath="$(BootsharpSerializerFilePath)" + InteropFilePath="$(BootsharpInteropFilePath)"/> - - - - - + + - - - + - - + + + From 0e00e5dd4b51261632f93e6ab94e596145553643 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 7 Jan 2024 23:20:33 +0300 Subject: [PATCH 08/75] etc --- src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index b1c8af02..34e959d9 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -86,7 +86,7 @@ private void GenerateInterop (AssemblyInspection inspection) WriteGenerated(InteropFilePath, content); } - private static void WriteGenerated (string path, string content) + private void WriteGenerated (string path, string content) { Directory.CreateDirectory(Path.GetDirectoryName(path)!); File.WriteAllText(path, content); From 327bb7a41deb766f041826cf564186220baba1be Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 7 Jan 2024 23:36:31 +0300 Subject: [PATCH 09/75] iteration --- src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs | 6 ++++++ src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs | 6 ++++++ src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs | 6 ++++++ src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs | 7 ++++++- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs diff --git a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs new file mode 100644 index 00000000..d51e9a5f --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class DependenciesTest : EmitTest +{ + protected override string TestedContent => GeneratedDependencies; +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs new file mode 100644 index 00000000..2d8eebcb --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class InterceptorTest : EmitTest +{ + protected override string TestedContent => GeneratedInterceptors; +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs new file mode 100644 index 00000000..930fef25 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -0,0 +1,6 @@ +namespace Bootsharp.Publish.Test; + +public class InteropTest : EmitTest +{ + protected override string TestedContent => GeneratedInterop; +} diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 34e959d9..abb4e33a 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -3,8 +3,13 @@ namespace Bootsharp.Publish; /// -/// First pass: emits code to be picked by .NET's source generators. +/// First pass: emits C# sources to be picked by .NET's source generators. /// +/// +/// This could've been implemented via a source generator, but .NET's generators +/// are not able to pick output of other generators, hence we're emitting +/// the code at build time before the .NET's sourcegen stage. +/// public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { [Required] public required string InspectedDirectory { get; set; } From 6447a75779cb2b814b68ed55874fdb28f84d7342 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 9 Jan 2024 03:11:51 +0300 Subject: [PATCH 10/75] fix docs last update --- .github/workflows/deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 044f65c8..7360ae4e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,6 +16,8 @@ jobs: name: github-pages steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-node@v3 - name: build run: | From a3e55bc6b99194689951a0cceb02c038f2e2ce49 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:49:58 +0300 Subject: [PATCH 11/75] iteration --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 2 +- src/cs/Bootsharp.Common/Meta/SolutionMeta.cs | 10 +++- .../Emit/DependenciesTest.cs | 45 +++++++++++++++- .../Bootsharp.Publish.Test/Emit/EmitTest.cs | 22 +++++--- .../Emit/InterceptorTest.cs | 6 --- .../Mock/MockCompiler.cs | 10 ++-- .../Mock/MockProject.cs | 3 +- .../AssemblyInspector/AssemblyInspection.cs | 6 ++- .../AssemblyInspector/AssemblyInspector.cs | 53 +++++++++++-------- .../Bootsharp.Publish/Common/TypeUtilities.cs | 4 +- .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 16 +----- .../Emit/DependenciesGenerator.cs | 49 +++++++++++++++-- .../Emit/InterceptorGenerator.cs | 10 ---- .../Emit/SerializerGenerator.cs | 4 +- .../Pack/BindingGenerator/BindingGenerator.cs | 2 +- .../DeclarationGenerator.cs | 2 +- src/cs/Bootsharp/Build/Bootsharp.targets | 6 +-- 17 files changed, 166 insertions(+), 84 deletions(-) delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index d94aa5d5..5d51f961 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -31,7 +31,7 @@ public class TypesTest public void Records () { // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 - _ = new SolutionMeta { Assemblies = [], Methods = [], Types = [] } with { Assemblies = default }; + _ = new SolutionMeta { Assemblies = [], Methods = [], Crawled = [], Exports = [], Imports = [] } with { Assemblies = default }; _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Type = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; _ = new ArgumentMeta { Name = "", JSName = "", Value = default } with { Name = "foo" }; diff --git a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs index e908f12f..b54b750d 100644 --- a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs +++ b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs @@ -18,5 +18,13 @@ public sealed record SolutionMeta /// Types referenced in the interop methods signatures, including /// types associated with the prior types, crawled recursively. /// - public required IReadOnlyCollection Types { get; init; } + public required IReadOnlyCollection Crawled { get; init; } + /// + /// Interface types specified in . + /// + public required IReadOnlyCollection Exports { get; init; } + /// + /// Interface types specified in . + /// + public required IReadOnlyCollection Imports { get; init; } } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs index d51e9a5f..ac69cc94 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs @@ -1,6 +1,49 @@ -namespace Bootsharp.Publish.Test; +using System.Diagnostics.CodeAnalysis; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace Bootsharp.Publish.Test; public class DependenciesTest : EmitTest { protected override string TestedContent => GeneratedDependencies; + + [Fact] + public void AddsCommonGeneratedTypesByDefault () + { + Execute(); + Added(All, "Bootsharp.Generated.Dependencies"); + Added(All, "Bootsharp.Generated.SerializerContext"); + Added(All, "Bootsharp.Generated.Interop"); + } + + [Fact] + public void AddsGeneratedExportTypes () + { + AddAssembly( + With("[assembly:JSExport(typeof(IFoo), typeof(Space.IBar))]"), + With("public interface IFoo {}"), + With("Space", "public interface IBar {}")); + Execute(); + Added(All, "Bootsharp.Generated.Exports.IFoo"); + Added(All, "Bootsharp.Generated.Exports.Space.IBar"); + } + + [Fact] + public void AddsGeneratedImportTypes () + { + AddAssembly( + With("[assembly:JSImport(typeof(IFoo), typeof(Space.IBar))]"), + With("public interface IFoo {}"), + With("Space", "public interface IBar {}")); + Execute(); + Added(All, "Bootsharp.Generated.Imports.IFoo"); + Added(All, "Bootsharp.Generated.Imports.Space.IBar"); + } + + private void Added (DynamicallyAccessedMemberTypes types, string name) => Added(types, name, Task.EntryAssemblyName); + + private void Added (DynamicallyAccessedMemberTypes types, string name, string assembly) + { + Contains($"""[DynamicDependency(DynamicallyAccessedMemberTypes.{types}, "{name}", "{assembly}")]"""); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs index 820ae134..7858e35b 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs @@ -2,31 +2,39 @@ public class EmitTest : TaskTest { + protected BootsharpEmit Task { get; } protected string GeneratedExports => ReadProjectFile(exportsPath); protected string GeneratedImports => ReadProjectFile(importsPath); - protected string GeneratedInterceptors => ReadProjectFile(interceptorsPath); protected string GeneratedDependencies => ReadProjectFile(dependenciesPath); protected string GeneratedSerializer => ReadProjectFile(serializerPath); protected string GeneratedInterop => ReadProjectFile(interopPath); private string exportsPath => $"{Project.Root}/Exports.g.cs"; private string importsPath => $"{Project.Root}/Imports.g.cs"; - private string interceptorsPath => $"{Project.Root}/Interceptors.g.cs"; private string dependenciesPath => $"{Project.Root}/Dependencies.g.cs"; - private string serializerPath => $"{Project.Root}/SerializerContext.g.cs"; + private string serializerPath => $"{Project.Root}/Serializer.g.cs"; private string interopPath => $"{Project.Root}/Interop.g.cs"; - public override void Execute () => CreateTask().Execute(); + public EmitTest () + { + Task = CreateTask(); + } + + public override void Execute () + { + if (LastAddedAssemblyName is not null) + Task.EntryAssemblyName = LastAddedAssemblyName; + Task.Execute(); + } private BootsharpEmit CreateTask () => new() { InspectedDirectory = Project.Root, - EntryAssemblyName = LastAddedAssemblyName ?? "System.Runtime.dll", + EntryAssemblyName = "System.Runtime.dll", ExportsFilePath = exportsPath, ImportsFilePath = importsPath, - InterceptorsFilePath = interceptorsPath, DependenciesFilePath = dependenciesPath, SerializerFilePath = serializerPath, - InteropFilePath = interceptorsPath, + InteropFilePath = interopPath, BuildEngine = Engine }; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs deleted file mode 100644 index 2d8eebcb..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterceptorTest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class InterceptorTest : EmitTest -{ - protected override string TestedContent => GeneratedInterceptors; -} diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs index ff192ecc..f605dd70 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs @@ -14,11 +14,13 @@ public class MockCompiler public void Compile (IEnumerable sources, string assemblyPath) { - var text = string.Join('\n', defaultUsings.Select(u => $"using {u};")) + '\n' + - string.Join('\n', sources.Select(BuildSource)); - var compilation = CreateCompilation(assemblyPath, text); + var source = string.Join('\n', defaultUsings.Select(u => $"using {u};")) + '\n' + + string.Join('\n', sources.Select(BuildSource)); + var compilation = CreateCompilation(assemblyPath, source); var result = compilation.Emit(assemblyPath); - Assert.True(result.Success); + if (result.Success) return; + var error = $"Invalid test source code: {result.Diagnostics.First().GetMessage()}"; + Assert.Fail(string.Join('\n', [error, "---", source, "---"])); } private static string BuildSource (MockSource source) diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs index f59809c0..83b5d531 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs @@ -53,8 +53,7 @@ private static string[] GetReferencePaths () return [ MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.dll")).FilePath, MetadataReference.CreateFromFile(typeof(object).Assembly.Location).FilePath, - MetadataReference.CreateFromFile(typeof(JSFunctionAttribute).Assembly.Location).FilePath, - MetadataReference.CreateFromFile(typeof(JSInvokableAttribute).Assembly.Location).FilePath + MetadataReference.CreateFromFile(typeof(JSExportAttribute).Assembly.Location).FilePath ]; } } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs index 4952a0f0..a6d09fdc 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs @@ -3,11 +3,13 @@ namespace Bootsharp.Publish; -internal record AssemblyInspection (MetadataLoadContext ctx) : IDisposable +internal class AssemblyInspection (MetadataLoadContext ctx) : IDisposable { public ImmutableArray Assemblies { get; init; } = []; public ImmutableArray Methods { get; init; } = []; - public ImmutableArray Types { get; init; } = []; + public ImmutableArray Crawled { get; init; } = []; + public ImmutableArray Exports { get; init; } = []; + public ImmutableArray Imports { get; init; } = []; public ImmutableArray Warnings { get; init; } = []; public void Dispose () => ctx.Dispose(); diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 4c32d7e1..4f46711f 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -7,6 +7,8 @@ internal sealed class AssemblyInspector (NamespaceBuilder spaceBuilder) { private readonly List assemblies = []; private readonly List methods = []; + private readonly List exports = []; + private readonly List imports = []; private readonly List warnings = []; private readonly TypeConverter converter = new(spaceBuilder); @@ -14,16 +16,16 @@ public AssemblyInspection InspectInDirectory (string directory) { var ctx = CreateLoadContext(directory); foreach (var assemblyPath in Directory.GetFiles(directory, "*.dll")) - try { InspectAssembly(assemblyPath, ctx); } + try { InspectAssemblyFile(assemblyPath, ctx); } catch (Exception e) { AddSkippedAssemblyWarning(assemblyPath, e); } return CreateInspection(ctx); } - private void InspectAssembly (string assemblyPath, MetadataLoadContext ctx) + private void InspectAssemblyFile (string assemblyPath, MetadataLoadContext ctx) { assemblies.Add(CreateAssembly(assemblyPath)); if (!ShouldIgnoreAssembly(assemblyPath)) - InspectMethods(ctx.LoadFromAssemblyPath(assemblyPath)); + InspectAssembly(ctx.LoadFromAssemblyPath(assemblyPath)); } private void AddSkippedAssemblyWarning (string assemblyPath, Exception exception) @@ -37,7 +39,9 @@ private void AddSkippedAssemblyWarning (string assemblyPath, Exception exception private AssemblyInspection CreateInspection (MetadataLoadContext ctx) => new(ctx) { Assemblies = assemblies.ToImmutableArray(), Methods = methods.ToImmutableArray(), - Types = converter.CrawledTypes.ToImmutableArray(), + Crawled = converter.CrawledTypes.ToImmutableArray(), + Exports = exports.ToImmutableArray(), + Imports = imports.ToImmutableArray(), Warnings = warnings.ToImmutableArray() }; @@ -46,21 +50,34 @@ private void AddSkippedAssemblyWarning (string assemblyPath, Exception exception Bytes = File.ReadAllBytes(assemblyPath) }; - private void InspectMethods (Assembly assembly) + private void InspectAssembly (Assembly assembly) { - foreach (var method in GetStaticMethods(assembly)) - foreach (var attribute in method.CustomAttributes) - InspectMethodWithAttribute(method, attribute.AttributeType.Name); + foreach (var exported in assembly.GetExportedTypes()) + InspectExportedType(exported); + foreach (var attribute in assembly.CustomAttributes) + InspectAssemblyAttribute(attribute); } - private void InspectMethodWithAttribute (MethodInfo method, string attributeName) + private void InspectExportedType (Type type) { - if (attributeName == nameof(JSInvokableAttribute)) - methods.Add(CreateMethod(method, MethodType.Invokable)); - else if (attributeName == nameof(JSFunctionAttribute)) - methods.Add(CreateMethod(method, MethodType.Function)); - else if (attributeName == nameof(JSEventAttribute)) - methods.Add(CreateMethod(method, MethodType.Event)); + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + foreach (var attr in method.CustomAttributes) + if (attr.AttributeType.FullName == typeof(JSInvokableAttribute).FullName) + methods.Add(CreateMethod(method, MethodType.Invokable)); + else if (attr.AttributeType.FullName == typeof(JSFunctionAttribute).FullName) + methods.Add(CreateMethod(method, MethodType.Function)); + else if (attr.AttributeType.FullName == typeof(JSEventAttribute).FullName) + methods.Add(CreateMethod(method, MethodType.Event)); + } + + private void InspectAssemblyAttribute (CustomAttributeData attribute) + { + if (attribute.AttributeType.FullName == typeof(JSExportAttribute).FullName) + exports.AddRange(((IEnumerable)attribute + .ConstructorArguments[0].Value!).Select(v => (Type)v.Value!)); + else if (attribute.AttributeType.FullName == typeof(JSImportAttribute).FullName) + imports.AddRange(((IEnumerable)attribute + .ConstructorArguments[0].Value!).Select(v => (Type)v.Value!)); } private MethodMeta CreateMethod (MethodInfo info, MethodType type) => new() { @@ -95,10 +112,4 @@ private void InspectMethodWithAttribute (MethodInfo method, string attributeName Serialized = ShouldSerialize(info.ParameterType) } }; - - private static IEnumerable GetStaticMethods (Assembly assembly) - { - var exported = assembly.GetExportedTypes(); - return exported.SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static)); - } } diff --git a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs index 6269cf19..7b72e97b 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs @@ -137,9 +137,9 @@ public static MetadataLoadContext CreateLoadContext (string directory) return new MetadataLoadContext(resolver); } - public static bool ShouldIgnoreAssembly (string assemblyPath) + public static bool ShouldIgnoreAssembly (string filePath) { - var assemblyName = Path.GetFileName(assemblyPath); + var assemblyName = Path.GetFileName(filePath); return assemblyName.StartsWith("System.") || assemblyName.StartsWith("Microsoft.") || assemblyName.StartsWith("netstandard") || diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index abb4e33a..60da9305 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -5,18 +5,12 @@ namespace Bootsharp.Publish; /// /// First pass: emits C# sources to be picked by .NET's source generators. /// -/// -/// This could've been implemented via a source generator, but .NET's generators -/// are not able to pick output of other generators, hence we're emitting -/// the code at build time before the .NET's sourcegen stage. -/// public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { [Required] public required string InspectedDirectory { get; set; } [Required] public required string EntryAssemblyName { get; set; } [Required] public required string ExportsFilePath { get; set; } [Required] public required string ImportsFilePath { get; set; } - [Required] public required string InterceptorsFilePath { get; set; } [Required] public required string DependenciesFilePath { get; set; } [Required] public required string SerializerFilePath { get; set; } [Required] public required string InteropFilePath { get; set; } @@ -27,7 +21,6 @@ public override bool Execute () using var inspection = InspectAssemblies(spaceBuilder); GenerateExports(inspection); GenerateImports(inspection); - GenerateInterceptors(inspection); GenerateDependencies(inspection); GenerateSerializer(inspection); GenerateInterop(inspection); @@ -63,16 +56,9 @@ private void GenerateImports (AssemblyInspection inspection) WriteGenerated(ImportsFilePath, content); } - private void GenerateInterceptors (AssemblyInspection inspection) - { - var generator = new InterceptorGenerator(); - var content = generator.Generate(inspection); - WriteGenerated(InterceptorsFilePath, content); - } - private void GenerateDependencies (AssemblyInspection inspection) { - var generator = new DependenciesGenerator(); + var generator = new DependenciesGenerator(EntryAssemblyName); var content = generator.Generate(inspection); WriteGenerated(DependenciesFilePath, content); } diff --git a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs index 08031bc6..2e20368c 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs @@ -1,10 +1,53 @@ -namespace Bootsharp.Publish; +using System.Diagnostics.CodeAnalysis; +using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace Bootsharp.Publish; /// /// Generates hints for DotNet to not trim specified dynamic dependencies, ie /// members that are not statically accessed in the user source code. /// -internal sealed class DependenciesGenerator +internal sealed class DependenciesGenerator (string entryAssembly) { - public string Generate (AssemblyInspection inspection) => ""; + private readonly HashSet added = []; + + public string Generate (AssemblyInspection inspection) + { + AddGeneratedCommon(); + AddGeneratedExportImport(inspection); + return + $$""" + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + + namespace Bootsharp.Generated; + + public static class Dependencies + { + [ModuleInitializer] + {{JoinLines(added)}} + internal static void RegisterDynamicDependencies () { } + } + """; + } + + private void AddGeneratedCommon () + { + Add(All, "Bootsharp.Generated.Dependencies", entryAssembly); + Add(All, "Bootsharp.Generated.SerializerContext", entryAssembly); + Add(All, "Bootsharp.Generated.Interop", entryAssembly); + } + + private void AddGeneratedExportImport (AssemblyInspection inspection) + { + foreach (var export in inspection.Exports) + Add(All, $"Bootsharp.Generated.Exports.{export.FullName}", entryAssembly); + foreach (var import in inspection.Imports) + Add(All, $"Bootsharp.Generated.Imports.{import.FullName}", entryAssembly); + } + + private void Add (DynamicallyAccessedMemberTypes types, string name, string assembly) + { + added.Add($"""[DynamicDependency(DynamicallyAccessedMemberTypes.{types}, "{name}", "{assembly}")]"""); + } } diff --git a/src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs deleted file mode 100644 index cdbc35da..00000000 --- a/src/cs/Bootsharp.Publish/Emit/InterceptorGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// Generates interceptors for and -/// interop methods declared by user. -/// -internal sealed class InterceptorGenerator -{ - public string Generate (AssemblyInspection inspection) => ""; -} diff --git a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs index 489f7024..a984d104 100644 --- a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs @@ -20,7 +20,7 @@ public string Generate (AssemblyInspection inspection) using System.Text.Json; using System.Text.Json.Serialization; - namespace Bootsharp; + namespace Bootsharp.Generated; {{JoinLines(attributes, 0)}} internal partial class SerializerContext : JsonSerializerContext @@ -57,7 +57,7 @@ private void CollectAttributes (string syntax, Type type) private void CollectDuplicates (AssemblyInspection inspection) { var names = new HashSet(); - foreach (var type in inspection.Types.DistinctBy(t => t.FullName)) + foreach (var type in inspection.Crawled.DistinctBy(t => t.FullName)) if (!names.Add(type.Name)) CollectAttributes(BuildSyntax(type), type); } diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs index b9ecc075..e37c12d3 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs @@ -17,7 +17,7 @@ public string Generate (AssemblyInspection inspection) { bindings = inspection.Methods .Select(m => new Binding(m, null, m.JSSpace)) - .Concat(inspection.Types.Where(t => t.IsEnum) + .Concat(inspection.Crawled.Where(t => t.IsEnum) .Select(t => new Binding(null, t, spaceBuilder.Build(t)))) .OrderBy(m => m.Namespace).ToArray(); if (bindings.Length == 0) return ""; diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs index 2ed513f2..15d99c56 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs @@ -7,7 +7,7 @@ internal sealed class DeclarationGenerator (NamespaceBuilder spaceBuilder) public string Generate (AssemblyInspection inspection) => JoinLines(0, """import type { Event } from "./event";""", - typesGenerator.Generate(inspection.Types), + typesGenerator.Generate(inspection.Crawled), methodsGenerator.Generate(inspection.Methods) ) + "\n"; } diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 4cf46fec..355259c2 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -7,9 +7,8 @@ $(IntermediateOutputPath)bootsharp $(BootsharpIntermediateDirectory)/Exports.g.cs $(BootsharpIntermediateDirectory)/Imports.g.cs - $(BootsharpIntermediateDirectory)/Interceptors.g.cs $(BootsharpIntermediateDirectory)/Dependencies.g.cs - $(BootsharpIntermediateDirectory)/SerializerContext.g.cs + $(BootsharpIntermediateDirectory)/Serializer.g.cs $(BootsharpIntermediateDirectory)/Interop.g.cs $(AssemblyName).dll @@ -66,20 +65,17 @@ EntryAssemblyName="$(BootsharpEntryAssemblyName)" ExportsFilePath="$(BootsharpExportsFilePath)" ImportsFilePath="$(BootsharpImportsFilePath)" - InterceptorsFilePath="$(BootsharpInterceptorsFilePath)" DependenciesFilePath="$(BootsharpDependenciesFilePath)" SerializerFilePath="$(BootsharpSerializerFilePath)" InteropFilePath="$(BootsharpInteropFilePath)"/> - - From d4d20161becc424aa2958b9e7aa7c188395d62ee Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:55:09 +0300 Subject: [PATCH 12/75] etc --- src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs index 2e20368c..49f2a374 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs @@ -5,7 +5,7 @@ namespace Bootsharp.Publish; /// /// Generates hints for DotNet to not trim specified dynamic dependencies, ie -/// members that are not statically accessed in the user source code. +/// members that are not explicitly accessed in the user source code. /// internal sealed class DependenciesGenerator (string entryAssembly) { From daa6f95abaf6809efe02eb06fc86857bfc383cfa Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Wed, 10 Jan 2024 19:39:30 +0300 Subject: [PATCH 13/75] iteration --- src/cs/Bootsharp.Common/Meta/SolutionMeta.cs | 4 ++-- .../Bootsharp.Publish.Test/Emit/DependenciesTest.cs | 13 +++++++++++++ .../Bootsharp.Publish/Emit/DependenciesGenerator.cs | 7 +++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs index b54b750d..550008c0 100644 --- a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs +++ b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs @@ -10,8 +10,8 @@ public sealed record SolutionMeta /// public required IReadOnlyCollection Assemblies { get; init; } /// - /// Interop methods in the solution: either top-level (eg [JSInvokable]) or - /// members of the auto-generated interop classes (eg [JSExport]). + /// Interop methods in the solution: either top-level (eg, ) or + /// members of the auto-generated interop classes (eg, ). /// public required IReadOnlyCollection Methods { get; init; } /// diff --git a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs index ac69cc94..4dc73e1e 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs @@ -40,6 +40,19 @@ public void AddsGeneratedImportTypes () Added(All, "Bootsharp.Generated.Imports.Space.IBar"); } + [Fact] + public void AddsClassesWithInteropMethods () + { + AddAssembly("Assembly.dll", + With("SpaceA", "public class ClassA { [JSInvokable] public static void Foo () {} }"), + With("SpaceB.SpaceC", "public class ClassB { [JSFunction] public static void Foo () {} }"), + With("public class ClassC { [JSEvent] public static void Foo () {} }")); + Execute(); + Added(All, "SpaceA.ClassA", "Assembly"); + Added(All, "SpaceB.SpaceC.ClassB", "Assembly"); + Added(All, "ClassC", "Assembly"); + } + private void Added (DynamicallyAccessedMemberTypes types, string name) => Added(types, name, Task.EntryAssemblyName); private void Added (DynamicallyAccessedMemberTypes types, string name, string assembly) diff --git a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs index 49f2a374..69f13cc3 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs @@ -15,6 +15,7 @@ public string Generate (AssemblyInspection inspection) { AddGeneratedCommon(); AddGeneratedExportImport(inspection); + AddClassesWithInteropMethods(inspection); return $$""" using System.Diagnostics.CodeAnalysis; @@ -46,6 +47,12 @@ private void AddGeneratedExportImport (AssemblyInspection inspection) Add(All, $"Bootsharp.Generated.Imports.{import.FullName}", entryAssembly); } + private void AddClassesWithInteropMethods (AssemblyInspection inspection) + { + foreach (var method in inspection.Methods) + Add(All, method.Space, method.Assembly); + } + private void Add (DynamicallyAccessedMemberTypes types, string name, string assembly) { added.Add($"""[DynamicDependency(DynamicallyAccessedMemberTypes.{types}, "{name}", "{assembly}")]"""); From e383cdf6102c9afaa0aa70696dc8f44eafb56c9d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Wed, 10 Jan 2024 22:03:43 +0300 Subject: [PATCH 14/75] iteration --- .../Emit/InteropTest.cs | 75 +++++++++++++ .../Emit/DependenciesGenerator.cs | 3 +- .../Emit/InteropGenerator.cs | 102 +++++++++++++++++- 3 files changed, 177 insertions(+), 3 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index 930fef25..1f0e5c7f 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -3,4 +3,79 @@ public class InteropTest : EmitTest { protected override string TestedContent => GeneratedInterop; + + [Fact] + public void WhenNothingInspectedGeneratesEmptyClass () + { + Execute(); + Contains( + """ + using System.Runtime.InteropServices.JavaScript; + using static Bootsharp.Serializer; + + namespace Bootsharp.Generated; + + public static partial class Interop + { + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterProxies () + """); + } + + [Fact] + public void CanGenerateForMethodsInGlobalSpace () + { + AddAssembly(With( + """ + public class Class + { + [JSInvokable] public static void Inv () {} + [JSFunction] public static void Fun () {} + [JSEvent] public static void Evt () {} + } + """)); + Execute(); + Contains("""Proxies.Set("Global.Class.fun", Class_Fun);"""); + Contains("""Proxies.Set("Global.Class.evt", Class_Evt);"""); + Contains("""[System.Runtime.InteropServices.JavaScript.JSExport] internal static void Class_Inv () => global::Class.Inv();"""); + Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); + Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); + } + + [Fact] + public void CanGenerateForMethodsInCustomSpaces () + { + AddAssembly(With( + """ + namespace SpaceA + { + public class Class + { + [JSInvokable] public static void Inv () {} + [JSFunction] public static void Fun () {} + [JSEvent] public static void Evt () {} + } + } + namespace SpaceA.SpaceB + { + public class Class + { + [JSInvokable] public static void Inv () {} + [JSFunction] public static void Fun () {} + [JSEvent] public static void Evt () {} + } + } + """)); + Execute(); + Contains("""Proxies.Set("SpaceA.Class.fun", SpaceA_Class_Fun);"""); + Contains("""Proxies.Set("SpaceA.Class.evt", SpaceA_Class_Evt);"""); + Contains("""Proxies.Set("SpaceA.SpaceB.Class.fun", SpaceA_SpaceB_Class_Fun);"""); + Contains("""Proxies.Set("SpaceA.SpaceB.Class.evt", SpaceA_SpaceB_Class_Evt);"""); + Contains("""JSExport] internal static void SpaceA_Class_Inv () => global::SpaceA.Class.Inv();"""); + Contains("""JSImport("SpaceA.Class.funSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Fun ();"""); + Contains("""JSImport("SpaceA.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Evt ();"""); + Contains("""JSExport] internal static void SpaceA_SpaceB_Class_Inv () => global::SpaceA.SpaceB.Class.Inv();"""); + Contains("""JSImport("SpaceA.SpaceB.Class.funSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Fun ();"""); + Contains("""JSImport("SpaceA.SpaceB.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Evt ();"""); + } } diff --git a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs index 69f13cc3..96e1a23e 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs @@ -19,13 +19,12 @@ public string Generate (AssemblyInspection inspection) return $$""" using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; namespace Bootsharp.Generated; public static class Dependencies { - [ModuleInitializer] + [System.Runtime.CompilerServices.ModuleInitializer] {{JoinLines(added)}} internal static void RegisterDynamicDependencies () { } } diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index d12e04c8..18418583 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -5,5 +5,105 @@ /// internal sealed class InteropGenerator { - public string Generate (AssemblyInspection inspection) => ""; + private readonly HashSet proxies = []; + private readonly HashSet methods = []; + + public string Generate (AssemblyInspection inspection) + { + foreach (var method in inspection.Methods) + if (method.Type == MethodType.Invokable) AddExportMethod(method); + else AddImportMethod(method); + return + $$""" + #nullable enable + #pragma warning disable + + using System.Runtime.InteropServices.JavaScript; + using static Bootsharp.Serializer; + + namespace Bootsharp.Generated; + + public static partial class Interop + { + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterProxies () + { + {{JoinLines(proxies, 2)}} + } + {{JoinLines(methods)}} + } + """; + } + + private void AddExportMethod (MethodMeta inv) + { + const string attr = "[System.Runtime.InteropServices.JavaScript.JSExport]"; + var date = MarshalAmbiguous(inv.ReturnValue.TypeSyntax, true); + var wait = inv.ReturnValue.Async && inv.ReturnValue.Serialized; + methods.Add($"{attr} {date}internal static {GenerateSignature(inv, wait)} => {GenerateBody(inv, wait)};"); + + string GenerateSignature (MethodMeta inv, bool wait) + { + var args = string.Join(", ", inv.Arguments.Select(GenerateSignatureArg)); + var @return = inv.ReturnValue.Void ? "void" : (inv.ReturnValue.Serialized + ? $"global::System.String{(inv.ReturnValue.Nullable ? "?" : "")}" + : inv.ReturnValue.TypeSyntax); + if (inv.ReturnValue.Serialized && inv.ReturnValue.Async) + @return = $"global::System.Threading.Tasks.Task<{@return}>"; + var signature = $"{@return} {inv.Name} ({args})"; + if (wait) signature = $"async {signature}"; + return signature; + } + + string GenerateBody (MethodMeta inv, bool wait) + { + var args = string.Join(", ", inv.Arguments.Select(GenerateBodyArg)); + var body = $"global::{inv.Space}.{inv.Name}({args})"; + if (wait) body = $"await {body}"; + if (inv.ReturnValue.Serialized) body = $"Serialize({body})"; + return body; + } + + string GenerateSignatureArg (ArgumentMeta arg) + { + var type = arg.Value.Serialized + ? $"global::System.String{(arg.Value.Nullable ? "?" : "")}" + : arg.Value.TypeSyntax; + return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; + } + + string GenerateBodyArg (ArgumentMeta arg) + { + if (!arg.Value.Serialized) return arg.Name; + return $"Deserialize<{arg.Value.TypeSyntax}>({arg.Name})"; + } + } + + private void AddImportMethod (MethodMeta method) + { + var args = string.Join(", ", method.Arguments.Select(GenerateArg)); + var @return = method.ReturnValue.Void ? "void" : (method.ReturnValue.Serialized + ? $"global::System.String{(method.ReturnValue.Nullable ? "?" : "")}" + : method.ReturnValue.TypeSyntax); + if (method.ReturnValue.Serialized && method.ReturnValue.Async) + @return = $"global::System.Threading.Tasks.Task<{@return}>"; + var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{BuildEndpoint(method, true)}", "Bootsharp")]"""; + var date = MarshalAmbiguous(method.ReturnValue.TypeSyntax, true); + methods.Add($"{attr} {date}internal static partial {@return} {method.Name} ({args});"); + proxies.Add($"""Function.Set("{BuildEndpoint(method, false)}", {method.Name});"""); + + string GenerateArg (ArgumentMeta arg) + { + var type = arg.Value.Serialized + ? $"global::System.String{(arg.Value.Nullable ? "?" : "")}" + : arg.Value.TypeSyntax; + return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; + } + + string BuildEndpoint (MethodMeta method, bool import) + { + var name = char.ToLowerInvariant(method.Name[0]) + method.Name[1..]; + return $"{method.JSSpace}.{name}{(import ? "Serialized" : "")}"; + } + } } From 0bd948056d373cf31020a8344c40148025373173 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:26:03 +0300 Subject: [PATCH 15/75] iteration --- .../Emit/SerializerTest.cs | 2 +- .../Mock/MockCompiler.cs | 2 +- .../Pack/BindingTest.cs | 230 +++++++++----- .../Pack/DeclarationTest.cs | 288 ++++++++++++------ .../AssemblyInspector/AssemblyInspector.cs | 2 +- .../JSSpaceBuilder.cs} | 14 +- .../JSSpaceConverter.cs} | 2 +- .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 6 +- .../Emit/InteropGenerator.cs | 8 +- .../Pack/BindingGenerator/BindingGenerator.cs | 4 +- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 10 +- .../DeclarationGenerator.cs | 2 +- .../DeclarationGenerator/TypeConverter.cs | 6 +- .../TypeDeclarationGenerator.cs | 7 +- 14 files changed, 376 insertions(+), 207 deletions(-) rename src/cs/Bootsharp.Publish/Common/{NamespaceBuilder/NamespaceBuilder.cs => JSSpaceBuilder/JSSpaceBuilder.cs} (60%) rename src/cs/Bootsharp.Publish/Common/{NamespaceBuilder/NamespaceConverter.cs => JSSpaceBuilder/JSSpaceConverter.cs} (83%) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs index 52e55a2a..2e2acdd8 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs @@ -51,7 +51,7 @@ public void AddsOnlyTopLevelTypesAndCrawledDuplicates () With("n", "public enum Enum { A, B }"), With("n", "public class Foo { public Struct S { get; } public ReadonlyStruct Rs { get; } }"), WithClass("n", "public class Bar : Foo { public ReadonlyRecordStruct Rrs { get; } public RecordClass Rc { get; } }"), - With("n", "public class Baz { public List Bars { get; } public Enum E { get; } }"), + With("n", "public class Baz { public List Bars { get; } public Enum E { get; } }"), WithClass("n", "[JSInvokable] public static Baz? GetBaz () => default;")); Execute(); Assert.Equal(2, Matches("JsonSerializable").Count); diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs index f605dd70..66f3a848 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs @@ -26,7 +26,7 @@ public void Compile (IEnumerable sources, string assemblyPath) private static string BuildSource (MockSource source) { var text = source.WrapInClass - ? $"public partial class MockClass {{ {source.Code} }}" + ? $"public partial class Class {{ {source.Code} }}" : source.Code; return source.Namespace is null ? text : $"namespace {source.Namespace} {{ {text} }}"; diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index cd31ff17..cb3cb7a4 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -14,7 +14,7 @@ public void WhenNoBindingsNothingIsGenerated () [Fact] public void InteropFunctionsImported () { - AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () { }")); + AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () {}")); Execute(); Contains( """ @@ -29,13 +29,15 @@ public void InteropFunctionsImported () [Fact] public void BindingForInvokableMethodIsGenerated () { - AddAssembly(WithClass("Foo.Bar", "[JSInvokable] public static void Nya () { }")); + AddAssembly(WithClass("Foo.Bar", "[JSInvokable] public static void Nya () {}")); Execute(); Contains( """ export const Foo = { Bar: { - nya: () => getExports().Foo_Bar_MockClass.Nya() + Class: { + nya: () => getExports().Foo_Bar_Class.Nya() + } } }; """); @@ -44,15 +46,17 @@ public void BindingForInvokableMethodIsGenerated () [Fact] public void BindingForFunctionMethodIsGenerated () { - AddAssembly(WithClass("Foo.Bar", "[JSFunction] public static void Fun () { }")); + AddAssembly(WithClass("Foo.Bar", "[JSFunction] public static void Fun () {}")); Execute(); Contains( """ export const Foo = { Bar: { - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Foo.Bar.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + Class: { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Foo.Bar.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + } } }; """); @@ -62,19 +66,21 @@ public void BindingForFunctionMethodIsGenerated () public void BindingForEventMethodIsGenerated () { AddAssembly( - WithClass("[JSEvent] public static void OnFoo () { }"), - WithClass("[JSEvent] public static void OnBar (string a) { }"), - WithClass("[JSEvent] public static void OnBaz (int a, bool b) { }")); + WithClass("[JSEvent] public static void OnFoo () {}"), + WithClass("[JSEvent] public static void OnBar (string a) {}"), + WithClass("[JSEvent] public static void OnBaz (int a, bool b) {}")); Execute(); Contains( """ export const Global = { - onFoo: new Event(), - onFooSerialized: () => Global.onFoo.broadcast(), - onBar: new Event(), - onBarSerialized: (a) => Global.onBar.broadcast(a), - onBaz: new Event(), - onBazSerialized: (a, b) => Global.onBaz.broadcast(a, b) + Class: { + onFoo: new Event(), + onFooSerialized: () => Global.Class.onFoo.broadcast(), + onBar: new Event(), + onBarSerialized: (a) => Global.Class.onBar.broadcast(a), + onBaz: new Event(), + onBazSerialized: (a, b) => Global.Class.onBaz.broadcast(a, b) + } }; """); } @@ -82,12 +88,14 @@ public void BindingForEventMethodIsGenerated () [Fact] public void LibraryExportsNamespaceObject () { - AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () { }")); + AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () {}")); Execute(); Contains( """ export const Foo = { - bar: () => getExports().Foo_MockClass.Bar() + Class: { + bar: () => getExports().Foo_Class.Bar() + } }; """); } @@ -95,14 +103,16 @@ public void LibraryExportsNamespaceObject () [Fact] public void WhenSpaceContainDotsObjectCreatedForEachPart () { - AddAssembly(WithClass("Foo.Bar.Nya", "[JSInvokable] public static void Bar () { }")); + AddAssembly(WithClass("Foo.Bar.Nya", "[JSInvokable] public static void Bar () {}")); Execute(); Contains( """ export const Foo = { Bar: { Nya: { - bar: () => getExports().Foo_Bar_Nya_MockClass.Bar() + Class: { + bar: () => getExports().Foo_Bar_Nya_Class.Bar() + } } } }; @@ -113,20 +123,24 @@ public void WhenSpaceContainDotsObjectCreatedForEachPart () public void WhenMultipleSpacesEachGetItsOwnObject () { AddAssembly( - WithClass("Foo", "[JSInvokable] public static void Foo () { }"), - WithClass("Bar.Nya", "[JSFunction] public static void Fun () { }")); + WithClass("Foo", "[JSInvokable] public static void Foo () {}"), + WithClass("Bar.Nya", "[JSFunction] public static void Fun () {}")); Execute(); Contains( """ export const Bar = { Nya: { - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Bar.Nya.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + Class: { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Bar.Nya.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + } } }; export const Foo = { - foo: () => getExports().Foo_MockClass.Foo() + Class: { + foo: () => getExports().Foo_Class.Foo() + } }; """); } @@ -134,16 +148,16 @@ public void WhenMultipleSpacesEachGetItsOwnObject () [Fact] public void WhenMultipleAssembliesWithEqualSpaceObjectDeclaredOnlyOnce () { - AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () { }")); - AddAssembly(WithClass("Foo", "[JSFunction] public static void Fun () { }")); + AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () {}")); + AddAssembly(WithClass("Foo", "[JSFunction] public static void Fun () {}")); Execute(); Assert.Single(Matches("export const Foo")); - Contains("bar: () => getExports().Foo_MockClass.Bar()"); + Contains("bar: () => getExports().Foo_Class.Bar()"); Contains( """ - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Foo.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Foo.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } """); } @@ -151,19 +165,23 @@ public void WhenMultipleAssembliesWithEqualSpaceObjectDeclaredOnlyOnce () public void DifferentSpacesWithSameRootAssignedUnderSameObject () { AddAssembly( - WithClass("Nya.Foo", "[JSInvokable] public static void Foo () { }"), - WithClass("Nya.Bar", "[JSFunction] public static void Fun () { }")); + WithClass("Nya.Foo", "[JSInvokable] public static void Foo () {}"), + WithClass("Nya.Bar", "[JSFunction] public static void Fun () {}")); Execute(); Contains( """ export const Nya = { Bar: { - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Nya.Bar.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + Class: { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Nya.Bar.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + } }, Foo: { - foo: () => getExports().Nya_Foo_MockClass.Foo() + Class: { + foo: () => getExports().Nya_Foo_Class.Foo() + } } }; """); @@ -173,18 +191,22 @@ public void DifferentSpacesWithSameRootAssignedUnderSameObject () public void DifferentSpacesStartingEquallyAreNotAssignedToSameObject () { AddAssembly( - WithClass("Foo", "[JSInvokable] public static void Method () { }"), - WithClass("FooBar.Baz", "[JSInvokable] public static void Method () { }") + WithClass("Foo", "[JSInvokable] public static void Method () {}"), + WithClass("FooBar.Baz", "[JSInvokable] public static void Method () {}") ); Execute(); Contains( """ export const Foo = { - method: () => getExports().Foo_MockClass.Method() + Class: { + method: () => getExports().Foo_Class.Method() + } }; export const FooBar = { Baz: { - method: () => getExports().FooBar_Baz_MockClass.Method() + Class: { + method: () => getExports().FooBar_Baz_Class.Method() + } } }; """); @@ -194,19 +216,45 @@ public void DifferentSpacesStartingEquallyAreNotAssignedToSameObject () public void BindingsFromMultipleSpacesAssignedToRespectiveObjects () { AddAssembly(WithClass("Foo", "[JSInvokable] public static int Foo () => 0;")); - AddAssembly(WithClass("Bar.Nya", "[JSFunction] public static void Fun () { }")); + AddAssembly(WithClass("Bar.Nya", "[JSFunction] public static void Fun () {}")); Execute(); Contains( """ export const Bar = { Nya: { - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Bar.Nya.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + Class: { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Bar.Nya.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + } } }; export const Foo = { - foo: () => getExports().Foo_MockClass.Foo() + Class: { + foo: () => getExports().Foo_Class.Foo() + } + }; + """); + } + + [Fact] + public void BindingsFromMultipleClassesAssignedToRespectiveObjects () + { + AddAssembly( + With("public class ClassA { [JSInvokable] public static void Inv () {} }"), + With("public class ClassB { [JSFunction] public static void Fun () {} }")); + Execute(); + Contains( + """ + export const Global = { + ClassA: { + inv: () => getExports().ClassA.Inv() + }, + ClassB: { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Global.ClassB.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + } }; """); } @@ -216,15 +264,17 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () { AddAssembly( WithClass("[JSInvokable] public static Task Nya () => Task.FromResult(0);"), - WithClass("[JSFunction] public static void Fun () { }")); + WithClass("[JSFunction] public static void Fun () {}")); Execute(); Contains( """ export const Global = { - nya: () => getExports().MockClass.Nya(), - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Global.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + Class: { + nya: () => getExports().Class.Nya(), + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Global.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } + } }; """); } @@ -235,17 +285,21 @@ public void NamespaceAttributeOverrideObjectNames () AddAssembly( With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), WithClass("Foo.Bar.Nya", "[JSInvokable] public static Task GetNya () => Task.CompletedTask;"), - WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun () { }")); + WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun () {}")); Execute(); Contains( """ export const Fun = { - get onFun() { return this.onFunHandler; }, - set onFun(handler) { this.onFunHandler = handler; this.onFunSerializedHandler = () => this.onFunHandler(); }, - get onFunSerialized() { if (typeof this.onFunHandler !== "function") throw Error("Failed to invoke 'Fun.onFun' from C#. Make sure to assign function in JavaScript."); return this.onFunSerializedHandler; } + Class: { + get onFun() { return this.onFunHandler; }, + set onFun(handler) { this.onFunHandler = handler; this.onFunSerializedHandler = () => this.onFunHandler(); }, + get onFunSerialized() { if (typeof this.onFunHandler !== "function") throw Error("Failed to invoke 'Fun.Class.onFun' from C#. Make sure to assign function in JavaScript."); return this.onFunSerializedHandler; } + } }; export const Nya = { - getNya: () => getExports().Foo_Bar_Nya_MockClass.GetNya() + Class: { + getNya: () => getExports().Foo_Bar_Nya_Class.GetNya() + } }; """); } @@ -253,12 +307,14 @@ public void NamespaceAttributeOverrideObjectNames () [Fact] public void VariablesConflictingWithJSTypesAreRenamed () { - AddAssembly(WithClass("[JSInvokable] public static void Fun (string function) { }")); + AddAssembly(WithClass("[JSInvokable] public static void Fun (string function) {}")); Execute(); Contains( """ export const Global = { - fun: (fn) => getExports().MockClass.Fun(fn) + Class: { + fun: (fn) => getExports().Class.Fun(fn) + } }; """); } @@ -270,20 +326,22 @@ public void SerializesCustomType () With("public record Info;"), WithClass("[JSInvokable] public static Info Foo (Info i) => default;"), WithClass("[JSFunction] public static Info? Bar (Info? i) => default;"), - WithClass("[JSEvent] public static void Baz (Info?[] i) { }"), - WithClass("[JSEvent] public static void Yaz (int a, Info i) { }")); + WithClass("[JSEvent] public static void Baz (Info?[] i) {}"), + WithClass("[JSEvent] public static void Yaz (int a, Info i) {}")); Execute(); Contains( """ export const Global = { - foo: (i) => deserialize(getExports().MockClass.Foo(serialize(i))), - get bar() { return this.barHandler; }, - set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i))); }, - get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, - baz: new Event(), - bazSerialized: (i) => Global.baz.broadcast(deserialize(i)), - yaz: new Event(), - yazSerialized: (a, i) => Global.yaz.broadcast(a, deserialize(i)) + Class: { + foo: (i) => deserialize(getExports().Class.Foo(serialize(i))), + get bar() { return this.barHandler; }, + set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i))); }, + get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, + baz: new Event(), + bazSerialized: (i) => Global.Class.baz.broadcast(deserialize(i)), + yaz: new Event(), + yazSerialized: (a, i) => Global.Class.yaz.broadcast(a, deserialize(i)) + } }; """); } @@ -301,14 +359,16 @@ public void AwaitsWhenSerializingInAsyncFunctions () Contains( """ export const Global = { - foo: async (i) => deserialize(await getExports().MockClass.Foo(serialize(i))), - get bar() { return this.barHandler; }, - set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i))); }, - get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, - baz: async () => deserialize(await getExports().MockClass.Baz()), - get yaz() { return this.yazHandler; }, - set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler()); }, - get yazSerialized() { if (typeof this.yazHandler !== "function") throw Error("Failed to invoke 'Global.yaz' from C#. Make sure to assign function in JavaScript."); return this.yazSerializedHandler; } + Class: { + foo: async (i) => deserialize(await getExports().Class.Foo(serialize(i))), + get bar() { return this.barHandler; }, + set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i))); }, + get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, + baz: async () => deserialize(await getExports().Class.Baz()), + get yaz() { return this.yazHandler; }, + set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler()); }, + get yazSerialized() { if (typeof this.yazHandler !== "function") throw Error("Failed to invoke 'Global.Class.yaz' from C#. Make sure to assign function in JavaScript."); return this.yazSerializedHandler; } + } }; """); } @@ -323,8 +383,12 @@ public void ExportedEnumsAreDeclaredInJS () Contains( """ export const n = { - getFoo: () => deserialize(getExports().n_MockClass.GetFoo()), - Foo: { "0": "A", "1": "B", "A": 0, "B": 1 } + Class: { + getFoo: () => deserialize(getExports().n_Class.GetFoo()), + Foo: { + "0": "A", "1": "B", "A": 0, "B": 1 + } + } }; """); } @@ -339,8 +403,12 @@ public void CustomEnumIndexesArePreservedInJS () Contains( """ export const n = { - getFoo: () => deserialize(getExports().n_MockClass.GetFoo()), - Foo: { "1": "A", "6": "B", "A": 1, "B": 6 } + Class: { + getFoo: () => deserialize(getExports().n_Class.GetFoo()), + Foo: { + "1": "A", "6": "B", "A": 1, "B": 6 + } + } }; """); } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 9e5e6674..3b4cb240 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -16,7 +16,12 @@ public void DeclaresNamespace () { AddAssembly(WithClass("Foo", "[JSInvokable] public static void Bar () { }")); Execute(); - Contains("export namespace Foo {"); + Contains( + """ + export namespace Foo.Class { + export function bar(): void; + } + """); } [Fact] @@ -24,7 +29,12 @@ public void DotsInSpaceArePreserved () { AddAssembly(WithClass("Foo.Bar.Nya", "[JSInvokable] public static void Bar () { }")); Execute(); - Contains("export namespace Foo.Bar.Nya {"); + Contains( + """ + export namespace Foo.Bar.Nya.Class { + export function bar(): void; + } + """); } [Fact] @@ -32,7 +42,12 @@ public void FunctionDeclarationIsExportedForInvokableMethod () { AddAssembly(WithClass("Foo", "[JSInvokable] public static void Foo () { }")); Execute(); - Contains("export namespace Foo {\n export function foo(): void;\n}"); + Contains( + """ + export namespace Foo.Class { + export function foo(): void; + } + """); } [Fact] @@ -40,7 +55,12 @@ public void AssignableVariableIsExportedForFunctionCallback () { AddAssembly(WithClass("Foo", "[JSFunction] public static void OnFoo () { }")); Execute(); - Contains("export namespace Foo {\n export let onFoo: () => void;\n}"); + Contains( + """ + export namespace Foo.Class { + export let onFoo: () => void; + } + """); } [Fact] @@ -53,7 +73,7 @@ public void EventPropertiesAreExportedForEventMethods () Execute(); Contains( """ - export namespace Foo { + export namespace Foo.Class { export const onFoo: Event<[]>; export const onBar: Event<[baz: string]>; export const onFar: Event<[yaz: number, nya: boolean | undefined]>; @@ -65,32 +85,46 @@ export namespace Foo { public void MembersFromSameSpaceAreDeclaredUnderSameSpace () { AddAssembly( - WithClass("Foo", "public class Foo { }"), - WithClass("Foo", "[JSInvokable] public static Foo GetFoo () => default;")); + With("Space", "public class Foo { }"), + With("Space", "public class Bar { }"), + WithClass("Space", "[JSInvokable] public static Foo GetFoo (Bar bar) => default;")); Execute(); - Contains("export namespace Foo {\n export interface Foo {\n }\n}"); - Contains("export namespace Foo {\n export function getFoo(): Foo.Foo;\n}"); + Contains( + """ + export namespace Space { + export interface Bar { + } + export interface Foo { + } + } + export namespace Space.Class { + export function getFoo(bar: Space.Bar): Space.Foo; + } + """); } [Fact] public void MembersFromDifferentSpacesAreDeclaredUnderRespectiveSpaces () { AddAssembly( - With("Foo", "public class Foo { }"), - WithClass("Bar", "[JSInvokable] public static Foo.Foo GetFoo () => default;")); + With("SpaceA", "public class Foo { }"), + With("SpaceB", "public class Bar { }"), + WithClass("[JSInvokable] public static SpaceA.Foo GetFoo (SpaceB.Bar bar) => default;")); Execute(); - Contains("export namespace Foo {\n export interface Foo {\n }\n}"); - Contains("export namespace Bar {\n export function getFoo(): Foo.Foo;\n}"); - } - - [Fact] - public void MultipleSpacesAreDeclaredFromNewLine () - { - AddAssembly( - WithClass("a", "[JSInvokable] public static void Foo () { }"), - WithClass("b", "[JSInvokable] public static void Bar () { }")); - Execute(); - Contains("\nexport namespace b"); + Contains( + """ + export namespace SpaceA { + export interface Foo { + } + } + export namespace SpaceB { + export interface Bar { + } + } + export namespace Global.Class { + export function getFoo(bar: SpaceB.Bar): SpaceA.Foo; + } + """); } [Fact] @@ -100,8 +134,8 @@ public void DifferentSpacesWithSameRootAreDeclaredIndividually () WithClass("Nya.Bar", "[JSInvokable] public static void Fun () { }"), WithClass("Nya.Foo", "[JSInvokable] public static void Foo () { }")); Execute(); - Contains("export namespace Nya.Bar {\n export function fun(): void;\n}"); - Contains("export namespace Nya.Foo {\n export function foo(): void;\n}"); + Contains("export namespace Nya.Bar.Class {\n export function fun(): void;\n}"); + Contains("export namespace Nya.Foo.Class {\n export function foo(): void;\n}"); } [Fact] @@ -112,7 +146,7 @@ public void WhenNoSpaceTypesAreDeclaredUnderGlobalSpace () WithClass("[JSFunction] public static void OnFoo (Foo foo) { }")); Execute(); Contains("export namespace Global {\n export interface Foo {\n }\n}"); - Contains("export namespace Global {\n export let onFoo: (foo: Global.Foo) => void;\n}"); + Contains("export namespace Global.Class {\n export let onFoo: (foo: Global.Foo) => void;\n}"); } [Fact] @@ -124,7 +158,7 @@ public void NamespaceAttributeOverrideSpaceNames () WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun (Nya.Nya nya) { }")); Execute(); Contains("export namespace Nya {\n export interface Nya {\n }\n}"); - Contains("export namespace Fun {\n export let onFun: (nya: Nya.Nya) => void;\n}"); + Contains("export namespace Fun.Class {\n export let onFun: (nya: Nya.Nya) => void;\n}"); } [Fact] @@ -207,8 +241,7 @@ public void IntArraysTranslatedToRelatedTypes () WithClass("[JSInvokable] public static void Int16 (short[] foo) {}"), WithClass("[JSInvokable] public static void Uint32 (uint[] foo) {}"), WithClass("[JSInvokable] public static void Int32 (int[] foo) {}"), - WithClass("[JSInvokable] public static void BigInt64 (long[] foo) {}") - ); + WithClass("[JSInvokable] public static void BigInt64 (long[] foo) {}")); Execute(); Contains("uint8(foo: Uint8Array): void"); Contains("int8(foo: Int8Array): void"); @@ -223,7 +256,7 @@ public void IntArraysTranslatedToRelatedTypes () public void DefinitionIsGeneratedForObjectType () { AddAssembly( - WithClass("n", "public class Foo { public string S { get; set; } public int I { get; set; } }"), + With("n", "public class Foo { public string S { get; set; } public int I { get; set; } }"), WithClass("n", "[JSInvokable] public static Foo Method (Foo t) => default;")); Execute(); Matches(@"export interface Foo {\s*s: string;\s*i: number;\s*}"); @@ -234,9 +267,9 @@ public void DefinitionIsGeneratedForObjectType () public void DefinitionIsGeneratedForInterfaceAndImplementation () { AddAssembly( - WithClass("n", "public interface Interface { Interface Foo { get; } void Bar (Interface b); }"), - WithClass("n", "public class Base { }"), - WithClass("n", "public class Derived : Base, Interface { public Interface Foo { get; } public void Bar (Interface b) {} }"), + With("n", "public interface Interface { Interface Foo { get; } void Bar (Interface b); }"), + With("n", "public class Base { }"), + With("n", "public class Derived : Base, Interface { public Interface Foo { get; } public void Bar (Interface b) {} }"), WithClass("n", "[JSInvokable] public static Derived Method (Interface b) => default;")); Execute(); Matches(@"export interface Interface {\s*foo: n.Interface;\s*}"); @@ -249,8 +282,8 @@ public void DefinitionIsGeneratedForInterfaceAndImplementation () public void DefinitionIsGeneratedForTypeWithListProperty () { AddAssembly( - WithClass("n", "public interface Item { }"), - WithClass("n", "public class Container { public List Items { get; } }"), + With("n", "public interface Item { }"), + With("n", "public class Container { public List Items { get; } }"), WithClass("n", "[JSInvokable] public static Container Combine (List items) => default;")); Execute(); Matches(@"export interface Item {\s*}"); @@ -262,8 +295,8 @@ public void DefinitionIsGeneratedForTypeWithListProperty () public void DefinitionIsGeneratedForTypeWithJaggedArrayProperty () { AddAssembly( - WithClass("n", "public interface Item { }"), - WithClass("n", "public class Container { public Item[][] Items { get; } }"), + With("n", "public interface Item { }"), + With("n", "public class Container { public Item[][] Items { get; } }"), WithClass("n", "[JSInvokable] public static Container Get () => default;")); Execute(); Matches(@"export interface Item {\s*}"); @@ -275,8 +308,8 @@ public void DefinitionIsGeneratedForTypeWithJaggedArrayProperty () public void DefinitionIsGeneratedForTypeWithReadOnlyListProperty () { AddAssembly( - WithClass("n", "public interface Item { }"), - WithClass("n", "public class Container { public IReadOnlyList Items { get; } }"), + With("n", "public interface Item { }"), + With("n", "public class Container { public IReadOnlyList Items { get; } }"), WithClass("n", "[JSInvokable] public static Container Combine (IReadOnlyList items) => default;")); Execute(); Matches(@"export interface Item {\s*}"); @@ -288,8 +321,8 @@ public void DefinitionIsGeneratedForTypeWithReadOnlyListProperty () public void DefinitionIsGeneratedForTypeWithDictionaryProperty () { AddAssembly( - WithClass("n", "public interface Item { }"), - WithClass("n", "public class Container { public Dictionary Items { get; } }"), + With("n", "public interface Item { }"), + With("n", "public class Container { public Dictionary Items { get; } }"), WithClass("n", "[JSInvokable] public static Container Combine (Dictionary items) => default;")); Execute(); Matches(@"export interface Item {\s*}"); @@ -301,8 +334,8 @@ public void DefinitionIsGeneratedForTypeWithDictionaryProperty () public void DefinitionIsGeneratedForTypeWithReadOnlyDictionaryProperty () { AddAssembly( - WithClass("n", "public interface Item { }"), - WithClass("n", "public class Container { public IReadOnlyDictionary Items { get; } }"), + With("n", "public interface Item { }"), + With("n", "public class Container { public IReadOnlyDictionary Items { get; } }"), WithClass("n", "[JSInvokable] public static Container Combine (IReadOnlyDictionary items) => default;")); Execute(); Matches(@"export interface Item {\s*}"); @@ -314,18 +347,27 @@ public void DefinitionIsGeneratedForTypeWithReadOnlyDictionaryProperty () public void DefinitionIsGeneratedForGenericClass () { AddAssembly( - WithClass("n", "public class GenericClass { public T Value { get; set; } }"), + With("n", "public class GenericClass { public T Value { get; set; } }"), WithClass("n", "[JSInvokable] public static void Method (GenericClass p) { }")); Execute(); - Matches(@"export interface GenericClass {\s*value: T;\s*}"); - Contains("method(p: n.GenericClass): void"); + Contains( + """ + export namespace n { + export interface GenericClass { + value: T; + } + } + export namespace n.Class { + export function method(p: n.GenericClass): void; + } + """); } [Fact] public void DefinitionIsGeneratedForGenericInterface () { AddAssembly( - WithClass("n", "public interface GenericInterface { public T Value { get; set; } }"), + With("n", "public interface GenericInterface { public T Value { get; set; } }"), WithClass("n", "[JSInvokable] public static GenericInterface Method () => default;")); Execute(); Matches(@"export interface GenericInterface {\s*value: T;\s*}"); @@ -353,31 +395,62 @@ public void DefinitionIsGeneratedForGenericClassWithMultipleTypeArguments () WithClass("n", "[JSInvokable] public static void Method (GenericClass p) { }")); Execute(); Matches(@"export interface GenericClass {\s*key: T1;\s*value: T2;\s*}"); - Contains("method(p: n.GenericClass): void"); + Contains("method(p: n.Class.GenericClass): void"); } [Fact] public void CanCrawlCustomTypes () { AddAssembly( - WithClass("n", "public struct Struct { public double A { get; set; } }"), - WithClass("n", "public readonly struct ReadonlyStruct { public double A { get; init; } }"), - WithClass("n", "public readonly record struct ReadonlyRecordStruct(double A);"), - WithClass("n", "public record class RecordClass(double A);"), - WithClass("n", "public enum Enum { A, B }"), - WithClass("n", "public class Foo { public Struct S { get; } public ReadonlyStruct Rs { get; } }"), - WithClass("n", "public class Bar : Foo { public ReadonlyRecordStruct Rrs { get; } public RecordClass Rc { get; } }"), - WithClass("n", "public class Baz { public List Bars { get; } public Enum E { get; } }"), - WithClass("n", "[JSInvokable] public static Baz GetBaz () => default;")); - Execute(); - Matches(@"export interface Struct {\s*a: number;\s*}"); - Matches(@"export interface ReadonlyStruct {\s*a: number;\s*}"); - Matches(@"export interface ReadonlyRecordStruct {\s*a: number;\s*}"); - Matches(@"export interface RecordClass {\s*a: number;\s*}"); - Matches(@"export enum Enum {\s*A,\s*B\s*}"); - Matches(@"export interface Foo {\s*s: n.Struct;\s*rs: n.ReadonlyStruct;\s*}"); - Matches(@"export interface Bar extends n.Foo {\s*rrs: n.ReadonlyRecordStruct;\s*rc: n.RecordClass;\s*}"); - Matches(@"export interface Baz {\s*bars: Array;\s*e: n.Enum;\s*}"); + With("Space", + """ + public struct Struct { public double A { get; set; } } + public readonly struct ReadonlyStruct { public double A { get; init; } } + public readonly record struct ReadonlyRecordStruct(double A); + public record class RecordClass(double A); + public enum Enum { A, B } + public class Foo { public Struct S { get; } public ReadonlyStruct Rs { get; } } + public class Bar : Foo { public ReadonlyRecordStruct Rrs { get; } public RecordClass Rc { get; } } + public class Baz { public List Bars { get; } public Enum E { get; } } + public class Class { [JSInvokable] public static Baz GetBaz () => default; } + """)); + Execute(); + Contains( + """ + export namespace Space { + export interface Baz { + bars: Array; + e: Space.Enum; + } + export interface Bar extends Space.Foo { + rrs: Space.ReadonlyRecordStruct; + rc: Space.RecordClass; + } + export interface ReadonlyRecordStruct { + a: number; + } + export interface RecordClass { + a: number; + } + export interface Struct { + a: number; + } + export interface ReadonlyStruct { + a: number; + } + export interface Foo { + s: Space.Struct; + rs: Space.ReadonlyStruct; + } + export enum Enum { + A, + B + } + } + export namespace Space.Class { + export function getBaz(): Space.Baz; + } + """); } [Fact] @@ -413,8 +486,7 @@ public void NullableMethodArgumentsUnionWithUndefined () { AddAssembly( WithClass("[JSInvokable] public static void Foo (string? bar) { }"), - WithClass("[JSFunction] public static void Fun (int? nya) { }") - ); + WithClass("[JSFunction] public static void Fun (int? nya) { }")); Execute(); Contains("export function foo(bar: string | undefined): void;"); Contains("export let fun: (nya: number | undefined) => void;"); @@ -426,8 +498,7 @@ public void NullableMethodReturnTypesUnionWithNull () AddAssembly( WithClass("[JSInvokable] public static string? Foo () => default;"), WithClass("[JSInvokable] public static Task Bar () => default;"), - WithClass("[JSFunction] public static ValueTask?> Nya () => default;") - ); + WithClass("[JSFunction] public static ValueTask?> Nya () => default;")); Execute(); Contains("export function foo(): string | null;"); Contains("export function bar(): Promise;"); @@ -438,60 +509,89 @@ public void NullableMethodReturnTypesUnionWithNull () public void NullableCollectionElementTypesUnionWithNull () { AddAssembly( - WithClass("public class Foo { }"), - WithClass("[JSFunction] public static List? Fun (int?[]? bar, Foo[]?[]? nya, Foo?[]?[]? far) => default;") - ); + With("public class Foo { }"), + WithClass("[JSFunction] public static List? Fun (int?[]? bar, Foo[]?[]? nya, Foo?[]?[]? far) => default;")); Execute(); - Contains("export let fun: (bar: Array | undefined," + - " nya: Array | null> | undefined," + - " far: Array | null> | undefined) =>" + - " Array | null;"); + Contains( + """ + export namespace Global { + export interface Foo { + } + } + export namespace Global.Class { + export let fun: (bar: Array | undefined, nya: Array | null> | undefined, far: Array | null> | undefined) => Array | null; + } + """); } [Fact] public void NullableCollectionElementTypesOfCustomTypeUnionWithNull () { AddAssembly( - WithClass("public interface IFoo { }"), - WithClass("public record Foo (List?>?>? Bar, IFoo?[]?[]? Nya) : IFoo;"), - WithClass("[JSFunction] public static IFoo Fun (Foo foo) => default;") - ); + With("public interface IFoo { }"), + With("public record Foo (List?>?>? Bar, IFoo?[]?[]? Nya) : IFoo;"), + WithClass("[JSFunction] public static IFoo Fun (Foo foo) => default;")); Execute(); - Contains(@"bar?: Array | null> | null>;"); - Contains(@"nya?: Array | null> | null>;"); + Contains("bar?: Array | null> | null>;"); + Contains("nya?: Array | null> | null>;"); } [Fact] public void NullablePropertiesHaveOptionalModificator () { AddAssembly( - WithClass("n", "public class Foo { public bool? Bool { get; } }"), - WithClass("n", "public class Bar { public Foo? Foo { get; } }"), + With("n", "public class Foo { public bool? Bool { get; } }"), + With("n", "public class Bar { public Foo? Foo { get; } }"), WithClass("n", "[JSInvokable] public static Foo FooBar (Bar bar) => default;")); Execute(); - Matches(@"export interface Foo {\s*bool\?: boolean;\s*}"); - Matches(@"export interface Bar {\s*foo\?: n.Foo;\s*}"); + Contains( + """ + export namespace n { + export interface Bar { + foo?: n.Foo; + } + export interface Foo { + bool?: boolean; + } + } + export namespace n.Class { + export function fooBar(bar: n.Bar): n.Foo; + } + """); } [Fact] public void NullableEnumsAreCrawled () { AddAssembly( - WithClass("n", "public enum Foo { A, B }"), - WithClass("n", "public class Bar { public Foo? Foo { get; } }"), + With("n", "public enum Foo { A, B }"), + With("n", "public class Bar { public Foo? Foo { get; } }"), WithClass("n", "[JSInvokable] public static Bar GetBar () => default;")); Execute(); - Matches(@"export enum Foo {\s*A,\s*B\s*}"); - Matches(@"export interface Bar {\s*foo\?: n.Foo;\s*}"); + Contains( + """ + export namespace n { + export interface Bar { + foo?: n.Foo; + } + export enum Foo { + A, + B + } + } + export namespace n.Class { + export function getBar(): n.Bar; + } + """); } [Fact] public void WhenTypeReferencedMultipleTimesItsDeclaredOnlyOnce () { AddAssembly( - WithClass("public interface Foo { }"), - WithClass("public class Bar : Foo { public Foo Foo { get; } }"), - WithClass("public class Far : Bar { public Bar Bar { get; } }"), + With("public interface Foo { }"), + With("public class Bar : Foo { public Foo Foo { get; } }"), + With("public class Far : Bar { public Bar Bar { get; } }"), WithClass("[JSInvokable] public static Bar TakeFooGiveBar (Foo f) => default;"), WithClass("[JSInvokable] public static Foo TakeBarGiveFoo (Bar b) => default;"), WithClass("[JSInvokable] public static Far TakeAllGiveFar (Foo f, Bar b, Far ff) => default;")); diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 4f46711f..04e347e1 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -3,7 +3,7 @@ namespace Bootsharp.Publish; -internal sealed class AssemblyInspector (NamespaceBuilder spaceBuilder) +internal sealed class AssemblyInspector (JSSpaceBuilder spaceBuilder) { private readonly List assemblies = []; private readonly List methods = []; diff --git a/src/cs/Bootsharp.Publish/Common/NamespaceBuilder/NamespaceBuilder.cs b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs similarity index 60% rename from src/cs/Bootsharp.Publish/Common/NamespaceBuilder/NamespaceBuilder.cs rename to src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs index 1bd9220c..1c9d3e7a 100644 --- a/src/cs/Bootsharp.Publish/Common/NamespaceBuilder/NamespaceBuilder.cs +++ b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs @@ -2,11 +2,9 @@ namespace Bootsharp.Publish; -internal sealed class NamespaceBuilder +internal sealed class JSSpaceBuilder { - private const string defaultSpace = "Global"; - private const string attributeName = "JSNamespaceAttribute"; - private readonly List converters = []; + private readonly List converters = []; public void CollectConverters (string outDir, string entryAssembly) { @@ -14,12 +12,13 @@ public void CollectConverters (string outDir, string entryAssembly) var assemblyPath = Path.Combine(outDir, entryAssembly); var assembly = context.LoadFromAssemblyPath(assemblyPath); foreach (var attribute in CollectAttributes(assembly)) - converters.Add(new NamespaceConverter(attribute)); + converters.Add(new JSSpaceConverter(attribute)); } public string Build (Type type) { - var space = type.Namespace ?? defaultSpace; + var space = type.FullName!.Replace("+", "."); + if (type.Namespace is null) space = $"Global.{space}"; foreach (var converter in converters) space = converter.Convert(space); return space; @@ -27,6 +26,7 @@ public string Build (Type type) private IEnumerable CollectAttributes (Assembly assembly) { - return assembly.CustomAttributes.Where(a => a.AttributeType.Name == attributeName); + return assembly.CustomAttributes.Where(a => + a.AttributeType.FullName == typeof(JSNamespaceAttribute).FullName); } } diff --git a/src/cs/Bootsharp.Publish/Common/NamespaceBuilder/NamespaceConverter.cs b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs similarity index 83% rename from src/cs/Bootsharp.Publish/Common/NamespaceBuilder/NamespaceConverter.cs rename to src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs index 01dd664a..e6a58ee1 100644 --- a/src/cs/Bootsharp.Publish/Common/NamespaceBuilder/NamespaceConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs @@ -3,7 +3,7 @@ namespace Bootsharp.Publish; -internal sealed class NamespaceConverter (CustomAttributeData attribute) +internal sealed class JSSpaceConverter (CustomAttributeData attribute) { private readonly string pattern = (string)attribute.ConstructorArguments[0].Value!; private readonly string replacement = (string)attribute.ConstructorArguments[1].Value!; diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 60da9305..690f181c 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -27,14 +27,14 @@ public override bool Execute () return true; } - private NamespaceBuilder CreateNamespaceBuilder () + private JSSpaceBuilder CreateNamespaceBuilder () { - var builder = new NamespaceBuilder(); + var builder = new JSSpaceBuilder(); builder.CollectConverters(InspectedDirectory, EntryAssemblyName); return builder; } - private AssemblyInspection InspectAssemblies (NamespaceBuilder spaceBuilder) + private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) { var inspector = new AssemblyInspector(spaceBuilder); var inspection = inspector.InspectInDirectory(InspectedDirectory); diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index 18418583..32b57b3d 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -87,10 +87,10 @@ private void AddImportMethod (MethodMeta method) : method.ReturnValue.TypeSyntax); if (method.ReturnValue.Serialized && method.ReturnValue.Async) @return = $"global::System.Threading.Tasks.Task<{@return}>"; - var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{BuildEndpoint(method, true)}", "Bootsharp")]"""; + var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{BuildEndpoint(method)}Serialized", "Bootsharp")]"""; var date = MarshalAmbiguous(method.ReturnValue.TypeSyntax, true); methods.Add($"{attr} {date}internal static partial {@return} {method.Name} ({args});"); - proxies.Add($"""Function.Set("{BuildEndpoint(method, false)}", {method.Name});"""); + proxies.Add($"""Function.Set("{BuildEndpoint(method)}", {method.Name});"""); string GenerateArg (ArgumentMeta arg) { @@ -100,10 +100,10 @@ string GenerateArg (ArgumentMeta arg) return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; } - string BuildEndpoint (MethodMeta method, bool import) + string BuildEndpoint (MethodMeta method) { var name = char.ToLowerInvariant(method.Name[0]) + method.Name[1..]; - return $"{method.JSSpace}.{name}{(import ? "Serialized" : "")}"; + return $"{method.JSSpace}.{name}"; } } } diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs index e37c12d3..6fcb339a 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; -internal sealed class BindingGenerator (NamespaceBuilder spaceBuilder) +internal sealed class BindingGenerator (JSSpaceBuilder spaceBuilder) { private readonly StringBuilder builder = new(); @@ -130,7 +130,7 @@ private void EmitEnum (Type @enum) var fields = string.Join(", ", values .Select(v => $"\"{v}\": \"{Enum.GetName(@enum, v)}\"") .Concat(values.Select(v => $"\"{Enum.GetName(@enum, v)}\": {v}"))); - builder.Append($"{Comma()}\n{Pad(level + 1)}{@enum.Name}: {{ {fields} }}"); + builder.Append($"{Comma()}\n{Pad(level + 1)}{fields}"); } private string GetRoot (Binding binding) diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index cd9e3653..e2fdcccf 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -25,14 +25,14 @@ public override bool Execute () return true; } - private NamespaceBuilder CreateNamespaceBuilder () + private JSSpaceBuilder CreateNamespaceBuilder () { - var builder = new NamespaceBuilder(); + var builder = new JSSpaceBuilder(); builder.CollectConverters(InspectedDirectory, EntryAssemblyName); return builder; } - private AssemblyInspection InspectAssemblies (NamespaceBuilder spaceBuilder) + private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) { var inspector = new AssemblyInspector(spaceBuilder); var inspection = inspector.InspectInDirectory(InspectedDirectory); @@ -40,14 +40,14 @@ private AssemblyInspection InspectAssemblies (NamespaceBuilder spaceBuilder) return inspection; } - private void GenerateBindings (AssemblyInspection inspection, NamespaceBuilder spaceBuilder) + private void GenerateBindings (AssemblyInspection inspection, JSSpaceBuilder spaceBuilder) { var generator = new BindingGenerator(spaceBuilder); var content = generator.Generate(inspection); File.WriteAllText(Path.Combine(BuildDirectory, "bindings.g.js"), content); } - private void GenerateDeclarations (AssemblyInspection inspection, NamespaceBuilder spaceBuilder) + private void GenerateDeclarations (AssemblyInspection inspection, JSSpaceBuilder spaceBuilder) { var generator = new DeclarationGenerator(spaceBuilder); var content = generator.Generate(inspection); diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs index 15d99c56..c82360fd 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs @@ -1,6 +1,6 @@ namespace Bootsharp.Publish; -internal sealed class DeclarationGenerator (NamespaceBuilder spaceBuilder) +internal sealed class DeclarationGenerator (JSSpaceBuilder spaceBuilder) { private readonly MethodDeclarationGenerator methodsGenerator = new(); private readonly TypeDeclarationGenerator typesGenerator = new(spaceBuilder); diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeConverter.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeConverter.cs index 25b1a19d..de3980bd 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeConverter.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; -internal sealed class TypeConverter (NamespaceBuilder spaceBuilder) +internal sealed class TypeConverter (JSSpaceBuilder spaceBuilder) { public IReadOnlyCollection CrawledTypes => crawler.Crawled; @@ -69,13 +69,13 @@ private string ConvertGeneric (Type type) { EnterNullability(type); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); - return $"{spaceBuilder.Build(type)}.{GetGenericNameWithoutArgs(type.Name)}<{args}>"; + return $"{GetGenericNameWithoutArgs(spaceBuilder.Build(type))}<{args}>"; } private string ConvertFinal (Type type) { if (type.Name == "Void") return "void"; - if (CrawledTypes.Contains(type)) return $"{spaceBuilder.Build(type)}.{type.Name}"; + if (CrawledTypes.Contains(type)) return spaceBuilder.Build(type); return Type.GetTypeCode(type) switch { TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index a222d5ae..8efffd95 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -3,7 +3,7 @@ namespace Bootsharp.Publish; -internal sealed class TypeDeclarationGenerator (NamespaceBuilder spaceBuilder) +internal sealed class TypeDeclarationGenerator (JSSpaceBuilder spaceBuilder) { private readonly StringBuilder builder = new(); private readonly TypeConverter converter = new(spaceBuilder); @@ -40,7 +40,7 @@ private void DeclareType () private bool ShouldOpenNamespace () { if (prevType is null) return true; - return spaceBuilder.Build(prevType) != GetNamespace(type); + return GetNamespace(prevType) != GetNamespace(type); } private void OpenNamespace () @@ -81,7 +81,8 @@ private void DeclareEnum () private string GetNamespace (Type type) { - return spaceBuilder.Build(type); + var space = spaceBuilder.Build(type); + return space[..space.LastIndexOf('.')]; } private void AppendExtensions () From 0883d3597fc45239079638805129fee4ce517762 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:32:40 +0300 Subject: [PATCH 16/75] iteration --- .../Pack/BindingGenerator/BindingGenerator.cs | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs index 6fcb339a..a1d0aa58 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs @@ -52,12 +52,12 @@ private bool ShouldOpenNamespace () private void OpenNamespace () { - level = binding.Namespace.Count(c => c == '.'); - var prevParts = prevBinding?.Namespace.Split('.') ?? Array.Empty(); + level = 0; + var prevParts = prevBinding?.Namespace.Split('.') ?? []; var parts = binding.Namespace.Split('.'); - for (var i = 0; i < parts.Length; i++) - if (prevParts.ElementAtOrDefault(i) == parts[i]) continue; - else if (i == 0) builder.Append($"\nexport const {parts[i]} = {{"); + while (prevParts.ElementAtOrDefault(level) == parts[level]) level++; + for (var i = level; i < parts.Length; level = i, i++) + if (i == 0) builder.Append($"\nexport const {parts[i]} = {{"); else builder.Append($"{Comma()}\n{Pad(i)}{parts[i]}: {{"); } @@ -69,11 +69,22 @@ private bool ShouldCloseNamespace () private void CloseNamespace () { - var target = (nextBinding is null || GetRoot(nextBinding) != GetRoot(binding)) ? 0 - : nextBinding.Namespace.Count(c => c == '.'); + var target = GetCloseLevel(); for (; level >= target; level--) if (level == 0) builder.Append("\n};"); else builder.Append($"\n{Pad(level)}}}"); + + int GetCloseLevel () + { + if (nextBinding is null) return 0; + var closeLevel = 0; + var parts = binding.Namespace.Split('.'); + var nextParts = nextBinding.Namespace.Split('.'); + for (var i = 0; i < parts.Length; i++) + if (parts[i] == nextParts[i]) closeLevel++; + else break; + return closeLevel; + } } private void EmitMethod (MethodMeta method) @@ -94,7 +105,7 @@ private void EmitInvokable (MethodMeta method) var body = $"{(wait ? "await " : "")}{endpoint}({invArgs})"; if (method.ReturnValue.Serialized) body = $"deserialize({body})"; var func = $"{(wait ? "async " : "")}({funcArgs}) => {body}"; - builder.Append($"{Comma()}\n{Pad(level + 1)}{method.JSName}: {func}"); + builder.Append($"{Break()}{method.JSName}: {func}"); } private void EmitFunction (MethodMeta method) @@ -110,18 +121,18 @@ private void EmitFunction (MethodMeta method) var set = $"this.{name}Handler = handler; this.{name}SerializedHandler = {(wait ? "async " : "")}({funcArgs}) => {body};"; var error = $"throw Error(\"Failed to invoke '{binding.Namespace}.{name}' from C#. Make sure to assign function in JavaScript.\")"; var serde = $"if (typeof this.{name}Handler !== \"function\") {error}; return this.{name}SerializedHandler;"; - builder.Append($"{Comma()}\n{Pad(level + 1)}get {name}() {{ return this.{name}Handler; }}"); - builder.Append($"{Comma()}\n{Pad(level + 1)}set {name}(handler) {{ {set} }}"); - builder.Append($"{Comma()}\n{Pad(level + 1)}get {name}Serialized() {{ {serde} }}"); + builder.Append($"{Break()}get {name}() {{ return this.{name}Handler; }}"); + builder.Append($"{Break()}set {name}(handler) {{ {set} }}"); + builder.Append($"{Break()}get {name}Serialized() {{ {serde} }}"); } private void EmitEvent (MethodMeta method) { var name = method.JSName; - builder.Append($"{Comma()}\n{Pad(level + 1)}{name}: new Event()"); + builder.Append($"{Break()}{name}: new Event()"); var funcArgs = string.Join(", ", method.Arguments.Select(a => a.JSName)); var invArgs = string.Join(", ", method.Arguments.Select(arg => arg.Value.Serialized ? $"deserialize({arg.JSName})" : arg.JSName)); - builder.Append($"{Comma()}\n{Pad(level + 1)}{name}Serialized: ({funcArgs}) => {method.JSSpace}.{name}.broadcast({invArgs})"); + builder.Append($"{Break()}{name}Serialized: ({funcArgs}) => {method.JSSpace}.{name}.broadcast({invArgs})"); } private void EmitEnum (Type @enum) @@ -130,19 +141,14 @@ private void EmitEnum (Type @enum) var fields = string.Join(", ", values .Select(v => $"\"{v}\": \"{Enum.GetName(@enum, v)}\"") .Concat(values.Select(v => $"\"{Enum.GetName(@enum, v)}\": {v}"))); - builder.Append($"{Comma()}\n{Pad(level + 1)}{fields}"); + builder.Append($"{Break()}{fields}"); } - private string GetRoot (Binding binding) - { - var firstDotIdx = binding.Namespace.IndexOf('.'); - if (firstDotIdx < 0) return binding.Namespace; - return binding.Namespace[..firstDotIdx]; - } - - private string Pad (int level) => new(' ', level * 4); - private string Comma () => builder[^1] == '{' ? "" : ","; private bool ShouldWait (MethodMeta method) => (method.Arguments.Any(a => a.Value.Serialized) || method.ReturnValue.Serialized) && method.ReturnValue.Async; + + private string Break () => $"{Comma()}\n{Pad(level + 1)}"; + private string Pad (int level) => new(' ', level * 4); + private string Comma () => builder[^1] == '{' ? "" : ","; } From 02e89697e781ed1ec947390114fd6322af19c2be Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:04:47 +0300 Subject: [PATCH 17/75] iteration --- .../Emit/InteropTest.cs | 76 ++++++++++++++++++- .../Bootsharp.Publish.Test/Mock/MockTest.cs | 15 ++++ .../Emit/InteropGenerator.cs | 12 ++- 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index 1f0e5c7f..f3f11de2 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -37,7 +37,7 @@ [JSEvent] public static void Evt () {} Execute(); Contains("""Proxies.Set("Global.Class.fun", Class_Fun);"""); Contains("""Proxies.Set("Global.Class.evt", Class_Evt);"""); - Contains("""[System.Runtime.InteropServices.JavaScript.JSExport] internal static void Class_Inv () => global::Class.Inv();"""); + Contains("[System.Runtime.InteropServices.JavaScript.JSExport] internal static void Class_Inv () => global::Class.Inv();"); Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); } @@ -71,11 +71,81 @@ [JSEvent] public static void Evt () {} Contains("""Proxies.Set("SpaceA.Class.evt", SpaceA_Class_Evt);"""); Contains("""Proxies.Set("SpaceA.SpaceB.Class.fun", SpaceA_SpaceB_Class_Fun);"""); Contains("""Proxies.Set("SpaceA.SpaceB.Class.evt", SpaceA_SpaceB_Class_Evt);"""); - Contains("""JSExport] internal static void SpaceA_Class_Inv () => global::SpaceA.Class.Inv();"""); + Contains("JSExport] internal static void SpaceA_Class_Inv () => global::SpaceA.Class.Inv();"); Contains("""JSImport("SpaceA.Class.funSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Fun ();"""); Contains("""JSImport("SpaceA.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Evt ();"""); - Contains("""JSExport] internal static void SpaceA_SpaceB_Class_Inv () => global::SpaceA.SpaceB.Class.Inv();"""); + Contains("JSExport] internal static void SpaceA_SpaceB_Class_Inv () => global::SpaceA.SpaceB.Class.Inv();"); Contains("""JSImport("SpaceA.SpaceB.Class.funSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Fun ();"""); Contains("""JSImport("SpaceA.SpaceB.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Evt ();"""); } + + [Fact] + public void HandlesVariousArgumentAndReturnTypes () + { + AddAssembly(With( + """ + namespace Space; + + public record Info; + + public class Class + { + [JSInvokable] public static void InvVoid () {} + [JSInvokable] public static Info InvWithArgs (Info a, int[] b) => default; + [JSInvokable] public static Task InvAsync () => default; + [JSInvokable] public static Task InvAsyncWithArgs (Info? i) => default; + [JSFunction] public static Info Fun (string a, int[] b) => default; + [JSFunction] public static Task FunAsync () => default; + [JSFunction] public static Task FunAsyncWithArgs (Info a) => default; + [JSEvent] public static void EvtWithArgs (Info? a, bool? b) {} + } + """)); + Execute(); + Contains("JSExport] internal static void Space_Class_InvVoid () => global::Space.Class.InvVoid();"); + Contains("JSExport] internal static global::System.String Space_Class_InvWithArgs (global::System.String a, global::System.Int32[] b) => Serialize(global::Space.Class.InvWithArgs(Deserialize(a), b));"); + Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_InvAsync () => global::Space.Class.InvAsync();"); + Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvAsyncWithArgs (global::System.String? i) => Serialize(await global::Space.Class.InvAsyncWithArgs(Deserialize(i)));"); + Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.String Space_Class_Fun (global::System.String a, global::System.Int32[] b);"""); + Contains("""JSImport("Space.Class.funAsyncSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsync ();"""); + Contains("""JSImport("Space.Class.funAsyncWithArgsSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsyncWithArgs (global::System.String a);"""); + Contains("""JSImport("Space.Class.evtWithArgsSerialized", "Bootsharp")] internal static partial void Space_Class_EvtWithArgs (global::System.String? a, global::System.Boolean? b);"""); + } + + [Fact] + public void DoesntSerializeTypesThatShouldNotBeSerialized () + { + AddAssembly(With( + """ + namespace Space; + + public class Class + { + [JSInvokable] public static Task Inv (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; + [JSInvokable] public static Task InvNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; + } + """)); + Execute(); + Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_Inv (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Space.Class.Inv(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); + Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); + } + + [Fact] + public void SerializesTypesThatShouldBeSerialized () + { + AddAssembly(With( + """ + namespace Space; + + public record Info; + + public class Class + { + [JSInvokable] public static Task InvA () => default; + [JSInvokable] public static Task InvB () => default; + } + """)); + Execute(); + Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvA () => Serialize(await global::Space.Class.InvA());"); + Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvB () => Serialize(await global::Space.Class.InvB());"); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs new file mode 100644 index 00000000..91d724cc --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs @@ -0,0 +1,15 @@ +using Xunit.Sdk; + +namespace Bootsharp.Publish.Test; + +public class MockTest +{ + [Fact] + public void WhenCompileFailsIncludesSourceAndError () + { + var project = new MockProject(); + var asm = new MockAssembly("asm.dll", [new(null, "foo", false)]); + Assert.Contains("Invalid test source code", Assert.Throws(() => project.AddAssembly(asm)).Message); + Assert.Contains("foo", Assert.Throws(() => project.AddAssembly(asm)).Message); + } +} diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index 32b57b3d..e566effb 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -50,7 +50,7 @@ string GenerateSignature (MethodMeta inv, bool wait) : inv.ReturnValue.TypeSyntax); if (inv.ReturnValue.Serialized && inv.ReturnValue.Async) @return = $"global::System.Threading.Tasks.Task<{@return}>"; - var signature = $"{@return} {inv.Name} ({args})"; + var signature = $"{@return} {BuildMethodName(inv)} ({args})"; if (wait) signature = $"async {signature}"; return signature; } @@ -89,8 +89,9 @@ private void AddImportMethod (MethodMeta method) @return = $"global::System.Threading.Tasks.Task<{@return}>"; var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{BuildEndpoint(method)}Serialized", "Bootsharp")]"""; var date = MarshalAmbiguous(method.ReturnValue.TypeSyntax, true); - methods.Add($"{attr} {date}internal static partial {@return} {method.Name} ({args});"); - proxies.Add($"""Function.Set("{BuildEndpoint(method)}", {method.Name});"""); + var name = BuildMethodName(method); + methods.Add($"{attr} {date}internal static partial {@return} {name} ({args});"); + proxies.Add($"""Proxies.Set("{BuildEndpoint(method)}", {name});"""); string GenerateArg (ArgumentMeta arg) { @@ -106,4 +107,9 @@ string BuildEndpoint (MethodMeta method) return $"{method.JSSpace}.{name}"; } } + + private string BuildMethodName (MethodMeta method) + { + return $"{method.Space.Replace('.', '_')}_{method.Name}"; + } } From ed21203d960ceee86e582386cb54f2877d1f9f2f Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:28:50 +0300 Subject: [PATCH 18/75] iteration --- .../Emit/InteropTest.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index f3f11de2..e25fb5a7 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -23,7 +23,7 @@ internal static void RegisterProxies () } [Fact] - public void CanGenerateForMethodsInGlobalSpace () + public void GeneratesForMethodsInGlobalSpace () { AddAssembly(With( """ @@ -43,7 +43,7 @@ [JSEvent] public static void Evt () {} } [Fact] - public void CanGenerateForMethodsInCustomSpaces () + public void GeneratesForMethodsInCustomSpaces () { AddAssembly(With( """ @@ -79,6 +79,26 @@ [JSEvent] public static void Evt () {} Contains("""JSImport("SpaceA.SpaceB.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Evt ();"""); } + [Fact] + public void GeneratesForMethodsInGeneratedClasses () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(Space.IExport))] + [assembly:JSImport(typeof(IImport))] + + namespace Space { public interface IExport { void Inv (); } } + public interface IImport { void Fun (); void NotifyEvt(); } + """)); + Execute(); + // TODO: Proxies.Set sets de-serialized lambda wrapper over the interop method, so that we can Proxies.Get on the other side w/o additional processing. + // Contains("""Proxies.Set("Global.Import.fun", Bootsharp_Generated_Imports_IImport_Fun);"""); + // Contains("""Proxies.Set("Global.Import.onEvt", Bootsharp_Generated_Imports_IImport_OnEvt);"""); + // Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); + // Contains("""JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); + // Contains("""JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); + } + [Fact] public void HandlesVariousArgumentAndReturnTypes () { @@ -101,6 +121,10 @@ [JSEvent] public static void EvtWithArgs (Info? a, bool? b) {} } """)); Execute(); + Contains("""Proxies.Set("Space.Class.fun", Space_Class_Fun);"""); + Contains("""Proxies.Set("Space.Class.funAsync", Space_Class_FunAsync);"""); + Contains("""Proxies.Set("Space.Class.funAsyncWithArgs", Space_Class_FunAsyncWithArgs);"""); + Contains("""Proxies.Set("Space.Class.evtWithArgs", Space_Class_EvtWithArgs);"""); Contains("JSExport] internal static void Space_Class_InvVoid () => global::Space.Class.InvVoid();"); Contains("JSExport] internal static global::System.String Space_Class_InvWithArgs (global::System.String a, global::System.Int32[] b) => Serialize(global::Space.Class.InvWithArgs(Deserialize(a), b));"); Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_InvAsync () => global::Space.Class.InvAsync();"); From 26fdf63474d851cfaab48447c83f78fb19df2c95 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 13 Jan 2024 20:47:17 +0300 Subject: [PATCH 19/75] iteration --- .../Bootsharp.Common.Test.csproj | 2 +- .../Bootsharp.Inject.Test.csproj | 2 +- .../Bootsharp.Publish.Test.csproj | 2 +- .../Emit/InteropTest.cs | 66 +++++++------------ .../Bootsharp.Publish/Common/TypeUtilities.cs | 2 +- 5 files changed, 29 insertions(+), 45 deletions(-) diff --git a/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj b/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj index 585af504..bb33f939 100644 --- a/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj +++ b/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj @@ -12,7 +12,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj b/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj index f9aa7b29..aae3a8b0 100644 --- a/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj +++ b/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj @@ -14,7 +14,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj b/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj index 383261d0..ce4985ec 100644 --- a/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj +++ b/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj @@ -15,7 +15,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index e25fb5a7..b6ea7647 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -99,42 +99,6 @@ public interface IImport { void Fun (); void NotifyEvt(); } // Contains("""JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); } - [Fact] - public void HandlesVariousArgumentAndReturnTypes () - { - AddAssembly(With( - """ - namespace Space; - - public record Info; - - public class Class - { - [JSInvokable] public static void InvVoid () {} - [JSInvokable] public static Info InvWithArgs (Info a, int[] b) => default; - [JSInvokable] public static Task InvAsync () => default; - [JSInvokable] public static Task InvAsyncWithArgs (Info? i) => default; - [JSFunction] public static Info Fun (string a, int[] b) => default; - [JSFunction] public static Task FunAsync () => default; - [JSFunction] public static Task FunAsyncWithArgs (Info a) => default; - [JSEvent] public static void EvtWithArgs (Info? a, bool? b) {} - } - """)); - Execute(); - Contains("""Proxies.Set("Space.Class.fun", Space_Class_Fun);"""); - Contains("""Proxies.Set("Space.Class.funAsync", Space_Class_FunAsync);"""); - Contains("""Proxies.Set("Space.Class.funAsyncWithArgs", Space_Class_FunAsyncWithArgs);"""); - Contains("""Proxies.Set("Space.Class.evtWithArgs", Space_Class_EvtWithArgs);"""); - Contains("JSExport] internal static void Space_Class_InvVoid () => global::Space.Class.InvVoid();"); - Contains("JSExport] internal static global::System.String Space_Class_InvWithArgs (global::System.String a, global::System.Int32[] b) => Serialize(global::Space.Class.InvWithArgs(Deserialize(a), b));"); - Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_InvAsync () => global::Space.Class.InvAsync();"); - Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvAsyncWithArgs (global::System.String? i) => Serialize(await global::Space.Class.InvAsyncWithArgs(Deserialize(i)));"); - Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.String Space_Class_Fun (global::System.String a, global::System.Int32[] b);"""); - Contains("""JSImport("Space.Class.funAsyncSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsync ();"""); - Contains("""JSImport("Space.Class.funAsyncWithArgsSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsyncWithArgs (global::System.String a);"""); - Contains("""JSImport("Space.Class.evtWithArgsSerialized", "Bootsharp")] internal static partial void Space_Class_EvtWithArgs (global::System.String? a, global::System.Boolean? b);"""); - } - [Fact] public void DoesntSerializeTypesThatShouldNotBeSerialized () { @@ -146,11 +110,17 @@ public class Class { [JSInvokable] public static Task Inv (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; [JSInvokable] public static Task InvNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; + [JSFunction] public static Task Fun (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; + [JSFunction] public static Task FunNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; } """)); Execute(); + Contains("""Proxies.Set("Space.Class.fun", Space_Class_Fun);"""); + Contains("""Proxies.Set("Space.Class.funNull", Space_Class_FunNull);"""); Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_Inv (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Space.Class.Inv(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); + Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_Fun (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16);"""); + Contains("""JSImport("Space.Class.funNullSerialized", "Bootsharp")] [return: JSMarshalAs>] internal static partial global::System.Threading.Tasks.Task Space_Class_FunNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16);"""); } [Fact] @@ -160,16 +130,30 @@ public void SerializesTypesThatShouldBeSerialized () """ namespace Space; - public record Info; + public record Record; public class Class { - [JSInvokable] public static Task InvA () => default; - [JSInvokable] public static Task InvB () => default; + [JSInvokable] public static Record InvA (Record a) => default; + [JSInvokable] public static Task InvB (Record?[]? a) => default; + [JSFunction] public static Record FunA (Record a) => default; + [JSFunction] public static Task FunB (Record?[]? a) => default; + + [JSInvokable] public static Task InvAsyncBytes () => default; + [JSFunction] public static Task FunAsyncBytes () => default; } """)); Execute(); - Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvA () => Serialize(await global::Space.Class.InvA());"); - Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvB () => Serialize(await global::Space.Class.InvB());"); + Contains("""Proxies.Set("Space.Class.funA", Space_Class_FunA);"""); + Contains("""Proxies.Set("Space.Class.funB", Space_Class_FunB);"""); + Contains("JSExport] internal static global::System.String Space_Class_InvA (global::System.String a) => Serialize(global::Space.Class.InvA(Deserialize(a)));"); + Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvB (global::System.String? a) => Serialize(await global::Space.Class.InvB(Deserialize(a)));"); + Contains("""JSImport("Space.Class.funASerialized", "Bootsharp")] internal static partial global::System.String Space_Class_FunA (global::System.String a);"""); + Contains("""JSImport("Space.Class.funBSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunB (global::System.String? a);"""); + + // TODO: Remove when resolved: https://github.com/elringus/bootsharp/issues/138 + Contains("""Proxies.Set("Space.Class.funAsyncBytes", Space_Class_FunAsyncBytes);"""); + Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvAsyncBytes () => Serialize(await global::Space.Class.InvAsyncBytes());"); + Contains("""JSImport("Space.Class.funAsyncBytesSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsyncBytes ();"""); } } diff --git a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs index 7b72e97b..00374a58 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs @@ -157,7 +157,7 @@ public static bool ShouldSerialize (Type type) { if (IsVoid(type)) return false; if (IsTaskWithResult(type, out var result)) - // TODO: Remove IsList (eg, serialization of Task) when https://github.com/dotnet/runtime/issues/81348 is resolved. + // TODO: Remove 'IsList(result)' when resolved: https://github.com/elringus/bootsharp/issues/138 return IsList(result) || ShouldSerialize(result); var array = type.IsArray; if (array) type = type.GetElementType()!; From 8674e8baa890f51f6a5a5c9f2bd3a3f648e5c552 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 13 Jan 2024 20:59:04 +0300 Subject: [PATCH 20/75] etc --- src/cs/Bootsharp/Build/Bootsharp.targets | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 355259c2..7488e90b 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -158,14 +158,11 @@ + SourceFiles="@(BootsharpMaps)" DestinationFolder="$(BootsharpBinariesDirectory)"/> + SourceFiles="@(BootsharpSymbols)" DestinationFolder="$(BootsharpBinariesDirectory)"/> + SourceFiles="@(BootsharpPdbs)" DestinationFolder="$(BootsharpBinariesDirectory)"/> From 81ee7a2c92f8e613e1bac9f9b88b049f5710a2ab Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 13 Jan 2024 21:10:32 +0300 Subject: [PATCH 21/75] etc --- src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index b6ea7647..c5b6c2ba 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -91,7 +91,6 @@ namespace Space { public interface IExport { void Inv (); } } public interface IImport { void Fun (); void NotifyEvt(); } """)); Execute(); - // TODO: Proxies.Set sets de-serialized lambda wrapper over the interop method, so that we can Proxies.Get on the other side w/o additional processing. // Contains("""Proxies.Set("Global.Import.fun", Bootsharp_Generated_Imports_IImport_Fun);"""); // Contains("""Proxies.Set("Global.Import.onEvt", Bootsharp_Generated_Imports_IImport_OnEvt);"""); // Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); @@ -144,6 +143,7 @@ public class Class } """)); Execute(); + // TODO: Proxies.Set sets de-serialized lambda wrapper over the interop method, so that we can Proxies.Get on the other side w/o additional processing. Contains("""Proxies.Set("Space.Class.funA", Space_Class_FunA);"""); Contains("""Proxies.Set("Space.Class.funB", Space_Class_FunB);"""); Contains("JSExport] internal static global::System.String Space_Class_InvA (global::System.String a) => Serialize(global::Space.Class.InvA(Deserialize(a)));"); From a1a0921d186d46f92f7fce28bafc9a4b7144d3f9 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 13 Jan 2024 23:16:52 +0300 Subject: [PATCH 22/75] iteration --- src/cs/Bootsharp.Common.Test/ExceptionTest.cs | 12 ---- src/cs/Bootsharp.Common.Test/ProxiesTest.cs | 28 +++++++++ .../{Exceptions => }/Error.cs | 0 .../Exceptions/NotIntercepted.cs | 9 --- src/cs/Bootsharp.Common/Interop/Proxies.cs | 59 +++++++++++++++++++ .../{ => Interop}/Serializer.cs | 2 +- .../Emit/InteropTest.cs | 32 +++++----- .../Emit/InteropGenerator.cs | 29 ++++----- 8 files changed, 116 insertions(+), 55 deletions(-) delete mode 100644 src/cs/Bootsharp.Common.Test/ExceptionTest.cs create mode 100644 src/cs/Bootsharp.Common.Test/ProxiesTest.cs rename src/cs/Bootsharp.Common/{Exceptions => }/Error.cs (100%) delete mode 100644 src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs create mode 100644 src/cs/Bootsharp.Common/Interop/Proxies.cs rename src/cs/Bootsharp.Common/{ => Interop}/Serializer.cs (94%) diff --git a/src/cs/Bootsharp.Common.Test/ExceptionTest.cs b/src/cs/Bootsharp.Common.Test/ExceptionTest.cs deleted file mode 100644 index 9377e54f..00000000 --- a/src/cs/Bootsharp.Common.Test/ExceptionTest.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Bootsharp.Common.Test; - -public class ExceptionTest -{ - [Fact] - public void NotImplementedIncludesMethodName () - { - Assert.Contains("$Func", Assert.Throws(Func).Message); - } - - private static void Func () => throw new NotIntercepted(); -} diff --git a/src/cs/Bootsharp.Common.Test/ProxiesTest.cs b/src/cs/Bootsharp.Common.Test/ProxiesTest.cs new file mode 100644 index 00000000..7763446e --- /dev/null +++ b/src/cs/Bootsharp.Common.Test/ProxiesTest.cs @@ -0,0 +1,28 @@ +using static Bootsharp.Proxies; + +namespace Bootsharp.Common.Test; + +public class ProxiesTest +{ + [Fact] + public void WhenEndpointNotFoundErrorIsThrown () + { + Assert.Contains("Proxy 'foo' is not found.", + Assert.Throws(() => Get("foo")).Message); + } + + [Fact] + public void WhenFunctionTypeIsWrongErrorIsThrown () + { + Set("bar", null); + Assert.Contains("Proxy 'bar' is not 'System.Action'.", + Assert.Throws(() => Get("bar")).Message); + } + + [Fact] + public void CanSetAndGetDelegate () + { + Set("echo", (int x, int y) => x + y); + Assert.Equal(15, Get>("echo")(6, 9)); + } +} diff --git a/src/cs/Bootsharp.Common/Exceptions/Error.cs b/src/cs/Bootsharp.Common/Error.cs similarity index 100% rename from src/cs/Bootsharp.Common/Exceptions/Error.cs rename to src/cs/Bootsharp.Common/Error.cs diff --git a/src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs b/src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs deleted file mode 100644 index 9756a992..00000000 --- a/src/cs/Bootsharp.Common/Exceptions/NotIntercepted.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Bootsharp; - -/// -/// Exception thrown when Bootsharp fails to intercept user-defined interop method. -/// -public sealed class NotIntercepted ([CallerMemberName] string name = "") : - Exception($"Bootsharp failed to intercept '${name}'."); diff --git a/src/cs/Bootsharp.Common/Interop/Proxies.cs b/src/cs/Bootsharp.Common/Interop/Proxies.cs new file mode 100644 index 00000000..a5a53a0e --- /dev/null +++ b/src/cs/Bootsharp.Common/Interop/Proxies.cs @@ -0,0 +1,59 @@ +namespace Bootsharp; + +/// +/// Provides access to generated interop methods for JavaScript functions and events. +/// +/// +/// Below is for internal reference; end users are not expected to use this API.
+/// Partial interop methods ( and ) +/// are accessed via delegates registered by associated IDs, where ID is the full name of +/// the declaring type of the interop method joined with the method's name by a dot. Eg, given:
+/// +/// namespace Space; +/// public static partial class Class +/// { +/// [JSFunction] public static partial int Foo (string arg); +/// } +///
+/// Proxy for the "Foo" method is registered as follows (emitted at build; +/// actual code will have additional de-/serialization steps):
+/// +/// Proxies.Set("Space.Class.Foo", (arg) => Bootsharp.Generated.Interop.Space_Class_Foo(arg)); +///
+/// "Foo" method is invoked via registered proxy +/// (emitted by source generator to implement original partial "Foo" method):
+/// +/// public static int Foo (string arg) => >("Space.Class.Foo")(arg);]]> +///
+///
+public static class Proxies +{ + private static readonly Dictionary map = new(); + + /// + /// Maps specified interop delegate by the specified ID. + /// + /// + /// Performed in the generated interop code at module initialization. + /// + public static void Set (string id, Delegate @delegate) + { + map[id] = @delegate; + } + + /// + /// Returns interop delegate of specified ID and type. + /// + /// + /// Used in sources generated for partial + /// and methods. + /// + public static T Get (string id) where T : Delegate + { + if (!map.TryGetValue(id, out var @delegate)) + throw new Error($"Proxy '{id}' is not found."); + if (@delegate is not T specific) + throw new Error($"Proxy '{id}' is not '{typeof(T)}'."); + return specific; + } +} diff --git a/src/cs/Bootsharp.Common/Serializer.cs b/src/cs/Bootsharp.Common/Interop/Serializer.cs similarity index 94% rename from src/cs/Bootsharp.Common/Serializer.cs rename to src/cs/Bootsharp.Common/Interop/Serializer.cs index 9914c13c..74d628dd 100644 --- a/src/cs/Bootsharp.Common/Serializer.cs +++ b/src/cs/Bootsharp.Common/Interop/Serializer.cs @@ -5,7 +5,7 @@ namespace Bootsharp; /// -/// Handles serialization of the interop data that can't be passed to and from JavaScript as-is. +/// Handles serialization of the interop values that can't be passed to and from JavaScript as-is. /// public static class Serializer { diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index c5b6c2ba..3eb95eed 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -35,8 +35,8 @@ [JSEvent] public static void Evt () {} } """)); Execute(); - Contains("""Proxies.Set("Global.Class.fun", Class_Fun);"""); - Contains("""Proxies.Set("Global.Class.evt", Class_Evt);"""); + Contains("""Proxies.Set("Class.Fun", Class_Fun);"""); + Contains("""Proxies.Set("Class.Evt", Class_Evt);"""); Contains("[System.Runtime.InteropServices.JavaScript.JSExport] internal static void Class_Inv () => global::Class.Inv();"); Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); @@ -67,10 +67,10 @@ [JSEvent] public static void Evt () {} } """)); Execute(); - Contains("""Proxies.Set("SpaceA.Class.fun", SpaceA_Class_Fun);"""); - Contains("""Proxies.Set("SpaceA.Class.evt", SpaceA_Class_Evt);"""); - Contains("""Proxies.Set("SpaceA.SpaceB.Class.fun", SpaceA_SpaceB_Class_Fun);"""); - Contains("""Proxies.Set("SpaceA.SpaceB.Class.evt", SpaceA_SpaceB_Class_Evt);"""); + Contains("""Proxies.Set("SpaceA.Class.Fun", SpaceA_Class_Fun);"""); + Contains("""Proxies.Set("SpaceA.Class.Evt", SpaceA_Class_Evt);"""); + Contains("""Proxies.Set("SpaceA.SpaceB.Class.Fun", SpaceA_SpaceB_Class_Fun);"""); + Contains("""Proxies.Set("SpaceA.SpaceB.Class.Evt", SpaceA_SpaceB_Class_Evt);"""); Contains("JSExport] internal static void SpaceA_Class_Inv () => global::SpaceA.Class.Inv();"); Contains("""JSImport("SpaceA.Class.funSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Fun ();"""); Contains("""JSImport("SpaceA.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Evt ();"""); @@ -91,8 +91,8 @@ namespace Space { public interface IExport { void Inv (); } } public interface IImport { void Fun (); void NotifyEvt(); } """)); Execute(); - // Contains("""Proxies.Set("Global.Import.fun", Bootsharp_Generated_Imports_IImport_Fun);"""); - // Contains("""Proxies.Set("Global.Import.onEvt", Bootsharp_Generated_Imports_IImport_OnEvt);"""); + // Contains("""Proxies.Set("Global.Import.Fun", Bootsharp_Generated_Imports_IImport_Fun);"""); + // Contains("""Proxies.Set("Global.Import.NotifyEvt", Bootsharp_Generated_Imports_IImport_OnEvt);"""); // Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); // Contains("""JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); // Contains("""JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); @@ -114,8 +114,8 @@ public class Class } """)); Execute(); - Contains("""Proxies.Set("Space.Class.fun", Space_Class_Fun);"""); - Contains("""Proxies.Set("Space.Class.funNull", Space_Class_FunNull);"""); + Contains("""Proxies.Set("Space.Class.Fun", Space_Class_Fun);"""); + Contains("""Proxies.Set("Space.Class.FunNull", Space_Class_FunNull);"""); Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_Inv (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Space.Class.Inv(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_Fun (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16);"""); @@ -135,24 +135,24 @@ public class Class { [JSInvokable] public static Record InvA (Record a) => default; [JSInvokable] public static Task InvB (Record?[]? a) => default; - [JSFunction] public static Record FunA (Record a) => default; - [JSFunction] public static Task FunB (Record?[]? a) => default; + [JSFunction] public static Record FunA (Record a) => Proxies.Get>("Space.Class.FunA")(a); + [JSFunction] public static Task FunB (Record?[]? a) => Proxies.Get>>("Space.Class.FunB")(a); [JSInvokable] public static Task InvAsyncBytes () => default; - [JSFunction] public static Task FunAsyncBytes () => default; + [JSFunction] public static Task FunAsyncBytes () => Proxies.Get>>("Space.Class.funAsyncBytes")(); } """)); Execute(); // TODO: Proxies.Set sets de-serialized lambda wrapper over the interop method, so that we can Proxies.Get on the other side w/o additional processing. - Contains("""Proxies.Set("Space.Class.funA", Space_Class_FunA);"""); - Contains("""Proxies.Set("Space.Class.funB", Space_Class_FunB);"""); + Contains("""Proxies.Set("Space.Class.FunA", Space_Class_FunA);"""); + Contains("""Proxies.Set("Space.Class.FunB", Space_Class_FunB);"""); Contains("JSExport] internal static global::System.String Space_Class_InvA (global::System.String a) => Serialize(global::Space.Class.InvA(Deserialize(a)));"); Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvB (global::System.String? a) => Serialize(await global::Space.Class.InvB(Deserialize(a)));"); Contains("""JSImport("Space.Class.funASerialized", "Bootsharp")] internal static partial global::System.String Space_Class_FunA (global::System.String a);"""); Contains("""JSImport("Space.Class.funBSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunB (global::System.String? a);"""); // TODO: Remove when resolved: https://github.com/elringus/bootsharp/issues/138 - Contains("""Proxies.Set("Space.Class.funAsyncBytes", Space_Class_FunAsyncBytes);"""); + Contains("""Proxies.Set("Space.Class.FunAsyncBytes", Space_Class_FunAsyncBytes);"""); Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvAsyncBytes () => Serialize(await global::Space.Class.InvAsyncBytes());"); Contains("""JSImport("Space.Class.funAsyncBytesSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsyncBytes ();"""); } diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index e566effb..bff16405 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -40,11 +40,11 @@ private void AddExportMethod (MethodMeta inv) const string attr = "[System.Runtime.InteropServices.JavaScript.JSExport]"; var date = MarshalAmbiguous(inv.ReturnValue.TypeSyntax, true); var wait = inv.ReturnValue.Async && inv.ReturnValue.Serialized; - methods.Add($"{attr} {date}internal static {GenerateSignature(inv, wait)} => {GenerateBody(inv, wait)};"); + methods.Add($"{attr} {date}internal static {BuildSignature(inv, wait)} => {BuildBody(inv, wait)};"); - string GenerateSignature (MethodMeta inv, bool wait) + string BuildSignature (MethodMeta inv, bool wait) { - var args = string.Join(", ", inv.Arguments.Select(GenerateSignatureArg)); + var args = string.Join(", ", inv.Arguments.Select(BuildSignatureArg)); var @return = inv.ReturnValue.Void ? "void" : (inv.ReturnValue.Serialized ? $"global::System.String{(inv.ReturnValue.Nullable ? "?" : "")}" : inv.ReturnValue.TypeSyntax); @@ -55,16 +55,16 @@ string GenerateSignature (MethodMeta inv, bool wait) return signature; } - string GenerateBody (MethodMeta inv, bool wait) + string BuildBody (MethodMeta inv, bool wait) { - var args = string.Join(", ", inv.Arguments.Select(GenerateBodyArg)); + var args = string.Join(", ", inv.Arguments.Select(BuildBodyArg)); var body = $"global::{inv.Space}.{inv.Name}({args})"; if (wait) body = $"await {body}"; if (inv.ReturnValue.Serialized) body = $"Serialize({body})"; return body; } - string GenerateSignatureArg (ArgumentMeta arg) + string BuildSignatureArg (ArgumentMeta arg) { var type = arg.Value.Serialized ? $"global::System.String{(arg.Value.Nullable ? "?" : "")}" @@ -72,7 +72,7 @@ string GenerateSignatureArg (ArgumentMeta arg) return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; } - string GenerateBodyArg (ArgumentMeta arg) + string BuildBodyArg (ArgumentMeta arg) { if (!arg.Value.Serialized) return arg.Name; return $"Deserialize<{arg.Value.TypeSyntax}>({arg.Name})"; @@ -81,31 +81,26 @@ string GenerateBodyArg (ArgumentMeta arg) private void AddImportMethod (MethodMeta method) { - var args = string.Join(", ", method.Arguments.Select(GenerateArg)); + var args = string.Join(", ", method.Arguments.Select(BuildArg)); var @return = method.ReturnValue.Void ? "void" : (method.ReturnValue.Serialized ? $"global::System.String{(method.ReturnValue.Nullable ? "?" : "")}" : method.ReturnValue.TypeSyntax); if (method.ReturnValue.Serialized && method.ReturnValue.Async) @return = $"global::System.Threading.Tasks.Task<{@return}>"; - var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{BuildEndpoint(method)}Serialized", "Bootsharp")]"""; + var endpoint = $"{method.JSSpace}.{method.JSName}Serialized"; + var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{endpoint}", "Bootsharp")]"""; var date = MarshalAmbiguous(method.ReturnValue.TypeSyntax, true); var name = BuildMethodName(method); methods.Add($"{attr} {date}internal static partial {@return} {name} ({args});"); - proxies.Add($"""Proxies.Set("{BuildEndpoint(method)}", {name});"""); + proxies.Add($"""Proxies.Set("{method.Space}.{method.Name}", {name});"""); - string GenerateArg (ArgumentMeta arg) + string BuildArg (ArgumentMeta arg) { var type = arg.Value.Serialized ? $"global::System.String{(arg.Value.Nullable ? "?" : "")}" : arg.Value.TypeSyntax; return $"{MarshalAmbiguous(arg.Value.TypeSyntax, false)}{type} {arg.Name}"; } - - string BuildEndpoint (MethodMeta method) - { - var name = char.ToLowerInvariant(method.Name[0]) + method.Name[1..]; - return $"{method.JSSpace}.{name}"; - } } private string BuildMethodName (MethodMeta method) From 9bb118fc9f842522fa6f4b374ab72eb5e329e930 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 13 Jan 2024 23:20:05 +0300 Subject: [PATCH 23/75] etc --- src/cs/Bootsharp.Common/Interop/Proxies.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Bootsharp.Common/Interop/Proxies.cs b/src/cs/Bootsharp.Common/Interop/Proxies.cs index a5a53a0e..6159f3ef 100644 --- a/src/cs/Bootsharp.Common/Interop/Proxies.cs +++ b/src/cs/Bootsharp.Common/Interop/Proxies.cs @@ -20,7 +20,7 @@ /// /// Proxies.Set("Space.Class.Foo", (arg) => Bootsharp.Generated.Interop.Space_Class_Foo(arg)); ///
-/// "Foo" method is invoked via registered proxy +/// Registered proxy is accessed as follows /// (emitted by source generator to implement original partial "Foo" method):
/// /// public static int Foo (string arg) => >("Space.Class.Foo")(arg);]]> From 6d721384671db9e3c4980780e7fab2b9cfb3bf81 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 14 Jan 2024 03:50:16 +0300 Subject: [PATCH 24/75] iteration --- src/cs/Bootsharp.Common/Interop/Proxies.cs | 2 +- .../Emit/InteropTest.cs | 12 +++++------ .../Mock/MockCompiler.cs | 8 ++++++-- .../Pack/DeclarationTest.cs | 20 +++++++++++-------- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/cs/Bootsharp.Common/Interop/Proxies.cs b/src/cs/Bootsharp.Common/Interop/Proxies.cs index 6159f3ef..8bf16c39 100644 --- a/src/cs/Bootsharp.Common/Interop/Proxies.cs +++ b/src/cs/Bootsharp.Common/Interop/Proxies.cs @@ -31,7 +31,7 @@ public static class Proxies private static readonly Dictionary map = new(); /// - /// Maps specified interop delegate by the specified ID. + /// Maps specified interop delegate to the specified ID. /// /// /// Performed in the generated interop code at module initialization. diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index 3eb95eed..7c811dda 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -107,19 +107,19 @@ namespace Space; public class Class { - [JSInvokable] public static Task Inv (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; - [JSInvokable] public static Task InvNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; - [JSFunction] public static Task Fun (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default; - [JSFunction] public static Task FunNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte?[] a13, int?[] a14, double?[] a15, string?[] a16) => default; + [JSInvokable] public static Task Inv (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => default!; + [JSInvokable] public static Task InvNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte[]? a13, int[]? a14, double[]? a15, string[]? a16) => default!; + [JSFunction] public static Task Fun (bool a1, byte a2, char a3, short a4, long a5, int a6, float a7, double a8, nint a9, DateTime a10, DateTimeOffset a11, string a12, byte[] a13, int[] a14, double[] a15, string[] a16) => Proxies.Get>>("Space.Class.Fun")(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); + [JSFunction] public static Task FunNull (bool? a1, byte? a2, char? a3, short? a4, long? a5, int? a6, float? a7, double? a8, nint? a9, DateTime? a10, DateTimeOffset? a11, string? a12, byte[]? a13, int[]? a14, double[]? a15, string[]? a16) => Proxies.Get>>("Space.Class.FunNull")(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); } """)); Execute(); Contains("""Proxies.Set("Space.Class.Fun", Space_Class_Fun);"""); Contains("""Proxies.Set("Space.Class.FunNull", Space_Class_FunNull);"""); Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_Inv (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Space.Class.Inv(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); - Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); + Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte[]? a13, global::System.Int32[]? a14, global::System.Double[]? a15, global::System.String[]? a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_Fun (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16);"""); - Contains("""JSImport("Space.Class.funNullSerialized", "Bootsharp")] [return: JSMarshalAs>] internal static partial global::System.Threading.Tasks.Task Space_Class_FunNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte?[] a13, global::System.Int32?[] a14, global::System.Double?[] a15, global::System.String?[] a16);"""); + Contains("""JSImport("Space.Class.funNullSerialized", "Bootsharp")] [return: JSMarshalAs>] internal static partial global::System.Threading.Tasks.Task Space_Class_FunNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte[]? a13, global::System.Int32[]? a14, global::System.Double[]? a15, global::System.String[]? a16);"""); } [Fact] diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs index 66f3a848..acc8f81d 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs @@ -14,8 +14,12 @@ public class MockCompiler public void Compile (IEnumerable sources, string assemblyPath) { - var source = string.Join('\n', defaultUsings.Select(u => $"using {u};")) + '\n' + - string.Join('\n', sources.Select(BuildSource)); + var source = + $""" + #nullable enable + {string.Join('\n', defaultUsings.Select(u => $"using {u};"))} + {string.Join('\n', sources.Select(BuildSource))} + """; var compilation = CreateCompilation(assemblyPath, source); var result = compilation.Emit(assemblyPath); if (result.Success) return; diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 3b4cb240..dda7b3e1 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -347,18 +347,22 @@ public void DefinitionIsGeneratedForTypeWithReadOnlyDictionaryProperty () public void DefinitionIsGeneratedForGenericClass () { AddAssembly( - With("n", "public class GenericClass { public T Value { get; set; } }"), - WithClass("n", "[JSInvokable] public static void Method (GenericClass p) { }")); + With("n", "public class Generic where T: notnull { public T Value { get; set; } }"), + With("n", "public class GenericNull { public T Value { get; set; } }"), + WithClass("n", "[JSInvokable] public static void Method (Generic a, GenericNull b) { }")); Execute(); Contains( """ export namespace n { - export interface GenericClass { + export interface Generic { value: T; } + export interface GenericNull { + value?: T; + } } export namespace n.Class { - export function method(p: n.GenericClass): void; + export function method(a: n.Generic, b: n.GenericNull): void; } """); } @@ -370,7 +374,7 @@ public void DefinitionIsGeneratedForGenericInterface () With("n", "public interface GenericInterface { public T Value { get; set; } }"), WithClass("n", "[JSInvokable] public static GenericInterface Method () => default;")); Execute(); - Matches(@"export interface GenericInterface {\s*value: T;\s*}"); + Matches(@"export interface GenericInterface {\s*value\?: T;\s*}"); Contains("method(): n.GenericInterface"); } @@ -382,8 +386,8 @@ public void DefinitionIsGeneratedForNestedGenericTypes () With("Bar", "public interface GenericInterface { public T Value { get; set; } }"), WithClass("n", "[JSInvokable] public static void Method (Foo.GenericClass> p) { }")); Execute(); - Matches(@"export namespace Foo {\s*export interface GenericClass {\s*value: T;\s*}\s*}"); - Matches(@"export namespace Bar {\s*export interface GenericInterface {\s*value: T;\s*}\s*}"); + Matches(@"export namespace Foo {\s*export interface GenericClass {\s*value\?: T;\s*}\s*}"); + Matches(@"export namespace Bar {\s*export interface GenericInterface {\s*value\?: T;\s*}\s*}"); Contains("method(p: Foo.GenericClass>): void"); } @@ -394,7 +398,7 @@ public void DefinitionIsGeneratedForGenericClassWithMultipleTypeArguments () WithClass("n", "public class GenericClass { public T1 Key { get; set; } public T2 Value { get; set; } }"), WithClass("n", "[JSInvokable] public static void Method (GenericClass p) { }")); Execute(); - Matches(@"export interface GenericClass {\s*key: T1;\s*value: T2;\s*}"); + Matches(@"export interface GenericClass {\s*key\?: T1;\s*value\?: T2;\s*}"); Contains("method(p: n.Class.GenericClass): void"); } From 95348c25c5d73e01ab898cbcaab79aec5829be97 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 14 Jan 2024 03:56:52 +0300 Subject: [PATCH 25/75] iteration --- src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index 7c811dda..2903bbed 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -30,8 +30,8 @@ public void GeneratesForMethodsInGlobalSpace () public class Class { [JSInvokable] public static void Inv () {} - [JSFunction] public static void Fun () {} - [JSEvent] public static void Evt () {} + [JSFunction] public static void Fun () => Proxies.Get("Class.Fun")(); + [JSEvent] public static void Evt () => Proxies.Get("Class.Evt")(); } """)); Execute(); @@ -52,8 +52,8 @@ namespace SpaceA public class Class { [JSInvokable] public static void Inv () {} - [JSFunction] public static void Fun () {} - [JSEvent] public static void Evt () {} + [JSFunction] public static void Fun () => Proxies.Get("SpaceA.Class.Fun")(); + [JSEvent] public static void Evt () => Proxies.Get("SpaceA.Class.Evt")(); } } namespace SpaceA.SpaceB @@ -61,8 +61,8 @@ namespace SpaceA.SpaceB public class Class { [JSInvokable] public static void Inv () {} - [JSFunction] public static void Fun () {} - [JSEvent] public static void Evt () {} + [JSFunction] public static void Fun () => Proxies.Get("SpaceA.SpaceB.Class.Fun")(); + [JSEvent] public static void Evt () => Proxies.Get("SpaceA.SpaceB.Class.Evt")(); } } """)); From 66a05551fc987f833aa961c4deb8f744d2092e23 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 14 Jan 2024 22:13:42 +0300 Subject: [PATCH 26/75] iteration --- .../Emit/InteropTest.cs | 27 +++++++++---------- .../Emit/InteropGenerator.cs | 13 +++++---- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index 2903bbed..e5a1ed9a 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -35,8 +35,8 @@ [JSInvokable] public static void Inv () {} } """)); Execute(); - Contains("""Proxies.Set("Class.Fun", Class_Fun);"""); - Contains("""Proxies.Set("Class.Evt", Class_Evt);"""); + Contains("""Proxies.Set("Class.Fun", () => Class_Fun());"""); + Contains("""Proxies.Set("Class.Evt", () => Class_Evt());"""); Contains("[System.Runtime.InteropServices.JavaScript.JSExport] internal static void Class_Inv () => global::Class.Inv();"); Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); @@ -67,10 +67,10 @@ [JSInvokable] public static void Inv () {} } """)); Execute(); - Contains("""Proxies.Set("SpaceA.Class.Fun", SpaceA_Class_Fun);"""); - Contains("""Proxies.Set("SpaceA.Class.Evt", SpaceA_Class_Evt);"""); - Contains("""Proxies.Set("SpaceA.SpaceB.Class.Fun", SpaceA_SpaceB_Class_Fun);"""); - Contains("""Proxies.Set("SpaceA.SpaceB.Class.Evt", SpaceA_SpaceB_Class_Evt);"""); + Contains("""Proxies.Set("SpaceA.Class.Fun", () => SpaceA_Class_Fun());"""); + Contains("""Proxies.Set("SpaceA.Class.Evt", () => SpaceA_Class_Evt());"""); + Contains("""Proxies.Set("SpaceA.SpaceB.Class.Fun", () => SpaceA_SpaceB_Class_Fun());"""); + Contains("""Proxies.Set("SpaceA.SpaceB.Class.Evt", () => SpaceA_SpaceB_Class_Evt());"""); Contains("JSExport] internal static void SpaceA_Class_Inv () => global::SpaceA.Class.Inv();"); Contains("""JSImport("SpaceA.Class.funSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Fun ();"""); Contains("""JSImport("SpaceA.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_Class_Evt ();"""); @@ -91,8 +91,8 @@ namespace Space { public interface IExport { void Inv (); } } public interface IImport { void Fun (); void NotifyEvt(); } """)); Execute(); - // Contains("""Proxies.Set("Global.Import.Fun", Bootsharp_Generated_Imports_IImport_Fun);"""); - // Contains("""Proxies.Set("Global.Import.NotifyEvt", Bootsharp_Generated_Imports_IImport_OnEvt);"""); + // Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.Fun", () => Bootsharp_Generated_Imports_IImport_Fun());"""); + // Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.OnEvt", () => Bootsharp_Generated_Imports_IImport_OnEvt());"""); // Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); // Contains("""JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); // Contains("""JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); @@ -114,8 +114,8 @@ public class Class } """)); Execute(); - Contains("""Proxies.Set("Space.Class.Fun", Space_Class_Fun);"""); - Contains("""Proxies.Set("Space.Class.FunNull", Space_Class_FunNull);"""); + Contains("""Proxies.Set("Space.Class.Fun", (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) => Space_Class_Fun(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16));"""); + Contains("""Proxies.Set("Space.Class.FunNull", (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) => Space_Class_FunNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16));"""); Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_Inv (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Space.Class.Inv(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte[]? a13, global::System.Int32[]? a14, global::System.Double[]? a15, global::System.String[]? a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_Fun (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16);"""); @@ -143,16 +143,15 @@ public class Class } """)); Execute(); - // TODO: Proxies.Set sets de-serialized lambda wrapper over the interop method, so that we can Proxies.Get on the other side w/o additional processing. - Contains("""Proxies.Set("Space.Class.FunA", Space_Class_FunA);"""); - Contains("""Proxies.Set("Space.Class.FunB", Space_Class_FunB);"""); + Contains("""Proxies.Set("Space.Class.FunA", (a) => Deserialize(Space_Class_FunA(Serialize(a))));"""); + Contains("""Proxies.Set("Space.Class.FunB", async (a) => Deserialize(await Space_Class_FunB(Serialize(a))));"""); Contains("JSExport] internal static global::System.String Space_Class_InvA (global::System.String a) => Serialize(global::Space.Class.InvA(Deserialize(a)));"); Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvB (global::System.String? a) => Serialize(await global::Space.Class.InvB(Deserialize(a)));"); Contains("""JSImport("Space.Class.funASerialized", "Bootsharp")] internal static partial global::System.String Space_Class_FunA (global::System.String a);"""); Contains("""JSImport("Space.Class.funBSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunB (global::System.String? a);"""); // TODO: Remove when resolved: https://github.com/elringus/bootsharp/issues/138 - Contains("""Proxies.Set("Space.Class.FunAsyncBytes", Space_Class_FunAsyncBytes);"""); + Contains("""Proxies.Set("Space.Class.FunAsyncBytes", async () => Deserialize(await Space_Class_FunAsyncBytes()));"""); Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvAsyncBytes () => Serialize(await global::Space.Class.InvAsyncBytes());"); Contains("""JSImport("Space.Class.funAsyncBytesSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsyncBytes ();"""); } diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index bff16405..b1a31fa2 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -10,9 +10,9 @@ internal sealed class InteropGenerator public string Generate (AssemblyInspection inspection) { - foreach (var method in inspection.Methods) + foreach (var method in inspection.Methods) // @formatter:off if (method.Type == MethodType.Invokable) AddExportMethod(method); - else AddImportMethod(method); + else { AddProxy(method); AddImportMethod(method); } // @formatter:on return $$""" #nullable enable @@ -79,6 +79,11 @@ string BuildBodyArg (ArgumentMeta arg) } } + private void AddProxy (MethodMeta method) + { + proxies.Add($"""Proxies.Set("{method.Space}.{method.Name}", {BuildMethodName(method)});"""); + } + private void AddImportMethod (MethodMeta method) { var args = string.Join(", ", method.Arguments.Select(BuildArg)); @@ -90,9 +95,7 @@ private void AddImportMethod (MethodMeta method) var endpoint = $"{method.JSSpace}.{method.JSName}Serialized"; var attr = $"""[System.Runtime.InteropServices.JavaScript.JSImport("{endpoint}", "Bootsharp")]"""; var date = MarshalAmbiguous(method.ReturnValue.TypeSyntax, true); - var name = BuildMethodName(method); - methods.Add($"{attr} {date}internal static partial {@return} {name} ({args});"); - proxies.Add($"""Proxies.Set("{method.Space}.{method.Name}", {name});"""); + methods.Add($"{attr} {date}internal static partial {@return} {BuildMethodName(method)} ({args});"); string BuildArg (ArgumentMeta arg) { From 938187de4f24548e83b9ea0797db18a3f3adbfb0 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 15 Jan 2024 02:01:20 +0300 Subject: [PATCH 27/75] iteration --- .../Emit/InteropGenerator.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index b1a31fa2..616cc58a 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -40,9 +40,9 @@ private void AddExportMethod (MethodMeta inv) const string attr = "[System.Runtime.InteropServices.JavaScript.JSExport]"; var date = MarshalAmbiguous(inv.ReturnValue.TypeSyntax, true); var wait = inv.ReturnValue.Async && inv.ReturnValue.Serialized; - methods.Add($"{attr} {date}internal static {BuildSignature(inv, wait)} => {BuildBody(inv, wait)};"); + methods.Add($"{attr} {date}internal static {BuildSignature()} => {BuildBody()};"); - string BuildSignature (MethodMeta inv, bool wait) + string BuildSignature () { var args = string.Join(", ", inv.Arguments.Select(BuildSignatureArg)); var @return = inv.ReturnValue.Void ? "void" : (inv.ReturnValue.Serialized @@ -55,7 +55,7 @@ string BuildSignature (MethodMeta inv, bool wait) return signature; } - string BuildBody (MethodMeta inv, bool wait) + string BuildBody () { var args = string.Join(", ", inv.Arguments.Select(BuildBodyArg)); var body = $"global::{inv.Space}.{inv.Name}({args})"; @@ -81,7 +81,29 @@ string BuildBodyArg (ArgumentMeta arg) private void AddProxy (MethodMeta method) { - proxies.Add($"""Proxies.Set("{method.Space}.{method.Name}", {BuildMethodName(method)});"""); + var id = $"{method.Space}.{method.Name}"; + var args = string.Join(", ", method.Arguments.Select(arg => arg.Name)); + var wait = method.ReturnValue.Async && method.ReturnValue.Serialized; + var async = wait ? "async " : ""; + proxies.Add($"""Proxies.Set("{id}", {async}({args}) => {BuildBody()});"""); + + string BuildBody () + { + var args = string.Join(", ", method.Arguments.Select(BuildBodyArg)); + var body = $"{BuildMethodName(method)}({args})"; + if (!method.ReturnValue.Serialized) return body; + if (wait) body = $"await {body}"; + var type = method.ReturnValue.Async + ? method.ReturnValue.TypeSyntax[36..^1] + : method.ReturnValue.TypeSyntax; + return $"Deserialize<{type}>({body})"; + } + + string BuildBodyArg (ArgumentMeta arg) + { + if (!arg.Value.Serialized) return arg.Name; + return $"Serialize({arg.Name})"; + } } private void AddImportMethod (MethodMeta method) From 401c093d70e7fc9c46b7e86cdef9b4bde9021cef Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 15 Jan 2024 20:18:51 +0300 Subject: [PATCH 28/75] iteration --- .../Bootsharp.Publish.Test/Emit/EmitTest.cs | 9 ++--- .../Bootsharp.Publish.Test/Emit/ExportTest.cs | 6 --- .../Emit/ImplementationsTest.cs | 37 +++++++++++++++++++ .../Bootsharp.Publish.Test/Emit/ImportTest.cs | 6 --- .../Emit/InteropTest.cs | 36 +++++++++--------- .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 19 +++------- .../Bootsharp.Publish/Emit/ExportGenerator.cs | 10 ----- .../Emit/ImplementationGenerator.cs | 10 +++++ .../Bootsharp.Publish/Emit/ImportGenerator.cs | 10 ----- src/cs/Bootsharp/Build/Bootsharp.targets | 12 ++---- 10 files changed, 77 insertions(+), 78 deletions(-) delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs index 7858e35b..5eacf71e 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs @@ -3,14 +3,12 @@ public class EmitTest : TaskTest { protected BootsharpEmit Task { get; } - protected string GeneratedExports => ReadProjectFile(exportsPath); - protected string GeneratedImports => ReadProjectFile(importsPath); + protected string GeneratedImplementations => ReadProjectFile(implementationsPath); protected string GeneratedDependencies => ReadProjectFile(dependenciesPath); protected string GeneratedSerializer => ReadProjectFile(serializerPath); protected string GeneratedInterop => ReadProjectFile(interopPath); - private string exportsPath => $"{Project.Root}/Exports.g.cs"; - private string importsPath => $"{Project.Root}/Imports.g.cs"; + private string implementationsPath => $"{Project.Root}/Implementations.g.cs"; private string dependenciesPath => $"{Project.Root}/Dependencies.g.cs"; private string serializerPath => $"{Project.Root}/Serializer.g.cs"; private string interopPath => $"{Project.Root}/Interop.g.cs"; @@ -30,8 +28,7 @@ public override void Execute () private BootsharpEmit CreateTask () => new() { InspectedDirectory = Project.Root, EntryAssemblyName = "System.Runtime.dll", - ExportsFilePath = exportsPath, - ImportsFilePath = importsPath, + ImplementationsFilePath = implementationsPath, DependenciesFilePath = dependenciesPath, SerializerFilePath = serializerPath, InteropFilePath = interopPath, diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs deleted file mode 100644 index 2fe72d31..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/ExportTest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class ExportTest : EmitTest -{ - protected override string TestedContent => GeneratedExports; -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs new file mode 100644 index 00000000..5aa7ba6e --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs @@ -0,0 +1,37 @@ +namespace Bootsharp.Publish.Test; + +public class ImplementationsTest : EmitTest +{ + protected override string TestedContent => GeneratedImplementations; + + [Fact] + public void GeneratesImplementationForExportedInterface () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(IExported))] + + public record Record; + + public interface IExported + { + void Inv (string? a); + Task InvAsync (); + Record? InvRecord (); + Task InvAsyncResult (); + string[] InvArray (int[] a); + } + """)); + Execute(); + Contains( + """ + namespace Exports + { + public class JSExported : global::IExported + { + + } + } + """); + } +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs deleted file mode 100644 index 79c42dfd..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/ImportTest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class ImportTest : EmitTest -{ - protected override string TestedContent => GeneratedImports; -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index e5a1ed9a..e02fdb19 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -79,24 +79,24 @@ [JSInvokable] public static void Inv () {} Contains("""JSImport("SpaceA.SpaceB.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Evt ();"""); } - [Fact] - public void GeneratesForMethodsInGeneratedClasses () - { - AddAssembly(With( - """ - [assembly:JSExport(typeof(Space.IExport))] - [assembly:JSImport(typeof(IImport))] - - namespace Space { public interface IExport { void Inv (); } } - public interface IImport { void Fun (); void NotifyEvt(); } - """)); - Execute(); - // Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.Fun", () => Bootsharp_Generated_Imports_IImport_Fun());"""); - // Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.OnEvt", () => Bootsharp_Generated_Imports_IImport_OnEvt());"""); - // Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); - // Contains("""JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); - // Contains("""JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); - } +// [Fact] +// public void GeneratesForMethodsInGeneratedClasses () +// { +// AddAssembly(With( +// """ +// [assembly:JSExport(typeof(Space.IExport))] +// [assembly:JSImport(typeof(IImport))] +// +// namespace Space { public interface IExport { void Inv (); } } +// public interface IImport { void Fun (); void NotifyEvt(); } +// """)); +// Execute(); +// Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.Fun", () => Bootsharp_Generated_Imports_IImport_Fun());"""); +// Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.OnEvt", () => Bootsharp_Generated_Imports_IImport_OnEvt());"""); +// Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); +// Contains("""JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); +// Contains("""JSImport("Global.Class.onEvtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); +// } [Fact] public void DoesntSerializeTypesThatShouldNotBeSerialized () diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 690f181c..7b428da7 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -9,8 +9,7 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { [Required] public required string InspectedDirectory { get; set; } [Required] public required string EntryAssemblyName { get; set; } - [Required] public required string ExportsFilePath { get; set; } - [Required] public required string ImportsFilePath { get; set; } + [Required] public required string ImplementationsFilePath { get; set; } [Required] public required string DependenciesFilePath { get; set; } [Required] public required string SerializerFilePath { get; set; } [Required] public required string InteropFilePath { get; set; } @@ -19,8 +18,7 @@ public override bool Execute () { var spaceBuilder = CreateNamespaceBuilder(); using var inspection = InspectAssemblies(spaceBuilder); - GenerateExports(inspection); - GenerateImports(inspection); + GenerateImplementations(inspection); GenerateDependencies(inspection); GenerateSerializer(inspection); GenerateInterop(inspection); @@ -42,18 +40,11 @@ private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) return inspection; } - private void GenerateExports (AssemblyInspection inspection) + private void GenerateImplementations (AssemblyInspection inspection) { - var generator = new ExportGenerator(); + var generator = new ImplementationGenerator(); var content = generator.Generate(inspection); - WriteGenerated(ExportsFilePath, content); - } - - private void GenerateImports (AssemblyInspection inspection) - { - var generator = new ImportGenerator(); - var content = generator.Generate(inspection); - WriteGenerated(ImportsFilePath, content); + WriteGenerated(ImplementationsFilePath, content); } private void GenerateDependencies (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs deleted file mode 100644 index ba3627d6..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ExportGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// Generates implementation classes for interfaces specified -/// under Bootsharp's . -/// -internal sealed class ExportGenerator -{ - public string Generate (AssemblyInspection inspection) => ""; -} diff --git a/src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs new file mode 100644 index 00000000..a2d5593d --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +/// +/// Generates implementation classes for interfaces specified under +/// and . +/// +internal sealed class ImplementationGenerator +{ + public string Generate (AssemblyInspection inspection) => ""; +} diff --git a/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs deleted file mode 100644 index e634ae4e..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ImportGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// Generates implementation classes for interfaces specified -/// under Bootsharp's . -/// -internal sealed class ImportGenerator -{ - public string Generate (AssemblyInspection inspection) => ""; -} diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 7488e90b..c560fa96 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -5,8 +5,7 @@ $(BootsharpRoot)/js $(BootsharpRoot)/tasks/Bootsharp.Publish.dll $(IntermediateOutputPath)bootsharp - $(BootsharpIntermediateDirectory)/Exports.g.cs - $(BootsharpIntermediateDirectory)/Imports.g.cs + $(BootsharpIntermediateDirectory)/Implementations.g.cs $(BootsharpIntermediateDirectory)/Dependencies.g.cs $(BootsharpIntermediateDirectory)/Serializer.g.cs $(BootsharpIntermediateDirectory)/Interop.g.cs @@ -63,19 +62,16 @@ - - + - - + From edbfbbfdb54b44a6e2bcd2df4474c8e82f149b33 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:48:42 +0300 Subject: [PATCH 29/75] etc --- .../Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs | 2 +- src/cs/Directory.Build.props | 2 +- src/js/src/modules.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs b/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs index d7f0586f..9c005662 100644 --- a/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs +++ b/src/cs/Bootsharp.Publish/Pack/ModulePatcher/InternalPatcher.cs @@ -13,7 +13,7 @@ public void Patch () { // Remove unnecessary environment-specific calls in .NET's internals, // that are offending bundlers and breaking usage in restricted environments, - // such as VS Code web extensions. (https://github.com/dotnet/runtime/issues/91558) + // such as VS Code web extensions. (https://github.com/elringus/bootsharp/issues/139) File.WriteAllText(dotnet, File.ReadAllText(dotnet, Encoding.UTF8) .Replace("import.meta.url", url) diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 27f9164b..94fb500e 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.0 + 0.2.0-alpha.1 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/src/modules.ts b/src/js/src/modules.ts index dfee8411..457981b6 100644 --- a/src/js/src/modules.ts +++ b/src/js/src/modules.ts @@ -4,19 +4,19 @@ export type * from "./dotnet.g.d.ts"; export type RuntimeConfig = MonoConfig & { assets?: AssetEntry[] }; /** Fetches main dotnet module (dotnet.js). */ -export async function getMain(root?: string): Promise { +export async function getMain(root?: string): Promise { if (root == null) return await import("./dotnet.g"); return await import(/*@vite-ignore*//*webpackIgnore:true*/`${root}/dotnet.js`); } /** Fetches dotnet native module (dotnet.native.js). */ -export async function getNative(root?: string): Promise { +export async function getNative(root?: string): Promise { if (root == null) return await import("./dotnet.native.g"); return await import(/*@vite-ignore*//*webpackIgnore:true*/`${root}/dotnet.native.js`); } /** Fetches dotnet runtime module (dotnet.runtime.js). */ -export async function getRuntime(root?: string): Promise { +export async function getRuntime(root?: string): Promise { if (root == null) return await import("./dotnet.runtime.g"); return await import(/*@vite-ignore*//*webpackIgnore:true*/`${root}/dotnet.runtime.js`); } From a66c1cc65aed47a10ee208d2fc0a5e7eac08579a Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:50:12 +0300 Subject: [PATCH 30/75] etc --- src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs | 2 +- .../Emit/{DependenciesGenerator.cs => DependencyGenerator.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/cs/Bootsharp.Publish/Emit/{DependenciesGenerator.cs => DependencyGenerator.cs} (96%) diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 7b428da7..60376759 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -49,7 +49,7 @@ private void GenerateImplementations (AssemblyInspection inspection) private void GenerateDependencies (AssemblyInspection inspection) { - var generator = new DependenciesGenerator(EntryAssemblyName); + var generator = new DependencyGenerator(EntryAssemblyName); var content = generator.Generate(inspection); WriteGenerated(DependenciesFilePath, content); } diff --git a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs similarity index 96% rename from src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs rename to src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index 96e1a23e..3c9e2dd2 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependenciesGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -7,7 +7,7 @@ namespace Bootsharp.Publish; /// Generates hints for DotNet to not trim specified dynamic dependencies, ie /// members that are not explicitly accessed in the user source code. /// -internal sealed class DependenciesGenerator (string entryAssembly) +internal sealed class DependencyGenerator (string entryAssembly) { private readonly HashSet added = []; From 9bb73ea5e7e849c7735067914188349e5c6fdfd0 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:51:16 +0300 Subject: [PATCH 31/75] etc --- src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs | 2 +- src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs | 2 +- src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index 3c9e2dd2..209144a9 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -4,7 +4,7 @@ namespace Bootsharp.Publish; /// -/// Generates hints for DotNet to not trim specified dynamic dependencies, ie +/// Generates hints for .NET to not trim specified dynamic dependencies, ie /// members that are not explicitly accessed in the user source code. /// internal sealed class DependencyGenerator (string entryAssembly) diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index 616cc58a..91a6fed7 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -1,7 +1,7 @@ namespace Bootsharp.Publish; /// -/// Generates bindings to be picked by DotNet's interop source generator. +/// Generates bindings to be picked by .NET's interop source generator. /// internal sealed class InteropGenerator { diff --git a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs index a984d104..503c2d58 100644 --- a/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/SerializerGenerator.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; /// /// Generates hints for all the types used in interop to be picked by -/// DotNet's JSON serializer source generator. Required for the serializer to +/// .NET's JSON serializer source generator. Required for the serializer to /// work without using reflection (which is required to support trimming). /// internal sealed class SerializerGenerator From 7d294ceaa1580b6bf01aba86867df2dc937efb87 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:53:45 +0300 Subject: [PATCH 32/75] iteration --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 5 ++-- src/cs/Bootsharp.Common/Meta/InterfaceKind.cs | 18 ++++++++++++ src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs | 17 +++++++++++ .../Meta/{MethodType.cs => MethodKind.cs} | 2 +- src/cs/Bootsharp.Common/Meta/MethodMeta.cs | 4 +-- src/cs/Bootsharp.Common/Meta/SolutionMeta.cs | 15 ++++------ .../AssemblyInspector/AssemblyInspection.cs | 3 +- .../AssemblyInspector/AssemblyInspector.cs | 29 +++++++++---------- .../Emit/DependencyGenerator.cs | 13 +++++---- .../Emit/InteropGenerator.cs | 2 +- .../Pack/BindingGenerator/BindingGenerator.cs | 4 +-- .../MethodDeclarationGenerator.cs | 4 +-- 12 files changed, 74 insertions(+), 42 deletions(-) create mode 100644 src/cs/Bootsharp.Common/Meta/InterfaceKind.cs create mode 100644 src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs rename src/cs/Bootsharp.Common/Meta/{MethodType.cs => MethodKind.cs} (96%) diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index 5d51f961..0e3a325c 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -31,9 +31,10 @@ public class TypesTest public void Records () { // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 - _ = new SolutionMeta { Assemblies = [], Methods = [], Crawled = [], Exports = [], Imports = [] } with { Assemblies = default }; + _ = new SolutionMeta { Assemblies = [], Interfaces = [], Methods = [], Crawled = [] } with { Assemblies = default }; _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; - _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Type = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; + _ = new InterfaceMeta { Kind = default, Type = default } with { Kind = InterfaceKind.Import }; + _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Kind = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; _ = new ArgumentMeta { Name = "", JSName = "", Value = default } with { Name = "foo" }; _ = new ValueMeta { Type = default, Nullable = true, TypeSyntax = "", Void = true, Serialized = true, Async = true, JSTypeSyntax = "" } with { TypeSyntax = "foo" }; _ = new MockItem("") with { Id = "foo" }; diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceKind.cs b/src/cs/Bootsharp.Common/Meta/InterfaceKind.cs new file mode 100644 index 00000000..77b5ea92 --- /dev/null +++ b/src/cs/Bootsharp.Common/Meta/InterfaceKind.cs @@ -0,0 +1,18 @@ +namespace Bootsharp; + +/// +/// Type of interop interface. +/// +public enum InterfaceKind +{ + /// + /// The interface was supplied under and + /// is intended for exposing C# APIs to JavaScript. + /// + Export, + /// + /// The interface was supplied under and + /// is intended for exposing JavaScript APIs to C#. + /// + Import +} diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs new file mode 100644 index 00000000..b17ccdfe --- /dev/null +++ b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs @@ -0,0 +1,17 @@ +namespace Bootsharp; + +/// +/// Bootsharp-specific metadata of a C# interface supplied by user +/// under either or . +/// +public sealed record InterfaceMeta +{ + /// + /// Kind of the interface. + /// + public required InterfaceKind Kind { get; init; } + /// + /// Associated C# type of the interface. + /// + public required Type Type { get; init; } +} diff --git a/src/cs/Bootsharp.Common/Meta/MethodType.cs b/src/cs/Bootsharp.Common/Meta/MethodKind.cs similarity index 96% rename from src/cs/Bootsharp.Common/Meta/MethodType.cs rename to src/cs/Bootsharp.Common/Meta/MethodKind.cs index a48dbe1d..ba3d1a8f 100644 --- a/src/cs/Bootsharp.Common/Meta/MethodType.cs +++ b/src/cs/Bootsharp.Common/Meta/MethodKind.cs @@ -3,7 +3,7 @@ /// /// Type of interop method. /// -public enum MethodType +public enum MethodKind { /// /// The method is implemented in C# and invoked from JavaScript; diff --git a/src/cs/Bootsharp.Common/Meta/MethodMeta.cs b/src/cs/Bootsharp.Common/Meta/MethodMeta.cs index 1d69af4a..8d7e3211 100644 --- a/src/cs/Bootsharp.Common/Meta/MethodMeta.cs +++ b/src/cs/Bootsharp.Common/Meta/MethodMeta.cs @@ -8,7 +8,7 @@ public sealed record MethodMeta /// /// Type of interop the method is implementing. /// - public required MethodType Type { get; init; } + public required MethodKind Kind { get; init; } /// /// C# assembly name (DLL file name, w/o the extension), under which the method is declared. /// @@ -42,6 +42,6 @@ public sealed record MethodMeta public override string ToString () { var args = string.Join(", ", Arguments.Select(a => a.ToString())); - return $"[{Type}] {Assembly}.{Space}.{Name} ({args}) => {ReturnValue}"; + return $"[{Kind}] {Assembly}.{Space}.{Name} ({args}) => {ReturnValue}"; } } diff --git a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs index 550008c0..d999d004 100644 --- a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs +++ b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs @@ -10,8 +10,13 @@ public sealed record SolutionMeta /// public required IReadOnlyCollection Assemblies { get; init; } /// + /// Interop interfaces in the solution: supplied by user under either + /// or . + /// + public required IReadOnlyCollection Interfaces { get; init; } + /// /// Interop methods in the solution: either top-level (eg, ) or - /// members of the auto-generated interop classes (eg, ). + /// members of the interop classes generated for . /// public required IReadOnlyCollection Methods { get; init; } /// @@ -19,12 +24,4 @@ public sealed record SolutionMeta /// types associated with the prior types, crawled recursively. /// public required IReadOnlyCollection Crawled { get; init; } - /// - /// Interface types specified in . - /// - public required IReadOnlyCollection Exports { get; init; } - /// - /// Interface types specified in . - /// - public required IReadOnlyCollection Imports { get; init; } } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs index a6d09fdc..1e596e1b 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs @@ -6,10 +6,9 @@ namespace Bootsharp.Publish; internal class AssemblyInspection (MetadataLoadContext ctx) : IDisposable { public ImmutableArray Assemblies { get; init; } = []; + public ImmutableArray Interfaces { get; init; } = []; public ImmutableArray Methods { get; init; } = []; public ImmutableArray Crawled { get; init; } = []; - public ImmutableArray Exports { get; init; } = []; - public ImmutableArray Imports { get; init; } = []; public ImmutableArray Warnings { get; init; } = []; public void Dispose () => ctx.Dispose(); diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 04e347e1..5a683dbe 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -6,9 +6,8 @@ namespace Bootsharp.Publish; internal sealed class AssemblyInspector (JSSpaceBuilder spaceBuilder) { private readonly List assemblies = []; + private readonly List interfaces = []; private readonly List methods = []; - private readonly List exports = []; - private readonly List imports = []; private readonly List warnings = []; private readonly TypeConverter converter = new(spaceBuilder); @@ -38,10 +37,9 @@ private void AddSkippedAssemblyWarning (string assemblyPath, Exception exception private AssemblyInspection CreateInspection (MetadataLoadContext ctx) => new(ctx) { Assemblies = assemblies.ToImmutableArray(), + Interfaces = interfaces.ToImmutableArray(), Methods = methods.ToImmutableArray(), Crawled = converter.CrawledTypes.ToImmutableArray(), - Exports = exports.ToImmutableArray(), - Imports = imports.ToImmutableArray(), Warnings = warnings.ToImmutableArray() }; @@ -63,25 +61,26 @@ private void InspectExportedType (Type type) foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) foreach (var attr in method.CustomAttributes) if (attr.AttributeType.FullName == typeof(JSInvokableAttribute).FullName) - methods.Add(CreateMethod(method, MethodType.Invokable)); + methods.Add(CreateMethod(method, MethodKind.Invokable)); else if (attr.AttributeType.FullName == typeof(JSFunctionAttribute).FullName) - methods.Add(CreateMethod(method, MethodType.Function)); + methods.Add(CreateMethod(method, MethodKind.Function)); else if (attr.AttributeType.FullName == typeof(JSEventAttribute).FullName) - methods.Add(CreateMethod(method, MethodType.Event)); + methods.Add(CreateMethod(method, MethodKind.Event)); } private void InspectAssemblyAttribute (CustomAttributeData attribute) { - if (attribute.AttributeType.FullName == typeof(JSExportAttribute).FullName) - exports.AddRange(((IEnumerable)attribute - .ConstructorArguments[0].Value!).Select(v => (Type)v.Value!)); - else if (attribute.AttributeType.FullName == typeof(JSImportAttribute).FullName) - imports.AddRange(((IEnumerable)attribute - .ConstructorArguments[0].Value!).Select(v => (Type)v.Value!)); + var name = attribute.AttributeType.FullName; + var kind = name == typeof(JSExportAttribute).FullName ? InterfaceKind.Export + : name == typeof(JSImportAttribute).FullName ? InterfaceKind.Import + : (InterfaceKind?)null; + if (!kind.HasValue) return; + var values = (IEnumerable)attribute.ConstructorArguments[0].Value!; + interfaces.AddRange(values.Select(v => new InterfaceMeta { Kind = kind.Value, Type = (Type)v.Value! })); } - private MethodMeta CreateMethod (MethodInfo info, MethodType type) => new() { - Type = type, + private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => new() { + Kind = kind, Assembly = info.DeclaringType!.Assembly.GetName().Name!, Space = info.DeclaringType.FullName!, Name = info.Name, diff --git a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index 209144a9..4c711441 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -14,7 +14,7 @@ internal sealed class DependencyGenerator (string entryAssembly) public string Generate (AssemblyInspection inspection) { AddGeneratedCommon(); - AddGeneratedExportImport(inspection); + AddGeneratedImplementations(inspection); AddClassesWithInteropMethods(inspection); return $$""" @@ -38,12 +38,13 @@ private void AddGeneratedCommon () Add(All, "Bootsharp.Generated.Interop", entryAssembly); } - private void AddGeneratedExportImport (AssemblyInspection inspection) + private void AddGeneratedImplementations (AssemblyInspection inspection) { - foreach (var export in inspection.Exports) - Add(All, $"Bootsharp.Generated.Exports.{export.FullName}", entryAssembly); - foreach (var import in inspection.Imports) - Add(All, $"Bootsharp.Generated.Imports.{import.FullName}", entryAssembly); + foreach (var export in inspection.Interfaces) + { + var space = export.Kind == InterfaceKind.Export ? "Exports" : "Imports"; + Add(All, $"Bootsharp.Generated.{space}.{export.Type.FullName}", entryAssembly); + } } private void AddClassesWithInteropMethods (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index 91a6fed7..ee2c2cf8 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -11,7 +11,7 @@ internal sealed class InteropGenerator public string Generate (AssemblyInspection inspection) { foreach (var method in inspection.Methods) // @formatter:off - if (method.Type == MethodType.Invokable) AddExportMethod(method); + if (method.Kind == MethodKind.Invokable) AddExportMethod(method); else { AddProxy(method); AddImportMethod(method); } // @formatter:on return $$""" diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs index a1d0aa58..8dd499d1 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs @@ -89,8 +89,8 @@ int GetCloseLevel () private void EmitMethod (MethodMeta method) { - if (method.Type == MethodType.Invokable) EmitInvokable(method); - else if (method.Type == MethodType.Function) EmitFunction(method); + if (method.Kind == MethodKind.Invokable) EmitInvokable(method); + else if (method.Kind == MethodKind.Function) EmitFunction(method); else EmitEvent(method); } diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/MethodDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/MethodDeclarationGenerator.cs index b61e25dc..7e3aa554 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/MethodDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/MethodDeclarationGenerator.cs @@ -24,8 +24,8 @@ public string Generate (IEnumerable sourceMethods) private void DeclareMethod () { if (ShouldOpenNamespace()) OpenNamespace(); - if (method.Type == MethodType.Invokable) DeclareInvokable(); - else if (method.Type == MethodType.Function) DeclareFunction(); + if (method.Kind == MethodKind.Invokable) DeclareInvokable(); + else if (method.Kind == MethodKind.Function) DeclareFunction(); else DeclareEvent(); if (ShouldCloseNamespace()) CloseNamespace(); } From 367b0481e2fc7f033474258138322bc6a731202b Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 16 Jan 2024 04:59:26 +0300 Subject: [PATCH 33/75] iteration --- src/cs/Bootsharp.Common.Test/BindingTest.cs | 28 ---------- .../Bootsharp.Common.Test/InterfacesTest.cs | 28 ++++++++++ .../Binding/BindingRegistry.cs | 31 ----------- .../Bootsharp.Common/Binding/ExportBinding.cs | 8 --- .../Bootsharp.Common/Binding/ImportBinding.cs | 7 --- .../Interop/ExportInterface.cs | 9 ++++ .../Interop/ImportInterface.cs | 8 +++ src/cs/Bootsharp.Common/Interop/Interfaces.cs | 44 +++++++++++++++ .../Bootsharp.Publish.Test/Emit/EmitTest.cs | 6 +-- .../Emit/ImplementationsTest.cs | 37 ------------- .../Emit/InterfacesTest.cs | 54 +++++++++++++++++++ .../TypeConverter}/TypeConverter.cs | 0 .../TypeConverter}/TypeCrawler.cs | 0 .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 10 ++-- .../Emit/ImplementationGenerator.cs | 10 ---- .../Emit/InterfaceGenerator.cs | 47 ++++++++++++++++ .../BindingGenerator.cs | 2 + .../Pack/BindingGenerator/Binding.cs | 3 -- .../Pack/ResourceGenerator.cs | 42 +++++++++++++++ .../ResourceGenerator/AssemblyResource.cs | 3 -- .../ResourceGenerator/ResourceGenerator.cs | 25 --------- .../ResourceGenerator/ResourcesTemplate.cs | 21 -------- src/cs/Bootsharp/Build/Bootsharp.targets | 8 +-- 23 files changed, 246 insertions(+), 185 deletions(-) delete mode 100644 src/cs/Bootsharp.Common.Test/BindingTest.cs create mode 100644 src/cs/Bootsharp.Common.Test/InterfacesTest.cs delete mode 100644 src/cs/Bootsharp.Common/Binding/BindingRegistry.cs delete mode 100644 src/cs/Bootsharp.Common/Binding/ExportBinding.cs delete mode 100644 src/cs/Bootsharp.Common/Binding/ImportBinding.cs create mode 100644 src/cs/Bootsharp.Common/Interop/ExportInterface.cs create mode 100644 src/cs/Bootsharp.Common/Interop/ImportInterface.cs create mode 100644 src/cs/Bootsharp.Common/Interop/Interfaces.cs delete mode 100644 src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs create mode 100644 src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs rename src/cs/Bootsharp.Publish/{Pack/DeclarationGenerator => Common/TypeConverter}/TypeConverter.cs (100%) rename src/cs/Bootsharp.Publish/{Pack/DeclarationGenerator => Common/TypeConverter}/TypeCrawler.cs (100%) delete mode 100644 src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs create mode 100644 src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs rename src/cs/Bootsharp.Publish/Pack/{BindingGenerator => }/BindingGenerator.cs (98%) delete mode 100644 src/cs/Bootsharp.Publish/Pack/BindingGenerator/Binding.cs create mode 100644 src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Pack/ResourceGenerator/AssemblyResource.cs delete mode 100644 src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourceGenerator.cs delete mode 100644 src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourcesTemplate.cs diff --git a/src/cs/Bootsharp.Common.Test/BindingTest.cs b/src/cs/Bootsharp.Common.Test/BindingTest.cs deleted file mode 100644 index 6cb3f72d..00000000 --- a/src/cs/Bootsharp.Common.Test/BindingTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Bootsharp.Common.Test; - -public class BindingTest -{ - [Fact] - public void Records () - { - // TODO: Remove once coverlet properly handles record coverage. - _ = new ExportBinding(default, default) with { Api = typeof(int) }; - _ = new ImportBinding(default) with { Implementation = "" }; - } - - [Fact] - public void RegistersExports () - { - var binding = new ExportBinding(typeof(IBackend), default); - BindingRegistry.Register(typeof(Backend), binding); - Assert.Equal(typeof(IBackend), BindingRegistry.Exports[typeof(Backend)].Api); - } - - [Fact] - public void RegistersImports () - { - var binding = new ImportBinding(new Frontend()); - BindingRegistry.Register(typeof(IFrontend), binding); - Assert.IsType(BindingRegistry.Imports[typeof(IFrontend)].Implementation); - } -} diff --git a/src/cs/Bootsharp.Common.Test/InterfacesTest.cs b/src/cs/Bootsharp.Common.Test/InterfacesTest.cs new file mode 100644 index 00000000..58fc049b --- /dev/null +++ b/src/cs/Bootsharp.Common.Test/InterfacesTest.cs @@ -0,0 +1,28 @@ +namespace Bootsharp.Common.Test; + +public class InterfacesTest +{ + [Fact] + public void Records () + { + // TODO: Remove once coverlet properly handles record coverage. + _ = new ExportInterface(default, default) with { Interface = typeof(int) }; + _ = new ImportInterface(default) with { Instance = "" }; + } + + [Fact] + public void RegistersExports () + { + var export = new ExportInterface(typeof(IBackend), default); + Interfaces.Register(typeof(Backend), export); + Assert.Equal(typeof(IBackend), Interfaces.Exports[typeof(Backend)].Interface); + } + + [Fact] + public void RegistersImports () + { + var import = new ImportInterface(new Frontend()); + Interfaces.Register(typeof(IFrontend), import); + Assert.IsType(Interfaces.Imports[typeof(IFrontend)].Instance); + } +} diff --git a/src/cs/Bootsharp.Common/Binding/BindingRegistry.cs b/src/cs/Bootsharp.Common/Binding/BindingRegistry.cs deleted file mode 100644 index 0757bf6d..00000000 --- a/src/cs/Bootsharp.Common/Binding/BindingRegistry.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Bootsharp; - -/// -/// Stores registered JavaScript bindings. -/// -[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] -public static class BindingRegistry -{ - /// - /// Registered export bindings mapped by the binding implementation (auto-generated) type. - /// - public static IReadOnlyDictionary Exports => exports; - /// - /// Registered import bindings mapped by the imported API interface type. - /// - public static IReadOnlyDictionary Imports => imports; - - private static readonly Dictionary exports = new(); - private static readonly Dictionary imports = new(); - - /// - /// Maps implementation type to export binding; used internally by the auto-generated code. - /// - public static void Register (Type impl, ExportBinding binding) => exports[impl] = binding; - /// - /// Maps imported interface type to import binding; used internally by the auto-generated code. - /// - public static void Register (Type api, ImportBinding binding) => imports[api] = binding; -} diff --git a/src/cs/Bootsharp.Common/Binding/ExportBinding.cs b/src/cs/Bootsharp.Common/Binding/ExportBinding.cs deleted file mode 100644 index fe893a34..00000000 --- a/src/cs/Bootsharp.Common/Binding/ExportBinding.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Bootsharp; - -/// -/// Exported JavaScript binding. -/// -/// Type of the exported interface. -/// Binding's implementation factory function. -public record ExportBinding (Type Api, Func Factory); diff --git a/src/cs/Bootsharp.Common/Binding/ImportBinding.cs b/src/cs/Bootsharp.Common/Binding/ImportBinding.cs deleted file mode 100644 index a72cc836..00000000 --- a/src/cs/Bootsharp.Common/Binding/ImportBinding.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Bootsharp; - -/// -/// Imported JavaScript binding. -/// -/// Binding's implementation instance. -public record ImportBinding (object Implementation); diff --git a/src/cs/Bootsharp.Common/Interop/ExportInterface.cs b/src/cs/Bootsharp.Common/Interop/ExportInterface.cs new file mode 100644 index 00000000..f5995fa5 --- /dev/null +++ b/src/cs/Bootsharp.Common/Interop/ExportInterface.cs @@ -0,0 +1,9 @@ +namespace Bootsharp; + +/// +/// Metadata about generated interop class for an interface supplied +/// under . +/// +/// Type of the exported interface. +/// Takes export interface implementation instance; returns interop class instance. +public record ExportInterface (Type Interface, Func Factory); diff --git a/src/cs/Bootsharp.Common/Interop/ImportInterface.cs b/src/cs/Bootsharp.Common/Interop/ImportInterface.cs new file mode 100644 index 00000000..bd7d2c15 --- /dev/null +++ b/src/cs/Bootsharp.Common/Interop/ImportInterface.cs @@ -0,0 +1,8 @@ +namespace Bootsharp; + +/// +/// Metadata about generated implementation for interface supplied +/// under . +/// +/// Import interface implementation instance. +public record ImportInterface (object Instance); diff --git a/src/cs/Bootsharp.Common/Interop/Interfaces.cs b/src/cs/Bootsharp.Common/Interop/Interfaces.cs new file mode 100644 index 00000000..7bfeedd2 --- /dev/null +++ b/src/cs/Bootsharp.Common/Interop/Interfaces.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Bootsharp; + +/// +/// Provides access to generated interop types for interfaces supplied +/// under and . +/// +/// +/// Exported interfaces are C# APIs invoked in JavaScript. Their C# implementation +/// (handler) is assumed to be supplied via +/// on program boot (usually via DI), before associated APIs are accessed in JavaScript. +/// Imported interfaces are JavaScript APIs invoked in C#. Their implementation +/// is instantiated in generated code and is available before program start. +/// +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +public static class Interfaces +{ + /// + /// Interop classes generated for interfaces + /// mapped by the generated class type. Expected to have + /// invoked with the interface implementation (handler) before associated API usage in JS. + /// + public static IReadOnlyDictionary Exports => exports; + /// + /// Implementations generated for interop + /// interfaces mapped by the interface type of the associated implementation. + /// + public static IReadOnlyDictionary Imports => imports; + + private static readonly Dictionary exports = new(); + private static readonly Dictionary imports = new(); + + /// + /// Maps type of the generated export interop class to the associated metadata. + /// Invoked by the generated code before program start. + /// + public static void Register (Type @class, ExportInterface export) => exports[@class] = export; + /// + /// Maps interface type of the generated import implementation to the associated metadata. + /// Invoked by the generated code before program start. + /// + public static void Register (Type @interface, ImportInterface import) => imports[@interface] = import; +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs index 5eacf71e..cee749bb 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/EmitTest.cs @@ -3,12 +3,12 @@ public class EmitTest : TaskTest { protected BootsharpEmit Task { get; } - protected string GeneratedImplementations => ReadProjectFile(implementationsPath); + protected string GeneratedInterfaces => ReadProjectFile(interfacesPath); protected string GeneratedDependencies => ReadProjectFile(dependenciesPath); protected string GeneratedSerializer => ReadProjectFile(serializerPath); protected string GeneratedInterop => ReadProjectFile(interopPath); - private string implementationsPath => $"{Project.Root}/Implementations.g.cs"; + private string interfacesPath => $"{Project.Root}/Interfaces.g.cs"; private string dependenciesPath => $"{Project.Root}/Dependencies.g.cs"; private string serializerPath => $"{Project.Root}/Serializer.g.cs"; private string interopPath => $"{Project.Root}/Interop.g.cs"; @@ -28,7 +28,7 @@ public override void Execute () private BootsharpEmit CreateTask () => new() { InspectedDirectory = Project.Root, EntryAssemblyName = "System.Runtime.dll", - ImplementationsFilePath = implementationsPath, + InterfacesFilePath = interfacesPath, DependenciesFilePath = dependenciesPath, SerializerFilePath = serializerPath, InteropFilePath = interopPath, diff --git a/src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs deleted file mode 100644 index 5aa7ba6e..00000000 --- a/src/cs/Bootsharp.Publish.Test/Emit/ImplementationsTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Bootsharp.Publish.Test; - -public class ImplementationsTest : EmitTest -{ - protected override string TestedContent => GeneratedImplementations; - - [Fact] - public void GeneratesImplementationForExportedInterface () - { - AddAssembly(With( - """ - [assembly:JSExport(typeof(IExported))] - - public record Record; - - public interface IExported - { - void Inv (string? a); - Task InvAsync (); - Record? InvRecord (); - Task InvAsyncResult (); - string[] InvArray (int[] a); - } - """)); - Execute(); - Contains( - """ - namespace Exports - { - public class JSExported : global::IExported - { - - } - } - """); - } -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs new file mode 100644 index 00000000..ceb994f6 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -0,0 +1,54 @@ +namespace Bootsharp.Publish.Test; + +public class InterfacesTest : EmitTest +{ + protected override string TestedContent => GeneratedInterfaces; + + [Fact] + public void GeneratesImplementationForExportedInterface () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(IExported))] + + public record Record; + + public interface IExported + { + void Inv (string? a); + Task InvAsync (); + Record? InvRecord (); + Task InvAsyncResult (); + string[] InvArray (int[] a); + } + """)); + Execute(); + Contains( + """ + namespace Bootsharp.Generated.Exports + { + public class JSExported + { + private static global::IExported handler = null!; + + public JSExported (global::IExported handler) + { + JSExported.handler = handler; + } + + [ModuleInitializer] + internal static void RegisterImplementation () + { + Interfaces.Register(typeof(JSExported), new ExportInterface(typeof(global::IExported), handler => new JSExported(handler))); + } + + [JSInvokable] public static void Inv (global::System.String? a) => handler.Inv(a); + [JSInvokable] public static global::System.Threading.Tasks.Task InvAsync () => handler.InvAsync(); + [JSInvokable] public static global::Record? InvRecord () => handler.InvRecord(); + [JSInvokable] public static global::System.Threading.Tasks.Task Nya () => handler.Nya(); + [JSInvokable] public static global::System.String[] Far (global::System.Int32[] far) => handler.Far(far); + } + } + """); + } +} diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs similarity index 100% rename from src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeConverter.cs rename to src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeCrawler.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeCrawler.cs similarity index 100% rename from src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeCrawler.cs rename to src/cs/Bootsharp.Publish/Common/TypeConverter/TypeCrawler.cs diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 60376759..4fff9b52 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -9,7 +9,7 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { [Required] public required string InspectedDirectory { get; set; } [Required] public required string EntryAssemblyName { get; set; } - [Required] public required string ImplementationsFilePath { get; set; } + [Required] public required string InterfacesFilePath { get; set; } [Required] public required string DependenciesFilePath { get; set; } [Required] public required string SerializerFilePath { get; set; } [Required] public required string InteropFilePath { get; set; } @@ -18,7 +18,7 @@ public override bool Execute () { var spaceBuilder = CreateNamespaceBuilder(); using var inspection = InspectAssemblies(spaceBuilder); - GenerateImplementations(inspection); + GenerateInterfaces(inspection); GenerateDependencies(inspection); GenerateSerializer(inspection); GenerateInterop(inspection); @@ -40,11 +40,11 @@ private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) return inspection; } - private void GenerateImplementations (AssemblyInspection inspection) + private void GenerateInterfaces (AssemblyInspection inspection) { - var generator = new ImplementationGenerator(); + var generator = new InterfaceGenerator(); var content = generator.Generate(inspection); - WriteGenerated(ImplementationsFilePath, content); + WriteGenerated(InterfacesFilePath, content); } private void GenerateDependencies (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs b/src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs deleted file mode 100644 index a2d5593d..00000000 --- a/src/cs/Bootsharp.Publish/Emit/ImplementationGenerator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bootsharp.Publish; - -/// -/// Generates implementation classes for interfaces specified under -/// and . -/// -internal sealed class ImplementationGenerator -{ - public string Generate (AssemblyInspection inspection) => ""; -} diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs new file mode 100644 index 00000000..3b6249d2 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -0,0 +1,47 @@ +namespace Bootsharp.Publish; + +/// +/// Generates interop classes for interfaces specified under +/// and . +/// +internal sealed class InterfaceGenerator +{ + private readonly HashSet exports = []; + private readonly HashSet imports = []; + private readonly HashSet registrations = []; + + public string Generate (AssemblyInspection inspection) + { + return + $$""" + #nullable enable + #pragma warning disable + + namespace Bootsharp.Generated.Exports + { + {{JoinLines(exports)}} + } + + namespace Bootsharp.Generated.Imports + { + {{JoinLines(imports)}} + } + + namespace Bootsharp.Generated + { + internal static class InterfaceRegistrations + { + [ModuleInitializer] + internal static void RegisterInterfaces () + { + {{JoinLines(registrations, 2)}} + } + } + } + """; + } + + private void AddExports () { } + + private void AddImports () { } +} diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs similarity index 98% rename from src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs rename to src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 8dd499d1..37a7a676 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -4,6 +4,8 @@ namespace Bootsharp.Publish; internal sealed class BindingGenerator (JSSpaceBuilder spaceBuilder) { + private record Binding (MethodMeta? Method, Type? Enum, string Namespace); + private readonly StringBuilder builder = new(); private Binding binding => bindings[index]; diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/Binding.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/Binding.cs deleted file mode 100644 index 052b4909..00000000 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/Binding.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bootsharp.Publish; - -internal record Binding (MethodMeta? Method, Type? Enum, string Namespace); diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs new file mode 100644 index 00000000..7f7d0c40 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs @@ -0,0 +1,42 @@ +namespace Bootsharp.Publish; + +internal sealed class ResourceGenerator (string entryAssemblyName, string buildDir, bool embed) +{ + public string Generate (AssemblyInspection inspection) + { + var wasm = BuildBin("dotnet.native.wasm", GenerateWasm()); + var assemblies = inspection.Assemblies.Select(BuildAssembly); + return + $$""" + export default { + wasm: {{wasm}}, + assemblies: [ + {{JoinLines(assemblies, 2, ",\n")}} + ], + entryAssemblyName: "{{entryAssemblyName}}" + }; + """; + } + + private string GenerateWasm () + { + if (!embed) return "undefined"; + var path = Path.Combine(buildDir, "dotnet.native.wasm"); + var bytes = File.ReadAllBytes(path); + return ToBase64(bytes); + } + + private string BuildAssembly (AssemblyMeta assembly) + { + var name = assembly.Name + ".wasm"; + var content = embed ? ToBase64(assembly.Bytes) : "undefined"; + return BuildBin(name, content); + } + + private string BuildBin (string name, string content) + { + return $$"""{ name: "{{name}}", content: {{content}} }"""; + } + + private string ToBase64 (byte[] bytes) => $"\"{Convert.ToBase64String(bytes)}\""; +} diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/AssemblyResource.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/AssemblyResource.cs deleted file mode 100644 index a83a1122..00000000 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/AssemblyResource.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bootsharp.Publish; - -public record AssemblyResource (string Name, string Content); diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourceGenerator.cs deleted file mode 100644 index c4078508..00000000 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourceGenerator.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Bootsharp.Publish; - -internal sealed class ResourceGenerator (string entryName, string buildDir, bool embed) -{ - public string Generate (AssemblyInspection inspection) => new ResourcesTemplate( - GenerateWasm(), - inspection.Assemblies.Select(GenerateAssembly), - entryName - ).Build(); - - private string GenerateWasm () - { - if (!embed) return "undefined"; - var path = Path.Combine(buildDir, "dotnet.native.wasm"); - var bytes = File.ReadAllBytes(path); - return ToBase64(bytes); - } - - private AssemblyResource GenerateAssembly (AssemblyMeta assembly) => new( - assembly.Name + ".wasm", - embed ? ToBase64(assembly.Bytes) : "undefined" - ); - - private string ToBase64 (byte[] bytes) => $"\"{Convert.ToBase64String(bytes)}\""; -} diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourcesTemplate.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourcesTemplate.cs deleted file mode 100644 index eb208ba7..00000000 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator/ResourcesTemplate.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Bootsharp.Publish; - -internal sealed class ResourcesTemplate ( - string wasm, - IEnumerable assemblies, - string entryAssemblyName) -{ - public string Build () => - $$""" - export default { - wasm: {{BuildBin("dotnet.native.wasm", wasm)}}, - assemblies: [ - {{JoinLines(assemblies.Select(a => BuildBin(a.Name, a.Content)), 2, ",\n")}} - ], - entryAssemblyName: "{{entryAssemblyName}}" - }; - """; - - private string BuildBin (string name, string content) => - $$"""{ name: "{{name}}", content: {{content}} }"""; -} diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index c560fa96..da68136f 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -5,7 +5,7 @@ $(BootsharpRoot)/js $(BootsharpRoot)/tasks/Bootsharp.Publish.dll $(IntermediateOutputPath)bootsharp - $(BootsharpIntermediateDirectory)/Implementations.g.cs + $(BootsharpIntermediateDirectory)/Interfaces.g.cs $(BootsharpIntermediateDirectory)/Dependencies.g.cs $(BootsharpIntermediateDirectory)/Serializer.g.cs $(BootsharpIntermediateDirectory)/Interop.g.cs @@ -62,16 +62,16 @@ - + - + From d3cb36cef9b86785229e0d0d86de79bf52179a37 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 16 Jan 2024 05:08:12 +0300 Subject: [PATCH 34/75] iteration --- .../Emit/InterfacesTest.cs | 24 ++++++++++++------- .../Emit/InterfaceGenerator.cs | 4 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs index ceb994f6..b097134c 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -36,17 +36,25 @@ public JSExported (global::IExported handler) JSExported.handler = handler; } - [ModuleInitializer] - internal static void RegisterImplementation () - { - Interfaces.Register(typeof(JSExported), new ExportInterface(typeof(global::IExported), handler => new JSExported(handler))); - } - [JSInvokable] public static void Inv (global::System.String? a) => handler.Inv(a); [JSInvokable] public static global::System.Threading.Tasks.Task InvAsync () => handler.InvAsync(); [JSInvokable] public static global::Record? InvRecord () => handler.InvRecord(); - [JSInvokable] public static global::System.Threading.Tasks.Task Nya () => handler.Nya(); - [JSInvokable] public static global::System.String[] Far (global::System.Int32[] far) => handler.Far(far); + [JSInvokable] public static global::System.Threading.Tasks.Task InvAsyncResult () => handler.InvAsyncResult(); + [JSInvokable] public static global::System.String[] InvArray (global::System.Int32[] a) => handler.InvArray(a); + } + } + """); + Contains( + """ + namespace Bootsharp.Generated + { + internal static class InterfaceRegistrations + { + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterInterfaces () + { + Interfaces.Register(typeof(JSExported), new ExportInterface(typeof(global::IExported), handler => new JSExported(handler))); + } } } """); diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs index 3b6249d2..0f96862c 100644 --- a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -31,10 +31,10 @@ namespace Bootsharp.Generated { internal static class InterfaceRegistrations { - [ModuleInitializer] + [System.Runtime.CompilerServices.ModuleInitializer] internal static void RegisterInterfaces () { - {{JoinLines(registrations, 2)}} + {{JoinLines(registrations, 3)}} } } } From 0208c4f90d9472258805cf811f23af480073be38 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:49:55 +0300 Subject: [PATCH 35/75] iteration --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 2 +- src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs | 16 ++++- .../Emit/DependenciesTest.cs | 8 +-- .../Pack/DeclarationTest.cs | 7 ++ .../AssemblyInspector/AssemblyInspector.cs | 39 +++++++++-- .../Common/JSSpaceBuilder/JSSpaceBuilder.cs | 8 ++- .../Bootsharp.Publish/Common/TextUtilities.cs | 13 +--- .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 2 +- .../Emit/DependencyGenerator.cs | 11 ++- .../Emit/InterfaceGenerator.cs | 70 +++++++++++++++---- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 2 +- 11 files changed, 131 insertions(+), 47 deletions(-) diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index 0e3a325c..0f54256a 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -33,7 +33,7 @@ public void Records () // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 _ = new SolutionMeta { Assemblies = [], Interfaces = [], Methods = [], Crawled = [] } with { Assemblies = default }; _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; - _ = new InterfaceMeta { Kind = default, Type = default } with { Kind = InterfaceKind.Import }; + _ = new InterfaceMeta { Kind = default, TypeSyntax = "", Name = "", Namespace = "" } with { Name = "foo" }; _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Kind = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; _ = new ArgumentMeta { Name = "", JSName = "", Value = default } with { Name = "foo" }; _ = new ValueMeta { Type = default, Nullable = true, TypeSyntax = "", Void = true, Serialized = true, Async = true, JSTypeSyntax = "" } with { TypeSyntax = "foo" }; diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs index b17ccdfe..e73057af 100644 --- a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs +++ b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs @@ -11,7 +11,19 @@ public sealed record InterfaceMeta /// public required InterfaceKind Kind { get; init; } /// - /// Associated C# type of the interface. + /// C# syntax of the interface type, as specified in source code. /// - public required Type Type { get; init; } + public required string TypeSyntax { get; init; } + /// + /// Namespace of the generated interop class implementation. + /// + public required string Namespace { get; init; } + /// + /// Name of the generated interop class implementation. + /// + public required string Name { get; init; } + /// + /// Full type name of the generated interop class implementation. + /// + public string FullName => $"{Namespace}.{Name}"; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs index 4dc73e1e..2e08c6e5 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs @@ -24,8 +24,8 @@ public void AddsGeneratedExportTypes () With("public interface IFoo {}"), With("Space", "public interface IBar {}")); Execute(); - Added(All, "Bootsharp.Generated.Exports.IFoo"); - Added(All, "Bootsharp.Generated.Exports.Space.IBar"); + Added(All, "Bootsharp.Generated.Exports.JSFoo"); + Added(All, "Bootsharp.Generated.Exports.Space.JSBar"); } [Fact] @@ -36,8 +36,8 @@ public void AddsGeneratedImportTypes () With("public interface IFoo {}"), With("Space", "public interface IBar {}")); Execute(); - Added(All, "Bootsharp.Generated.Imports.IFoo"); - Added(All, "Bootsharp.Generated.Imports.Space.IBar"); + Added(All, "Bootsharp.Generated.Imports.JSFoo"); + Added(All, "Bootsharp.Generated.Imports.Space.JSBar"); } [Fact] diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index dda7b3e1..6c9cf6ba 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -97,6 +97,7 @@ export interface Bar { export interface Foo { } } + export namespace Space.Class { export function getFoo(bar: Space.Bar): Space.Foo; } @@ -121,6 +122,7 @@ export namespace SpaceB { export interface Bar { } } + export namespace Global.Class { export function getFoo(bar: SpaceB.Bar): SpaceA.Foo; } @@ -361,6 +363,7 @@ export interface GenericNull { value?: T; } } + export namespace n.Class { export function method(a: n.Generic, b: n.GenericNull): void; } @@ -451,6 +454,7 @@ export enum Enum { B } } + export namespace Space.Class { export function getBaz(): Space.Baz; } @@ -522,6 +526,7 @@ export namespace Global { export interface Foo { } } + export namespace Global.Class { export let fun: (bar: Array | undefined, nya: Array | null> | undefined, far: Array | null> | undefined) => Array | null; } @@ -558,6 +563,7 @@ export interface Foo { bool?: boolean; } } + export namespace n.Class { export function fooBar(bar: n.Bar): n.Foo; } @@ -583,6 +589,7 @@ export enum Foo { B } } + export namespace n.Class { export function getBar(): n.Bar; } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 5a683dbe..206b48ac 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -3,7 +3,7 @@ namespace Bootsharp.Publish; -internal sealed class AssemblyInspector (JSSpaceBuilder spaceBuilder) +internal sealed class AssemblyInspector (JSSpaceBuilder spaceBuilder, string entryAssemblyName) { private readonly List assemblies = []; private readonly List interfaces = []; @@ -75,8 +75,27 @@ private void InspectAssemblyAttribute (CustomAttributeData attribute) : name == typeof(JSImportAttribute).FullName ? InterfaceKind.Import : (InterfaceKind?)null; if (!kind.HasValue) return; - var values = (IEnumerable)attribute.ConstructorArguments[0].Value!; - interfaces.AddRange(values.Select(v => new InterfaceMeta { Kind = kind.Value, Type = (Type)v.Value! })); + foreach (var arg in (IEnumerable)attribute.ConstructorArguments[0].Value!) + InspectInterface((Type)arg.Value!, kind.Value); + } + + private void InspectInterface (Type @interface, InterfaceKind kind) + { + var meta = CreateInterface(@interface, kind); + interfaces.Add(meta); + foreach (var method in @interface.GetMethods()) + InspectInterfaceMethod(method, meta); + } + + private void InspectInterfaceMethod (MethodInfo info, InterfaceMeta meta) + { + var kind = meta.Kind == InterfaceKind.Export ? MethodKind.Invokable + : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event + : MethodKind.Function; + methods.Add(CreateMethod(info, kind) with { + Assembly = entryAssemblyName, + Space = meta.FullName + }); } private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => new() { @@ -95,7 +114,7 @@ private void InspectAssemblyAttribute (CustomAttributeData attribute) Serialized = ShouldSerialize(info.ReturnType) }, JSSpace = spaceBuilder.Build(info.DeclaringType), - JSName = ToFirstLower(info.Name), + JSName = ToFirstLower(info.Name) }; private ArgumentMeta CreateArgument (ParameterInfo info) => new() { @@ -111,4 +130,16 @@ private void InspectAssemblyAttribute (CustomAttributeData attribute) Serialized = ShouldSerialize(info.ParameterType) } }; + + private InterfaceMeta CreateInterface (Type @interface, InterfaceKind kind) + { + var space = kind == InterfaceKind.Export ? "Exports" : "Imports"; + if (@interface.Namespace != null) space += $".{@interface.Namespace}"; + return new InterfaceMeta { + Kind = kind, + TypeSyntax = BuildSyntax(@interface), + Namespace = $"Bootsharp.Generated.{space}", + Name = "JS" + @interface.Name[1..] + }; + } } diff --git a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs index 1c9d3e7a..7b59057c 100644 --- a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs +++ b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs @@ -15,15 +15,17 @@ public void CollectConverters (string outDir, string entryAssembly) converters.Add(new JSSpaceConverter(attribute)); } - public string Build (Type type) + public string Build (string fullname, bool global) { - var space = type.FullName!.Replace("+", "."); - if (type.Namespace is null) space = $"Global.{space}"; + var prefix = global ? "Global." : ""; + var space = $"{prefix}{fullname.Replace("+", ".")}"; foreach (var converter in converters) space = converter.Convert(space); return space; } + public string Build (Type type) => Build(type.FullName!, type.Namespace is null); + private IEnumerable CollectAttributes (Assembly assembly) { return assembly.CustomAttributes.Where(a => diff --git a/src/cs/Bootsharp.Publish/Common/TextUtilities.cs b/src/cs/Bootsharp.Publish/Common/TextUtilities.cs index 6e90906e..08db8622 100644 --- a/src/cs/Bootsharp.Publish/Common/TextUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TextUtilities.cs @@ -1,14 +1,13 @@ global using static Bootsharp.Publish.TextUtilities; -using System.Text.RegularExpressions; namespace Bootsharp.Publish; -internal static partial class TextUtilities +internal static class TextUtilities { public static string JoinLines (IEnumerable values, int indent = 1, string separator = "\n") { if (indent > 0) separator += new string(' ', indent * 4); - return RemoveEmptyLines(string.Join(separator, values.Where(v => v is not null))); + return string.Join(separator, values.Where(v => v is not null)); } public static string JoinLines (params string?[] values) => JoinLines(values, 1); @@ -19,12 +18,4 @@ public static string ToFirstLower (string value) if (value.Length == 1) return value.ToLowerInvariant(); return char.ToLowerInvariant(value[0]) + value[1..]; } - - private static string RemoveEmptyLines (string content) - { - return RegexEmptyLines().Replace(content, string.Empty).Trim(); - } - - [GeneratedRegex(@"^\s*$\n|\r", RegexOptions.Multiline)] - private static partial Regex RegexEmptyLines (); } diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 4fff9b52..d7c0694f 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -34,7 +34,7 @@ private JSSpaceBuilder CreateNamespaceBuilder () private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) { - var inspector = new AssemblyInspector(spaceBuilder); + var inspector = new AssemblyInspector(spaceBuilder, EntryAssemblyName); var inspection = inspector.InspectInDirectory(InspectedDirectory); new InspectionReporter(Log).Report(inspection); return inspection; diff --git a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index 4c711441..f9d6ed8c 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -14,7 +14,7 @@ internal sealed class DependencyGenerator (string entryAssembly) public string Generate (AssemblyInspection inspection) { AddGeneratedCommon(); - AddGeneratedImplementations(inspection); + AddGeneratedInteropClasses(inspection); AddClassesWithInteropMethods(inspection); return $$""" @@ -38,13 +38,10 @@ private void AddGeneratedCommon () Add(All, "Bootsharp.Generated.Interop", entryAssembly); } - private void AddGeneratedImplementations (AssemblyInspection inspection) + private void AddGeneratedInteropClasses (AssemblyInspection inspection) { - foreach (var export in inspection.Interfaces) - { - var space = export.Kind == InterfaceKind.Export ? "Exports" : "Imports"; - Add(All, $"Bootsharp.Generated.{space}.{export.Type.FullName}", entryAssembly); - } + foreach (var inter in inspection.Interfaces) + Add(All, inter.FullName, entryAssembly); } private void AddClassesWithInteropMethods (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs index 0f96862c..5f30f1a0 100644 --- a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -6,26 +6,19 @@ /// internal sealed class InterfaceGenerator { - private readonly HashSet exports = []; - private readonly HashSet imports = []; + private readonly HashSet classes = []; private readonly HashSet registrations = []; public string Generate (AssemblyInspection inspection) { + foreach (var inter in inspection.Interfaces) + AddInterface(inter, inspection.Methods.Where(m => m.Space == inter.FullName)); return $$""" #nullable enable #pragma warning disable - namespace Bootsharp.Generated.Exports - { - {{JoinLines(exports)}} - } - - namespace Bootsharp.Generated.Imports - { - {{JoinLines(imports)}} - } + {{JoinLines(classes, 0)}} namespace Bootsharp.Generated { @@ -41,7 +34,58 @@ internal static void RegisterInterfaces () """; } - private void AddExports () { } + private void AddInterface (InterfaceMeta inter, IEnumerable methods) + { + if (inter.Kind == InterfaceKind.Export) + classes.Add(EmitExportClass(inter, methods)); + else classes.Add(EmitImportClass(inter, methods)); + registrations.Add(EmitRegistration(inter)); + } + + private string EmitExportClass (InterfaceMeta inter, IEnumerable methods) => + $$""" + namespace {{inter.Namespace}} + { + public class {{inter.Name}} + { + private static {{inter.TypeSyntax}} handler = null!; + + public {{inter.Name}} ({{inter.TypeSyntax}} handler) + { + {{inter.Name}}.handler = handler; + } - private void AddImports () { } + {{JoinLines(methods.Select(EmitExportMethod), 2)}} + } + } + """; + + private string EmitImportClass (InterfaceMeta inter, IEnumerable methods) => + $$""" + namespace {{inter.Namespace}} + { + public class {{inter.Name}} + { + {{JoinLines(methods.Select(EmitImportMethod), 2)}} + } + } + """; + + private string EmitRegistration (InterfaceMeta inter) => inter.Kind == InterfaceKind.Import ? + $"Interfaces.Register(typeof({inter.TypeSyntax}), new ImportInterface(new {inter.Name}()));" : + $"Interfaces.Register(typeof({inter.Name}), new ExportInterface(typeof({inter.TypeSyntax}), handler => new {inter.Name}(handler)));"; + + private string EmitExportMethod (MethodMeta method) + { + var sigArgs = string.Join(", ", method.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); + var sig = $"public static {method.ReturnValue.TypeSyntax} {method.Name} ({sigArgs})"; + var args = string.Join(", ", method.Arguments.Select(a => a.Name)); + return $"[JSInvokable] {sig} => handler.{method.Name}({args});"; + } + + private string EmitImportMethod (MethodMeta method) + { + var attr = method.Kind == MethodKind.Function ? "JSFunction" : "JSEvent"; + return $"[{attr}]"; + } } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index e2fdcccf..eb1be59e 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -34,7 +34,7 @@ private JSSpaceBuilder CreateNamespaceBuilder () private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) { - var inspector = new AssemblyInspector(spaceBuilder); + var inspector = new AssemblyInspector(spaceBuilder, EntryAssemblyName); var inspection = inspector.InspectInDirectory(InspectedDirectory); new InspectionReporter(Log).Report(inspection); return inspection; From 32fa4b43163068d0cf7f78d94ed0b6e80b242747 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 16 Jan 2024 21:51:37 +0300 Subject: [PATCH 36/75] iteration --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 14 +---- .../Attributes/JSImportAttribute.cs | 13 +--- .../Emit/InterfacesTest.cs | 59 ++++++++++++++++++- .../Emit/InterfaceGenerator.cs | 39 +++++++++--- 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index 0f54256a..19c2d1a9 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -15,9 +15,7 @@ NamePattern = "ImportNamePattern", NameReplacement = "ImportNameReplacement", InvokePattern = "ImportInvokePattern", - InvokeReplacement = "ImportInvokeReplacement", - EventPattern = "ImportEventPattern", - EventReplacement = "ImportEventReplacement" + InvokeReplacement = "ImportInvokeReplacement" )] namespace Bootsharp.Common.Test; @@ -59,14 +57,6 @@ public void NameAndInvokeParametersAreNullByDefault () Assert.Null(attribute.InvokeReplacement); } - [Fact] - public void EventParametersAreNullByDefault () // (defaults are in generator) - { - var attribute = new JSImportAttribute(typeof(IBackend)); - Assert.Null(attribute.EventPattern); - Assert.Null(attribute.EventReplacement); - } - [Fact] public void ExportParametersEqualArguments () { @@ -87,8 +77,6 @@ public void ImportParametersEqualArguments () Assert.Equal("ImportNameReplacement", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.NameReplacement))); Assert.Equal("ImportInvokePattern", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.InvokePattern))); Assert.Equal("ImportInvokeReplacement", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.InvokeReplacement))); - Assert.Equal("ImportEventPattern", GetNamedValue(import.NamedArguments, nameof(JSImportAttribute.EventPattern))); - Assert.Equal("ImportEventReplacement", GetNamedValue(import.NamedArguments, nameof(JSImportAttribute.EventReplacement))); } private static object GetNamedValue (IList args, string key) => diff --git a/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs index 4921c96a..d057adcb 100644 --- a/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs +++ b/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs @@ -9,8 +9,8 @@ /// For example, given "IFrontend" interface is imported, "JSFrontend" class will be generated, /// which has to be implemented in JavaScript.
/// When an interface method starts with "Notify", an event bindings will ge generated (instead of function); -/// JavaScript name of the event will start with "on" instead of "Notify". This behaviour can be configured -/// with and parameters. +/// JavaScript name of the event will start with "on" instead of "Notify". +/// This behaviour can be configured via preferences. ///
/// /// Generate JavaScript APIs based on "IFrontendAPI" and "IOtherFrontendAPI" interfaces: @@ -24,15 +24,6 @@ [AttributeUsage(AttributeTargets.Assembly)] public sealed class JSImportAttribute : JSTypeAttribute { - /// - /// Regex pattern to match method names indicating an event binding should generated (instead of function). - /// - public string? EventPattern { get; init; } - /// - /// Replacement for the event pattern matches. - /// - public string? EventReplacement { get; init; } - /// public JSImportAttribute (params Type[] types) : base(types) { } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs index b097134c..7abe2bbe 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -5,7 +5,7 @@ public class InterfacesTest : EmitTest protected override string TestedContent => GeneratedInterfaces; [Fact] - public void GeneratesImplementationForExportedInterface () + public void GeneratesInteropClassForExportedInterface () { AddAssembly(With( """ @@ -59,4 +59,61 @@ internal static void RegisterInterfaces () } """); } + + [Fact] + public void GeneratesImplementationForImportedInterface () + { + AddAssembly(With( + """ + [assembly:JSImport(typeof(IImported))] + + public record Record; + + public interface IImported + { + void Inv (string? a); + Task InvAsync (); + Record? InvRecord (); + Task InvAsyncResult (); + string[] InvArray (int[] a); + } + """)); + Execute(); + Contains( + """ + namespace Bootsharp.Generated.Imports + { + public class JSImported : global::IImported + { + [JSFunction] public static void Inv (global::System.String? a) => Proxies.Get>("Bootsharp.Generated.Imports.JSImported.Inv")(a); + [JSFunction] public static global::System.Threading.Tasks.Task InvAsync () => Proxies.Get>("Bootsharp.Generated.Imports.JSImported.InvAsync")(); + [JSFunction] public static global::Record? InvRecord () => Proxies.Get>("Bootsharp.Generated.Imports.JSImported.InvRecord")(); + [JSFunction] public static global::System.Threading.Tasks.Task InvAsyncResult () => Proxies.Get>>("Bootsharp.Generated.Imports.JSImported.InvAsyncResult")(); + [JSFunction] public static global::System.String[] InvArray (global::System.Int32[] a) => Proxies.Get>("Bootsharp.Generated.Imports.JSImported.InvArray")(a); + + void global::IImported.Inv (global::System.String? a) => Inv(a); + global::System.Threading.Tasks.Task global::IImported.InvAsync () => InvAsync(); + global::Record? global::IImported.InvRecord () => InvRecord(); + global::System.Threading.Tasks.Task global::IImported.InvAsyncResult () => InvAsyncResult(); + global::System.String[] global::IImported.InvArray (global::System.Int32[] a) => InvArray(a); + } + } + """); + Contains( + """ + namespace Bootsharp.Generated + { + internal static class InterfaceRegistrations + { + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterInterfaces () + { + Interfaces.Register(typeof(global::IImported), new ImportInterface(new JSImported())); + } + } + } + """); + } + + // TODO: Events } diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs index 5f30f1a0..1a71fcee 100644 --- a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -12,7 +12,7 @@ internal sealed class InterfaceGenerator public string Generate (AssemblyInspection inspection) { foreach (var inter in inspection.Interfaces) - AddInterface(inter, inspection.Methods.Where(m => m.Space == inter.FullName)); + AddInterface(inter, inspection); return $$""" #nullable enable @@ -34,15 +34,15 @@ internal static void RegisterInterfaces () """; } - private void AddInterface (InterfaceMeta inter, IEnumerable methods) + private void AddInterface (InterfaceMeta inter, AssemblyInspection inspection) { - if (inter.Kind == InterfaceKind.Export) - classes.Add(EmitExportClass(inter, methods)); + var methods = inspection.Methods.Where(m => m.Space == inter.FullName).ToArray(); + if (inter.Kind == InterfaceKind.Export) classes.Add(EmitExportClass(inter, methods)); else classes.Add(EmitImportClass(inter, methods)); registrations.Add(EmitRegistration(inter)); } - private string EmitExportClass (InterfaceMeta inter, IEnumerable methods) => + private string EmitExportClass (InterfaceMeta inter, MethodMeta[] methods) => $$""" namespace {{inter.Namespace}} { @@ -60,13 +60,15 @@ public class {{inter.Name}} } """; - private string EmitImportClass (InterfaceMeta inter, IEnumerable methods) => + private string EmitImportClass (InterfaceMeta inter, MethodMeta[] methods) => $$""" namespace {{inter.Namespace}} { - public class {{inter.Name}} + public class {{inter.Name}} : {{inter.TypeSyntax}} { {{JoinLines(methods.Select(EmitImportMethod), 2)}} + + {{JoinLines(methods.Select(m => EmitImportMethodImplementation(inter, m)), 2)}} } } """; @@ -86,6 +88,27 @@ private string EmitExportMethod (MethodMeta method) private string EmitImportMethod (MethodMeta method) { var attr = method.Kind == MethodKind.Function ? "JSFunction" : "JSEvent"; - return $"[{attr}]"; + var sigArgs = string.Join(", ", method.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); + var sig = $"public static {method.ReturnValue.TypeSyntax} {method.Name} ({sigArgs})"; + var getter = EmitProxyGetter(method); + var id = $"{method.Space}.{method.Name}"; + var args = string.Join(", ", method.Arguments.Select(a => a.Name)); + return $"[{attr}] {sig} => {getter}(\"{id}\")({args});"; + } + + private string EmitImportMethodImplementation (InterfaceMeta inter, MethodMeta method) + { + var sigArgs = string.Join(", ", method.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); + var args = string.Join(", ", method.Arguments.Select(a => a.Name)); + return $"{method.ReturnValue.TypeSyntax} {inter.TypeSyntax}.{method.Name} ({sigArgs}) => {method.Name}({args});"; + } + + private string EmitProxyGetter (MethodMeta method) + { + var func = method.ReturnValue.Void ? "Action" : "Func"; + var syntax = method.Arguments.Select(a => a.Value.TypeSyntax).ToList(); + if (!method.ReturnValue.Void) syntax.Add(method.ReturnValue.TypeSyntax); + if (syntax.Count > 0) func = $"{func}<{string.Join(", ", syntax)}>"; + return $"Proxies.Get<{func}>"; } } From 3358f38ee452558db88ade5b258f4160ea2854e2 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 16 Jan 2024 21:54:31 +0300 Subject: [PATCH 37/75] etc --- .../Emit/InterfaceGenerator.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs index 1a71fcee..61bd03c2 100644 --- a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -34,25 +34,25 @@ internal static void RegisterInterfaces () """; } - private void AddInterface (InterfaceMeta inter, AssemblyInspection inspection) + private void AddInterface (InterfaceMeta i, AssemblyInspection inspection) { - var methods = inspection.Methods.Where(m => m.Space == inter.FullName).ToArray(); - if (inter.Kind == InterfaceKind.Export) classes.Add(EmitExportClass(inter, methods)); - else classes.Add(EmitImportClass(inter, methods)); - registrations.Add(EmitRegistration(inter)); + var methods = inspection.Methods.Where(m => m.Space == i.FullName).ToArray(); + if (i.Kind == InterfaceKind.Export) classes.Add(EmitExportClass(i, methods)); + else classes.Add(EmitImportClass(i, methods)); + registrations.Add(EmitRegistration(i)); } - private string EmitExportClass (InterfaceMeta inter, MethodMeta[] methods) => + private string EmitExportClass (InterfaceMeta i, MethodMeta[] methods) => $$""" - namespace {{inter.Namespace}} + namespace {{i.Namespace}} { - public class {{inter.Name}} + public class {{i.Name}} { - private static {{inter.TypeSyntax}} handler = null!; + private static {{i.TypeSyntax}} handler = null!; - public {{inter.Name}} ({{inter.TypeSyntax}} handler) + public {{i.Name}} ({{i.TypeSyntax}} handler) { - {{inter.Name}}.handler = handler; + {{i.Name}}.handler = handler; } {{JoinLines(methods.Select(EmitExportMethod), 2)}} @@ -60,22 +60,22 @@ public class {{inter.Name}} } """; - private string EmitImportClass (InterfaceMeta inter, MethodMeta[] methods) => + private string EmitImportClass (InterfaceMeta i, MethodMeta[] methods) => $$""" - namespace {{inter.Namespace}} + namespace {{i.Namespace}} { - public class {{inter.Name}} : {{inter.TypeSyntax}} + public class {{i.Name}} : {{i.TypeSyntax}} { {{JoinLines(methods.Select(EmitImportMethod), 2)}} - {{JoinLines(methods.Select(m => EmitImportMethodImplementation(inter, m)), 2)}} + {{JoinLines(methods.Select(m => EmitImportMethodImplementation(i, m)), 2)}} } } """; - private string EmitRegistration (InterfaceMeta inter) => inter.Kind == InterfaceKind.Import ? - $"Interfaces.Register(typeof({inter.TypeSyntax}), new ImportInterface(new {inter.Name}()));" : - $"Interfaces.Register(typeof({inter.Name}), new ExportInterface(typeof({inter.TypeSyntax}), handler => new {inter.Name}(handler)));"; + private string EmitRegistration (InterfaceMeta i) => i.Kind == InterfaceKind.Import ? + $"Interfaces.Register(typeof({i.TypeSyntax}), new ImportInterface(new {i.Name}()));" : + $"Interfaces.Register(typeof({i.Name}), new ExportInterface(typeof({i.TypeSyntax}), handler => new {i.Name}(handler)));"; private string EmitExportMethod (MethodMeta method) { @@ -96,11 +96,11 @@ private string EmitImportMethod (MethodMeta method) return $"[{attr}] {sig} => {getter}(\"{id}\")({args});"; } - private string EmitImportMethodImplementation (InterfaceMeta inter, MethodMeta method) + private string EmitImportMethodImplementation (InterfaceMeta i, MethodMeta method) { var sigArgs = string.Join(", ", method.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); var args = string.Join(", ", method.Arguments.Select(a => a.Name)); - return $"{method.ReturnValue.TypeSyntax} {inter.TypeSyntax}.{method.Name} ({sigArgs}) => {method.Name}({args});"; + return $"{method.ReturnValue.TypeSyntax} {i.TypeSyntax}.{method.Name} ({sigArgs}) => {method.Name}({args});"; } private string EmitProxyGetter (MethodMeta method) From dc1961c8d0193a830abd021778d74ae724c031f9 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:14:32 +0300 Subject: [PATCH 38/75] iteration --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 3 +- src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs | 4 + .../Meta/InterfaceMethodMeta.cs | 17 ++++ .../Emit/InterfacesTest.cs | 87 ++++++++++++++++++- .../AssemblyInspector/AssemblyInspector.cs | 53 ++++++----- .../Emit/InterfaceGenerator.cs | 34 ++++---- 6 files changed, 154 insertions(+), 44 deletions(-) create mode 100644 src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index 19c2d1a9..d67d83e4 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -31,7 +31,8 @@ public void Records () // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 _ = new SolutionMeta { Assemblies = [], Interfaces = [], Methods = [], Crawled = [] } with { Assemblies = default }; _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; - _ = new InterfaceMeta { Kind = default, TypeSyntax = "", Name = "", Namespace = "" } with { Name = "foo" }; + _ = new InterfaceMeta { Kind = default, TypeSyntax = "", Name = "", Namespace = "", Methods = [] } with { Name = "foo" }; + _ = new InterfaceMethodMeta { Name = "", Generated = default } with { Name = "foo" }; _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Kind = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; _ = new ArgumentMeta { Name = "", JSName = "", Value = default } with { Name = "foo" }; _ = new ValueMeta { Type = default, Nullable = true, TypeSyntax = "", Void = true, Serialized = true, Async = true, JSTypeSyntax = "" } with { TypeSyntax = "foo" }; diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs index e73057af..6a61a24b 100644 --- a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs +++ b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs @@ -26,4 +26,8 @@ public sealed record InterfaceMeta /// Full type name of the generated interop class implementation. /// public string FullName => $"{Namespace}.{Name}"; + /// + /// Methods declared on the interface and associated interop counterparts. + /// + public required IReadOnlyCollection Methods { get; init; } } diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs b/src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs new file mode 100644 index 00000000..2705da7b --- /dev/null +++ b/src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs @@ -0,0 +1,17 @@ +namespace Bootsharp; + +/// +/// Bootsharp-specific metadata of a method declared on either +/// or interface. +/// +public sealed record InterfaceMethodMeta +{ + /// + /// Name of the method as declared on the interface. + /// + public required string Name { get; set; } + /// + /// Metadata about the interop method generated for the interface method. + /// + public required MethodMeta Generated { get; set; } +} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs index 7abe2bbe..9e289ade 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -53,7 +53,7 @@ internal static class InterfaceRegistrations [System.Runtime.CompilerServices.ModuleInitializer] internal static void RegisterInterfaces () { - Interfaces.Register(typeof(JSExported), new ExportInterface(typeof(global::IExported), handler => new JSExported(handler))); + Interfaces.Register(typeof(Bootsharp.Generated.Exports.JSExported), new ExportInterface(typeof(global::IExported), handler => new Bootsharp.Generated.Exports.JSExported(handler))); } } } @@ -108,12 +108,93 @@ internal static class InterfaceRegistrations [System.Runtime.CompilerServices.ModuleInitializer] internal static void RegisterInterfaces () { - Interfaces.Register(typeof(global::IImported), new ImportInterface(new JSImported())); + Interfaces.Register(typeof(global::IImported), new ImportInterface(new Bootsharp.Generated.Imports.JSImported())); } } } """); } - // TODO: Events + [Fact] + public void RespectsInterfaceNamespace () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(Space.IImported))] + + namespace Space; + + public record Record; + + public interface IExported { void Inv (Record a); } + public interface IImported { void Fun (Record a); } + """)); + Execute(); + Contains( + """ + namespace Bootsharp.Generated.Exports.Space + { + public class JSExported + { + private static global::Space.IExported handler = null!; + + public JSExported (global::Space.IExported handler) + { + JSExported.handler = handler; + } + + [JSInvokable] public static void Inv (global::Space.Record a) => handler.Inv(a); + } + } + namespace Bootsharp.Generated.Imports.Space + { + public class JSImported : global::Space.IImported + { + [JSFunction] public static void Fun (global::Space.Record a) => Proxies.Get>("Bootsharp.Generated.Imports.Space.JSImported.Fun")(a); + + void global::Space.IImported.Fun (global::Space.Record a) => Fun(a); + } + } + """); + Contains( + """ + namespace Bootsharp.Generated + { + internal static class InterfaceRegistrations + { + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterInterfaces () + { + Interfaces.Register(typeof(Bootsharp.Generated.Exports.Space.JSExported), new ExportInterface(typeof(global::Space.IExported), handler => new Bootsharp.Generated.Exports.Space.JSExported(handler))); + Interfaces.Register(typeof(global::Space.IImported), new ImportInterface(new Bootsharp.Generated.Imports.Space.JSImported())); + } + } + } + """); + } + + [Fact] + public void WhenImportedMethodStartsWithNotifyEmitsEvent () + { + AddAssembly(With( + """ + [assembly:JSImport(typeof(IImported))] + + public interface IImported { void NotifyFoo (); } + """)); + Execute(); + Contains( + """ + namespace Bootsharp.Generated.Imports + { + public class JSImported : global::IImported + { + [JSEvent] public static void OnFoo () => Proxies.Get("Bootsharp.Generated.Imports.JSImported.OnFoo")(); + + void global::IImported.NotifyFoo () => OnFoo(); + } + } + """); + } } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 206b48ac..4563076a 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -76,26 +76,15 @@ private void InspectAssemblyAttribute (CustomAttributeData attribute) : (InterfaceKind?)null; if (!kind.HasValue) return; foreach (var arg in (IEnumerable)attribute.ConstructorArguments[0].Value!) - InspectInterface((Type)arg.Value!, kind.Value); + AddInterface((Type)arg.Value!, kind.Value); } - private void InspectInterface (Type @interface, InterfaceKind kind) + private void AddInterface (Type iType, InterfaceKind kind) { - var meta = CreateInterface(@interface, kind); + var meta = CreateInterface(iType, kind); interfaces.Add(meta); - foreach (var method in @interface.GetMethods()) - InspectInterfaceMethod(method, meta); - } - - private void InspectInterfaceMethod (MethodInfo info, InterfaceMeta meta) - { - var kind = meta.Kind == InterfaceKind.Export ? MethodKind.Invokable - : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event - : MethodKind.Function; - methods.Add(CreateMethod(info, kind) with { - Assembly = entryAssemblyName, - Space = meta.FullName - }); + foreach (var method in meta.Methods) + methods.Add(method.Generated); } private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => new() { @@ -131,15 +120,35 @@ private void InspectInterfaceMethod (MethodInfo info, InterfaceMeta meta) } }; - private InterfaceMeta CreateInterface (Type @interface, InterfaceKind kind) + private InterfaceMeta CreateInterface (Type iType, InterfaceKind kind) { - var space = kind == InterfaceKind.Export ? "Exports" : "Imports"; - if (@interface.Namespace != null) space += $".{@interface.Namespace}"; + var space = "Bootsharp.Generated." + (kind == InterfaceKind.Export ? "Exports" : "Imports"); + if (iType.Namespace != null) space += $".{iType.Namespace}"; + var name = "JS" + iType.Name[1..]; + var mSpace = $"{space}.{name}"; return new InterfaceMeta { Kind = kind, - TypeSyntax = BuildSyntax(@interface), - Namespace = $"Bootsharp.Generated.{space}", - Name = "JS" + @interface.Name[1..] + TypeSyntax = BuildSyntax(iType), + Namespace = space, + Name = name, + Methods = iType.GetMethods().Select(m => CreateInterfaceMethod(m, kind, mSpace)).ToArray() + }; + } + + private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKind iKind, string space) + { + var mKind = iKind == InterfaceKind.Export ? MethodKind.Invokable + : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event + : MethodKind.Function; + var name = mKind == MethodKind.Event ? $"On{info.Name[6..]}" : info.Name; + return new() { + Name = info.Name, + Generated = CreateMethod(info, mKind) with { + Assembly = entryAssemblyName, + Space = space, + Name = name, + JSName = ToFirstLower(name) + } }; } } diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs index 61bd03c2..65ed5819 100644 --- a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -36,13 +36,12 @@ internal static void RegisterInterfaces () private void AddInterface (InterfaceMeta i, AssemblyInspection inspection) { - var methods = inspection.Methods.Where(m => m.Space == i.FullName).ToArray(); - if (i.Kind == InterfaceKind.Export) classes.Add(EmitExportClass(i, methods)); - else classes.Add(EmitImportClass(i, methods)); + if (i.Kind == InterfaceKind.Export) classes.Add(EmitExportClass(i)); + else classes.Add(EmitImportClass(i)); registrations.Add(EmitRegistration(i)); } - private string EmitExportClass (InterfaceMeta i, MethodMeta[] methods) => + private string EmitExportClass (InterfaceMeta i) => $$""" namespace {{i.Namespace}} { @@ -55,27 +54,27 @@ public class {{i.Name}} {{i.Name}}.handler = handler; } - {{JoinLines(methods.Select(EmitExportMethod), 2)}} + {{JoinLines(i.Methods.Select(m => EmitExportMethod(m.Generated)), 2)}} } } """; - private string EmitImportClass (InterfaceMeta i, MethodMeta[] methods) => + private string EmitImportClass (InterfaceMeta i) => $$""" namespace {{i.Namespace}} { public class {{i.Name}} : {{i.TypeSyntax}} { - {{JoinLines(methods.Select(EmitImportMethod), 2)}} + {{JoinLines(i.Methods.Select(m => EmitImportMethod(m.Generated)), 2)}} - {{JoinLines(methods.Select(m => EmitImportMethodImplementation(i, m)), 2)}} + {{JoinLines(i.Methods.Select(m => EmitImportMethodImplementation(i, m)), 2)}} } } """; private string EmitRegistration (InterfaceMeta i) => i.Kind == InterfaceKind.Import ? - $"Interfaces.Register(typeof({i.TypeSyntax}), new ImportInterface(new {i.Name}()));" : - $"Interfaces.Register(typeof({i.Name}), new ExportInterface(typeof({i.TypeSyntax}), handler => new {i.Name}(handler)));"; + $"Interfaces.Register(typeof({i.TypeSyntax}), new ImportInterface(new {i.FullName}()));" : + $"Interfaces.Register(typeof({i.FullName}), new ExportInterface(typeof({i.TypeSyntax}), handler => new {i.FullName}(handler)));"; private string EmitExportMethod (MethodMeta method) { @@ -90,17 +89,16 @@ private string EmitImportMethod (MethodMeta method) var attr = method.Kind == MethodKind.Function ? "JSFunction" : "JSEvent"; var sigArgs = string.Join(", ", method.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); var sig = $"public static {method.ReturnValue.TypeSyntax} {method.Name} ({sigArgs})"; - var getter = EmitProxyGetter(method); - var id = $"{method.Space}.{method.Name}"; var args = string.Join(", ", method.Arguments.Select(a => a.Name)); - return $"[{attr}] {sig} => {getter}(\"{id}\")({args});"; + return $"[{attr}] {sig} => {EmitProxyGetter(method)}({args});"; } - private string EmitImportMethodImplementation (InterfaceMeta i, MethodMeta method) + private string EmitImportMethodImplementation (InterfaceMeta i, InterfaceMethodMeta method) { - var sigArgs = string.Join(", ", method.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); - var args = string.Join(", ", method.Arguments.Select(a => a.Name)); - return $"{method.ReturnValue.TypeSyntax} {i.TypeSyntax}.{method.Name} ({sigArgs}) => {method.Name}({args});"; + var gen = method.Generated; + var sigArgs = string.Join(", ", gen.Arguments.Select(a => $"{a.Value.TypeSyntax} {a.Name}")); + var args = string.Join(", ", gen.Arguments.Select(a => a.Name)); + return $"{gen.ReturnValue.TypeSyntax} {i.TypeSyntax}.{method.Name} ({sigArgs}) => {gen.Name}({args});"; } private string EmitProxyGetter (MethodMeta method) @@ -109,6 +107,6 @@ private string EmitProxyGetter (MethodMeta method) var syntax = method.Arguments.Select(a => a.Value.TypeSyntax).ToList(); if (!method.ReturnValue.Void) syntax.Add(method.ReturnValue.TypeSyntax); if (syntax.Count > 0) func = $"{func}<{string.Join(", ", syntax)}>"; - return $"Proxies.Get<{func}>"; + return $"Proxies.Get<{func}>(\"{method.Space}.{method.Name}\")"; } } From e934367a80a3c07b07aa3d3d0f6aed288f284b9d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:22:24 +0300 Subject: [PATCH 39/75] etc --- .../Common/AssemblyInspector/AssemblyInspection.cs | 13 ++++++------- .../Common/AssemblyInspector/AssemblyInspector.cs | 13 ++++++------- .../Common/AssemblyInspector/InspectionReporter.cs | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs index 1e596e1b..40c25d52 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs @@ -1,15 +1,14 @@ -using System.Collections.Immutable; -using System.Reflection; +using System.Reflection; namespace Bootsharp.Publish; internal class AssemblyInspection (MetadataLoadContext ctx) : IDisposable { - public ImmutableArray Assemblies { get; init; } = []; - public ImmutableArray Interfaces { get; init; } = []; - public ImmutableArray Methods { get; init; } = []; - public ImmutableArray Crawled { get; init; } = []; - public ImmutableArray Warnings { get; init; } = []; + public IReadOnlyCollection Assemblies { get; init; } = []; + public IReadOnlyCollection Interfaces { get; init; } = []; + public IReadOnlyCollection Methods { get; init; } = []; + public IReadOnlyCollection Crawled { get; init; } = []; + public IReadOnlyCollection Warnings { get; init; } = []; public void Dispose () => ctx.Dispose(); } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 4563076a..066b6f84 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using System.Reflection; +using System.Reflection; namespace Bootsharp.Publish; @@ -36,11 +35,11 @@ private void AddSkippedAssemblyWarning (string assemblyPath, Exception exception } private AssemblyInspection CreateInspection (MetadataLoadContext ctx) => new(ctx) { - Assemblies = assemblies.ToImmutableArray(), - Interfaces = interfaces.ToImmutableArray(), - Methods = methods.ToImmutableArray(), - Crawled = converter.CrawledTypes.ToImmutableArray(), - Warnings = warnings.ToImmutableArray() + Assemblies = [..assemblies], + Interfaces = [..interfaces], + Methods = [..methods], + Crawled = [..converter.CrawledTypes], + Warnings = [..warnings] }; private AssemblyMeta CreateAssembly (string assemblyPath) => new() { diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs index bb6a421b..7a167eed 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs @@ -8,9 +8,9 @@ internal sealed class InspectionReporter (TaskLoggingHelper logger) public void Report (AssemblyInspection inspection) { logger.LogMessage(MessageImportance.Normal, "Bootsharp assembly inspection result:"); - logger.LogMessage(MessageImportance.Normal, JoinLines($"Discovered {inspection.Assemblies.Length} assemblies:", + logger.LogMessage(MessageImportance.Normal, JoinLines($"Discovered {inspection.Assemblies.Count} assemblies:", JoinLines(inspection.Assemblies.Select(a => a.Name)))); - logger.LogMessage(MessageImportance.Normal, JoinLines($"Discovered {inspection.Methods.Length} JS methods:", + logger.LogMessage(MessageImportance.Normal, JoinLines($"Discovered {inspection.Methods.Count} JS methods:", JoinLines(inspection.Methods.Select(m => m.ToString())))); foreach (var warning in inspection.Warnings) From 632c0985bad6090f14610420ed6993ad331d58c5 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:23:55 +0300 Subject: [PATCH 40/75] etc --- .../Common/AssemblyInspector/AssemblyInspection.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs index 40c25d52..0d3768d0 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs @@ -4,11 +4,11 @@ namespace Bootsharp.Publish; internal class AssemblyInspection (MetadataLoadContext ctx) : IDisposable { - public IReadOnlyCollection Assemblies { get; init; } = []; - public IReadOnlyCollection Interfaces { get; init; } = []; - public IReadOnlyCollection Methods { get; init; } = []; - public IReadOnlyCollection Crawled { get; init; } = []; - public IReadOnlyCollection Warnings { get; init; } = []; + public required IReadOnlyCollection Assemblies { get; init; } + public required IReadOnlyCollection Interfaces { get; init; } + public required IReadOnlyCollection Methods { get; init; } + public required IReadOnlyCollection Crawled { get; init; } + public required IReadOnlyCollection Warnings { get; init; } public void Dispose () => ctx.Dispose(); } From 88b8d6003b53fef9c9b81f52980ce44f53dce4d8 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:14:19 +0300 Subject: [PATCH 41/75] iteration --- .../Emit/InteropTest.cs | 36 +++++++++---------- .../AssemblyInspector/AssemblyInspector.cs | 3 ++ .../Common/JSSpaceBuilder/JSSpaceBuilder.cs | 10 +++--- .../Common/TypeConverter/TypeConverter.cs | 2 +- .../TypeDeclarationGenerator.cs | 8 +++-- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index e02fdb19..ccfc9907 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -79,24 +79,24 @@ [JSInvokable] public static void Inv () {} Contains("""JSImport("SpaceA.SpaceB.Class.evtSerialized", "Bootsharp")] internal static partial void SpaceA_SpaceB_Class_Evt ();"""); } -// [Fact] -// public void GeneratesForMethodsInGeneratedClasses () -// { -// AddAssembly(With( -// """ -// [assembly:JSExport(typeof(Space.IExport))] -// [assembly:JSImport(typeof(IImport))] -// -// namespace Space { public interface IExport { void Inv (); } } -// public interface IImport { void Fun (); void NotifyEvt(); } -// """)); -// Execute(); -// Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.Fun", () => Bootsharp_Generated_Imports_IImport_Fun());"""); -// Contains("""Proxies.Set("Bootsharp.Generated.Imports.IImport.OnEvt", () => Bootsharp_Generated_Imports_IImport_OnEvt());"""); -// Contains("JSExport] internal static void Class_Inv () => global::Class.Inv();"); -// Contains("""JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); -// Contains("""JSImport("Global.Class.onEvtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); -// } + [Fact] + public void GeneratesForMethodsInGeneratedClasses () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(IImported))] + + namespace Space { public interface IExported { void Inv (); } } + public interface IImported { void Fun (); void NotifyEvt(); } + """)); + Execute(); + Contains("""Proxies.Set("Bootsharp.Generated.Imports.JSImported.Fun", () => Bootsharp_Generated_Imports_JSImported_Fun());"""); + Contains("""Proxies.Set("Bootsharp.Generated.Imports.JSImported.OnEvt", () => Bootsharp_Generated_Imports_JSImported_OnEvt());"""); + Contains("JSExport] internal static void Bootsharp_Generated_Exports_Space_JSExported_Inv () => global::Bootsharp.Generated.Exports.Space.JSExported.Inv();"); + Contains("""JSImport("Global.Imported.funSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_JSImported_Fun ();"""); + Contains("""JSImport("Global.Imported.onEvtSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_JSImported_OnEvt ();"""); + } [Fact] public void DoesntSerializeTypesThatShouldNotBeSerialized () diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 066b6f84..08854cbd 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -140,12 +140,15 @@ private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKin : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event : MethodKind.Function; var name = mKind == MethodKind.Event ? $"On{info.Name[6..]}" : info.Name; + var jsSpace = spaceBuilder.Build(info.DeclaringType!); + jsSpace = jsSpace[..(jsSpace.LastIndexOf('.') + 1)] + jsSpace[(jsSpace.LastIndexOf('.') + 2)..]; return new() { Name = info.Name, Generated = CreateMethod(info, mKind) with { Assembly = entryAssemblyName, Space = space, Name = name, + JSSpace = jsSpace, JSName = ToFirstLower(name) } }; diff --git a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs index 7b59057c..4c518314 100644 --- a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs +++ b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs @@ -15,17 +15,17 @@ public void CollectConverters (string outDir, string entryAssembly) converters.Add(new JSSpaceConverter(attribute)); } - public string Build (string fullname, bool global) + public string Build (Type type) { - var prefix = global ? "Global." : ""; - var space = $"{prefix}{fullname.Replace("+", ".")}"; + var space = type.FullName ?? type.Name; + if (type.Namespace is null) space = $"Global.{space}"; + if (type.IsGenericType) space = GetGenericNameWithoutArgs(space); + if (space.Contains('+')) space = space.Replace("+", "."); foreach (var converter in converters) space = converter.Convert(space); return space; } - public string Build (Type type) => Build(type.FullName!, type.Namespace is null); - private IEnumerable CollectAttributes (Assembly assembly) { return assembly.CustomAttributes.Where(a => diff --git a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs index de3980bd..0440d9e4 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs @@ -69,7 +69,7 @@ private string ConvertGeneric (Type type) { EnterNullability(type); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); - return $"{GetGenericNameWithoutArgs(spaceBuilder.Build(type))}<{args}>"; + return $"{spaceBuilder.Build(type)}<{args}>"; } private string ConvertFinal (Type type) diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index 8efffd95..8c71646f 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -130,8 +130,10 @@ private void Append (string content, int level) private string BuildTypeName (Type type) { - if (!type.IsGenericType) return type.Name; - var args = string.Join(", ", type.GetGenericArguments().Select(a => a.Name)); - return $"{GetGenericNameWithoutArgs(type.Name)}<{args}>"; + var space = spaceBuilder.Build(type); + var name = space[(space.LastIndexOf('.') + 1)..]; + if (!type.IsGenericType) return name; + var args = string.Join(", ", type.GetGenericArguments().Select(BuildTypeName)); + return $"{name}<{args}>"; } } From 05bae60bd9761c7c942fdc5b48d5856d26417cf9 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 05:03:15 +0300 Subject: [PATCH 42/75] return of the generator --- src/cs/.scripts/cover.ps1 | 1 + .../Bootsharp.Common.Test.csproj | 2 +- .../Bootsharp.Common/Bootsharp.Common.csproj | 2 + .../Bootsharp.Generate.Test.csproj | 41 ++++++ src/cs/Bootsharp.Generate.Test/EventTest.cs | 41 ++++++ .../Bootsharp.Generate.Test/FunctionTest.cs | 116 ++++++++++++++++ .../Bootsharp.Generate.Test/GeneratorTest.cs | 125 ++++++++++++++++++ src/cs/Bootsharp.Generate.Test/Verifier.cs | 16 +++ .../Bootsharp.Generate.csproj | 15 +++ src/cs/Bootsharp.Generate/PartialClass.cs | 58 ++++++++ src/cs/Bootsharp.Generate/PartialMethod.cs | 76 +++++++++++ src/cs/Bootsharp.Generate/SourceGenerator.cs | 29 ++++ src/cs/Bootsharp.Generate/SyntaxReceiver.cs | 39 ++++++ .../Bootsharp.Inject.Test.csproj | 2 +- .../Bootsharp.Publish.Test.csproj | 2 +- src/cs/Bootsharp.sln | 12 ++ 16 files changed, 574 insertions(+), 3 deletions(-) create mode 100644 src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj create mode 100644 src/cs/Bootsharp.Generate.Test/EventTest.cs create mode 100644 src/cs/Bootsharp.Generate.Test/FunctionTest.cs create mode 100644 src/cs/Bootsharp.Generate.Test/GeneratorTest.cs create mode 100644 src/cs/Bootsharp.Generate.Test/Verifier.cs create mode 100644 src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj create mode 100644 src/cs/Bootsharp.Generate/PartialClass.cs create mode 100644 src/cs/Bootsharp.Generate/PartialMethod.cs create mode 100644 src/cs/Bootsharp.Generate/SourceGenerator.cs create mode 100644 src/cs/Bootsharp.Generate/SyntaxReceiver.cs diff --git a/src/cs/.scripts/cover.ps1 b/src/cs/.scripts/cover.ps1 index 4b07125f..5fe991b2 100644 --- a/src/cs/.scripts/cover.ps1 +++ b/src/cs/.scripts/cover.ps1 @@ -2,6 +2,7 @@ try { $out = "../.cover/" $json = "../.cover/coverage.json" dotnet test Bootsharp.Common.Test/Bootsharp.Common.Test.csproj /p:CollectCoverage=true /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out + dotnet test Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj /p:CollectCoverage=true /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out /p:MergeWith=$json dotnet test Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj /p:CollectCoverage=true /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out /p:MergeWith=$json dotnet test Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj /p:CollectCoverage=true /p:CoverletOutputFormat="json%2copencover" /p:ExcludeByAttribute=GeneratedCodeAttribute /p:CoverletOutput=$out /p:MergeWith=$json reportgenerator "-reports:*/*.xml" "-targetdir:.cover" -reporttypes:HTML diff --git a/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj b/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj index bb33f939..0a66cd84 100644 --- a/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj +++ b/src/cs/Bootsharp.Common.Test/Bootsharp.Common.Test.csproj @@ -12,7 +12,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Bootsharp.Common/Bootsharp.Common.csproj b/src/cs/Bootsharp.Common/Bootsharp.Common.csproj index 76a68d46..b52f6d7a 100644 --- a/src/cs/Bootsharp.Common/Bootsharp.Common.csproj +++ b/src/cs/Bootsharp.Common/Bootsharp.Common.csproj @@ -12,6 +12,8 @@ + diff --git a/src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj b/src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj new file mode 100644 index 00000000..742b45f6 --- /dev/null +++ b/src/cs/Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj @@ -0,0 +1,41 @@ + + + + net8.0 + enable + false + + + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json; + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/cs/Bootsharp.Generate.Test/EventTest.cs b/src/cs/Bootsharp.Generate.Test/EventTest.cs new file mode 100644 index 00000000..149605ea --- /dev/null +++ b/src/cs/Bootsharp.Generate.Test/EventTest.cs @@ -0,0 +1,41 @@ +namespace Bootsharp.Generate.Test; + +public static class EventTest +{ + public static object[][] Data { get; } = [ + // Can generate event binding without namespace and arguments. + [ + """ + partial class Foo + { + [JSEvent] partial void OnBar (); + } + """, + """ + partial class Foo + { + partial void OnBar () => global::Bootsharp.Proxies.Get("Foo.OnBar")(); + } + """ + ], + // Can generate event binding with namespace and arguments. + [ + """ + namespace Space; + + public static partial class Foo + { + [JSEvent] public static partial void OnBar (string a, int b); + } + """, + """ + namespace Space; + + public static partial class Foo + { + public static partial void OnBar (global::System.String a, global::System.Int32 b) => global::Bootsharp.Proxies.Get>("Space.Foo.OnBar")(a, b); + } + """ + ] + ]; +} diff --git a/src/cs/Bootsharp.Generate.Test/FunctionTest.cs b/src/cs/Bootsharp.Generate.Test/FunctionTest.cs new file mode 100644 index 00000000..b82cef08 --- /dev/null +++ b/src/cs/Bootsharp.Generate.Test/FunctionTest.cs @@ -0,0 +1,116 @@ +namespace Bootsharp.Generate.Test; + +public static class FunctionTest +{ + public static object[][] Data { get; } = [ + // Can generate void binding under root namespace. + [ + """ + partial class Foo + { + [JSFunction] partial void Bar (); + } + """, + """ + partial class Foo + { + partial void Bar () => global::Bootsharp.Proxies.Get("Foo.Bar")(); + } + """ + ], + // Can generate void task binding under file-scoped namespace. + [ + """ + using System.Threading.Tasks; + + namespace File.Scoped; + + public static partial class Foo + { + [JSFunction] private static partial Task BarAsync (string a, int b); + } + """, + """ + using System.Threading.Tasks; + + namespace File.Scoped; + + public static partial class Foo + { + private static partial global::System.Threading.Tasks.Task BarAsync (global::System.String a, global::System.Int32 b) => global::Bootsharp.Proxies.Get>("File.Scoped.Foo.BarAsync")(a, b); + } + """ + ], + // Can generate value task binding. + [ + """ + using System.Threading.Tasks; + + namespace File.Scoped; + + public static partial class Foo + { + [JSFunction] private static partial Task BarAsync (); + } + """, + """ + using System.Threading.Tasks; + + namespace File.Scoped; + + public static partial class Foo + { + private static partial global::System.Threading.Tasks.Task BarAsync () => global::Bootsharp.Proxies.Get>>("File.Scoped.Foo.BarAsync")(); + } + """ + ], + // Can generate under classic namespace. + [ + """ + using System; + using System.Threading.Tasks; + + namespace Classic + { + partial class Foo + { + [JSFunction] public partial DateTime GetTime (DateTime time); + [JSFunction] public partial Task GetTimeAsync (DateTime time); + } + } + """, + """ + using System; + using System.Threading.Tasks; + + namespace Classic + { + partial class Foo + { + public partial global::System.DateTime GetTime (global::System.DateTime time) => global::Bootsharp.Proxies.Get>("Classic.Foo.GetTime")(time); + public partial global::System.Threading.Tasks.Task GetTimeAsync (global::System.DateTime time) => global::Bootsharp.Proxies.Get>>("Classic.Foo.GetTimeAsync")(time); + } + } + """ + ], + // Special corner case when UsingDirectiveSyntax.Name is null. + [ + """ + using x = (System.String, System.Boolean); + + partial class Foo + { + [JSFunction] partial void Bar (); + } + """, + """ + using x = (System.String, System.Boolean); + + partial class Foo + { + partial void Bar () => global::Bootsharp.Proxies.Get("Foo.Bar")(); + } + """ + ] + ]; +} diff --git a/src/cs/Bootsharp.Generate.Test/GeneratorTest.cs b/src/cs/Bootsharp.Generate.Test/GeneratorTest.cs new file mode 100644 index 00000000..7ebe1279 --- /dev/null +++ b/src/cs/Bootsharp.Generate.Test/GeneratorTest.cs @@ -0,0 +1,125 @@ +using System.Text; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; + +namespace Bootsharp.Generate.Test; + +public class GeneratorTest +{ + private static readonly List<(string file, string content)> sourceCache = []; + private readonly Verifier verifier = new(); + + [Fact] + public async Task WhenSourceIsEmptyNothingIsGenerated () + { + await Verify(""); + } + + [Fact] + public async Task NothingIsGeneratedWhenNoAttributes () + { + await Verify("partial class Foo { }"); + } + + [Fact] + public async Task WhenAttributeIsFromOtherNamespaceItsIgnored () + { + await Verify( + """ + [assembly:JSImport([])] + public class JSImportAttribute : System.Attribute { public JSImportAttribute (Type[] _) { } } + """); + } + + [Fact] + public async Task DoesntEmitDuplicateRegistrations () + { + verifier.TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck; + await Verify( + """ + partial class FunctionAfterInvokable + { + [JSInvokable] static void Bar () { } + [JSFunction] partial void Baz (); + } + partial class EventAfterInvokable + { + [JSInvokable] static void Bar () { } + [JSEvent] partial void Baz (); + } + partial class EventAfterFunction + { + [JSFunction] partial void Bar (); + [JSEvent] partial void Baz (); + } + """); + } + + [Fact] + public async Task DoesntAnalyzeGeneratedFiles () + { + // otherwise it'll pick files emitted in publish task + verifier.TestState.Sources.Add(("foo.g.cs", + """ + public static partial class Foo + { + [JSInvokable] public static void Bar () { } + [JSFunction] public static void Baz () { } + [JSEvent] public static void Nya () { } + } + """)); + await Verify(""); + } + + [Theory, MemberData(nameof(FunctionTest.Data), MemberType = typeof(FunctionTest))] + public Task PartialFunctionsImplemented (string source, string expected) + => Verify(source, ("FooFunctions.g.cs", expected)); + + [Theory, MemberData(nameof(EventTest.Data), MemberType = typeof(EventTest))] + public Task PartialEventsImplemented (string source, string expected) + => Verify(source, ("FooEvents.g.cs", expected)); + + private async Task Verify (string source, params (string file, string content)[] expected) + { + IncludeBootsharpSources(verifier.TestState.Sources); + verifier.TestCode = source; + for (int i = 0; i < expected.Length; i++) + { + IncludeCommonExpected(ref expected[i].content); + verifier.TestState.GeneratedSources.Add((typeof(SourceGenerator), expected[i].file, + SourceText.From(expected[i].content, Encoding.UTF8))); + } + await verifier.RunAsync(); + } + + private static void IncludeBootsharpSources (SourceFileList sources) + { + if (sourceCache.Count > 0) + { + foreach (var source in sourceCache) + sources.Add(source); + return; + } + var root = Path.GetFullPath($"{Environment.CurrentDirectory}/../../../../Bootsharp.Common"); + foreach (var path in Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories)) + if (!path.Replace("\\", "/").Contains("/obj/")) + sourceCache.Add((Path.GetFileName(path), File.ReadAllText(path))); + sourceCache.Add(("GlobalUsings.cs", + """ + global using System; + global using System.Collections.Generic; + global using System.IO; + global using System.Linq; + global using System.Threading.Tasks; + global using Bootsharp; + """)); + IncludeBootsharpSources(sources); + } + + private void IncludeCommonExpected (ref string expected) => + expected = $""" + #nullable enable + #pragma warning disable + {expected} + """; +} diff --git a/src/cs/Bootsharp.Generate.Test/Verifier.cs b/src/cs/Bootsharp.Generate.Test/Verifier.cs new file mode 100644 index 00000000..0fcf9356 --- /dev/null +++ b/src/cs/Bootsharp.Generate.Test/Verifier.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; + +namespace Bootsharp.Generate.Test; + +public sealed class Verifier : CSharpSourceGeneratorTest + where T : IIncrementalGenerator, new() +{ + protected override string DefaultTestProjectName { get; } = "GeneratorTest"; + + public Verifier () => ReferenceAssemblies = ReferenceAssemblies.Net.Net80; + + protected override bool IsCompilerDiagnosticIncluded (Diagnostic diagnostic, CompilerDiagnostics _) => + diagnostic.Severity == DiagnosticSeverity.Error; +} diff --git a/src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj b/src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj new file mode 100644 index 00000000..9aeff458 --- /dev/null +++ b/src/cs/Bootsharp.Generate/Bootsharp.Generate.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + latest + enable + true + false + + + + + + + diff --git a/src/cs/Bootsharp.Generate/PartialClass.cs b/src/cs/Bootsharp.Generate/PartialClass.cs new file mode 100644 index 00000000..dd28638c --- /dev/null +++ b/src/cs/Bootsharp.Generate/PartialClass.cs @@ -0,0 +1,58 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Bootsharp.Generate; + +internal sealed class PartialClass ( + Compilation compilation, + ClassDeclarationSyntax syntax, + IReadOnlyList methods) +{ + public string Name { get; } = syntax.Identifier.ToString(); + + public string EmitSource () => + """ + #nullable enable + #pragma warning disable + + """ + + EmitUsings() + + WrapNamespace( + EmitHeader() + + EmitMethods() + + EmitFooter() + ); + + private string EmitUsings () + { + var usings = string.Join("\n", syntax.SyntaxTree.GetRoot() + .DescendantNodesAndSelf().OfType()); + return string.IsNullOrEmpty(usings) ? "" : usings + "\n\n"; + } + + private string EmitHeader () => $"{syntax.Modifiers} class {syntax.Identifier}\n{{"; + + private string EmitMethods () + { + if (methods.Count == 0) return ""; + var sources = methods.Select(m => " " + m.EmitSource(compilation)); + return "\n" + string.Join("\n", sources); + } + + private string EmitFooter () => "\n}"; + + private string WrapNamespace (string source) + { + if (syntax.Parent is NamespaceDeclarationSyntax space) + return $$""" + namespace {{space.Name}} + { + {{string.Join("\n", source.Split(["\r\n", "\r", "\n"], StringSplitOptions.None) + .Select((s, i) => i > 0 && s.Length > 0 ? " " + s : s))}} + } + """; + if (syntax.Parent is FileScopedNamespaceDeclarationSyntax fileSpace) + return $"namespace {fileSpace.Name};\n\n{source}"; + return source; + } +} diff --git a/src/cs/Bootsharp.Generate/PartialMethod.cs b/src/cs/Bootsharp.Generate/PartialMethod.cs new file mode 100644 index 00000000..05a56f93 --- /dev/null +++ b/src/cs/Bootsharp.Generate/PartialMethod.cs @@ -0,0 +1,76 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Bootsharp.Generate; + +internal sealed class PartialMethod (MethodDeclarationSyntax syntax) +{ + private IMethodSymbol method; + + public string EmitSource (Compilation compilation) + { + method = compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax)!; + return $"{syntax.Modifiers} {EmitSignature()} => {EmitBody()};".Replace("partial async", "async partial"); + } + + private string EmitSignature () + { + var args = method.Parameters.Select(p => $"{BuildSyntax(p.Type)} {p.Name}"); + return $"{BuildSyntax(method.ReturnType)} {method.Name} ({string.Join(", ", args)})"; + } + + private string EmitBody () + { + return $"""global::Bootsharp.Proxies.Get<{BuildGetterType()}>("{BuildId()}")({BuildArgs()})"""; + } + + private string BuildId () + { + if (method.ContainingNamespace.IsGlobalNamespace) return $"{method.ContainingType.Name}.{method.Name}"; + return string.Join(".", [..method.ContainingNamespace.ConstituentNamespaces, method.ContainingType.Name, method.Name]); + } + + private string BuildGetterType () + { + if (method.ReturnsVoid && method.Parameters.Length == 0) return "global::System.Action"; + var basename = method.ReturnsVoid ? "global::System.Action" : "global::System.Func"; + var args = method.Parameters.Select(p => BuildSyntax(p.Type)); + if (!method.ReturnsVoid) args = args.Append(BuildSyntax(method.ReturnType)); + return $"{basename}<{string.Join(", ", args)}>"; + } + + private string BuildArgs () + { + if (method.Parameters.Length == 0) return ""; + return string.Join(", ", method.Parameters.Select(p => p.Name)); + } + + private static string BuildSyntax (ITypeSymbol type) + { + if (type.SpecialType == SpecialType.System_Void) return "void"; + if (type is IArrayTypeSymbol arrayType) return $"{BuildSyntax(arrayType.ElementType)}[]"; + var nullable = type.NullableAnnotation == NullableAnnotation.Annotated ? "?" : ""; + if (IsGeneric(type, out var args)) return BuildGeneric(type, args) + nullable; + return $"global::{ResolveTypeName(type)}{nullable}"; + + static string BuildGeneric (ITypeSymbol type, ImmutableArray args) + { + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) return BuildSyntax(args[0]); + return $"global::{ResolveTypeName(type)}<{string.Join(", ", args.Select(BuildSyntax))}>"; + } + + static string ResolveTypeName (ITypeSymbol type) + { + if (type.ContainingNamespace.IsGlobalNamespace) return type.Name; + return string.Join(".", type.ContainingNamespace.ConstituentNamespaces) + "." + type.Name; + } + + static bool IsGeneric (ITypeSymbol type, out ImmutableArray args) + { + args = type is INamedTypeSymbol { IsGenericType: true } named ? named.TypeArguments : default; + return args != default; + } + } +} diff --git a/src/cs/Bootsharp.Generate/SourceGenerator.cs b/src/cs/Bootsharp.Generate/SourceGenerator.cs new file mode 100644 index 00000000..d09360df --- /dev/null +++ b/src/cs/Bootsharp.Generate/SourceGenerator.cs @@ -0,0 +1,29 @@ +using Microsoft.CodeAnalysis; + +namespace Bootsharp.Generate; + +[Generator(LanguageNames.CSharp)] +public sealed class SourceGenerator : IIncrementalGenerator +{ + public void Initialize (IncrementalGeneratorInitializationContext context) => context + .RegisterSourceOutput(context.CompilationProvider, Compile); + + private static void Compile (SourceProductionContext context, Compilation compilation) + { + var receiver = VisitNodes(compilation); + foreach (var @class in receiver.FunctionClasses) + context.AddSource($"{@class.Name}Functions.g", @class.EmitSource()); + foreach (var @class in receiver.EventClasses) + context.AddSource($"{@class.Name}Events.g", @class.EmitSource()); + } + + private static SyntaxReceiver VisitNodes (Compilation compilation) + { + var receiver = new SyntaxReceiver(); + foreach (var tree in compilation.SyntaxTrees) + if (!tree.FilePath.EndsWith(".g.cs")) + foreach (var node in tree.GetRoot().DescendantNodesAndSelf()) + receiver.VisitNode(node, compilation); + return receiver; + } +} diff --git a/src/cs/Bootsharp.Generate/SyntaxReceiver.cs b/src/cs/Bootsharp.Generate/SyntaxReceiver.cs new file mode 100644 index 00000000..6ac9491e --- /dev/null +++ b/src/cs/Bootsharp.Generate/SyntaxReceiver.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Bootsharp.Generate; + +internal sealed class SyntaxReceiver +{ + public List FunctionClasses { get; } = []; + public List EventClasses { get; } = []; + + public void VisitNode (SyntaxNode node, Compilation compilation) + { + if (node is ClassDeclarationSyntax classSyntax) + VisitClass(classSyntax, compilation); + } + + private void VisitClass (ClassDeclarationSyntax syntax, Compilation compilation) + { + var functions = GetMethodsWithAttribute(syntax, "JSFunction"); + if (functions.Count > 0) FunctionClasses.Add(new(compilation, syntax, functions)); + var events = GetMethodsWithAttribute(syntax, "JSEvent"); + if (events.Count > 0) EventClasses.Add(new(compilation, syntax, events)); + } + + private List GetMethodsWithAttribute (ClassDeclarationSyntax syntax, string attribute) + { + return syntax.Members + .OfType() + .Where(s => HasAttribute(s, attribute)) + .Select(m => new PartialMethod(m)).ToList(); + } + + private bool HasAttribute (MethodDeclarationSyntax syntax, string attributeName) + { + return syntax.AttributeLists + .SelectMany(l => l.Attributes) + .Any(a => a.ToString().Contains(attributeName)); + } +} diff --git a/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj b/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj index aae3a8b0..e0e26eb3 100644 --- a/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj +++ b/src/cs/Bootsharp.Inject.Test/Bootsharp.Inject.Test.csproj @@ -14,7 +14,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj b/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj index ce4985ec..09eb9248 100644 --- a/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj +++ b/src/cs/Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj @@ -15,7 +15,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Bootsharp.sln b/src/cs/Bootsharp.sln index 70e5e32e..f9ed4695 100644 --- a/src/cs/Bootsharp.sln +++ b/src/cs/Bootsharp.sln @@ -5,6 +5,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Common", "Bootsha EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Common.Test", "Bootsharp.Common.Test/Bootsharp.Common.Test.csproj", "{AEE52DF2-26C4-479B-B62D-14B58903BCE7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Generate", "Bootsharp.Generate/Bootsharp.Generate.csproj", "{F395C565-EAFD-4F89-A7DD-4CA12761A86B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Generate.Test", "Bootsharp.Generate.Test/Bootsharp.Generate.Test.csproj", "{B17E4577-6391-4669-9B12-083CA18ADF33}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Publish", "Bootsharp.Publish/Bootsharp.Publish.csproj", "{4CB73FC6-D64D-470A-8490-7604A292B973}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bootsharp.Publish.Test", "Bootsharp.Publish.Test/Bootsharp.Publish.Test.csproj", "{62571679-675C-4E85-AB3F-5C243920EE6B}" @@ -19,6 +23,10 @@ Global {8D23B7D8-3E5D-420D-80FF-6A437B03D303}.Release|Any CPU.Build.0 = Release|Any CPU {8D23B7D8-3E5D-420D-80FF-6A437B03D303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D23B7D8-3E5D-420D-80FF-6A437B03D303}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Release|Any CPU.Build.0 = Release|Any CPU + {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F395C565-EAFD-4F89-A7DD-4CA12761A86B}.Debug|Any CPU.Build.0 = Debug|Any CPU {4CB73FC6-D64D-470A-8490-7604A292B973}.Release|Any CPU.ActiveCfg = Release|Any CPU {4CB73FC6-D64D-470A-8490-7604A292B973}.Release|Any CPU.Build.0 = Release|Any CPU {4CB73FC6-D64D-470A-8490-7604A292B973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -27,6 +35,10 @@ Global {AEE52DF2-26C4-479B-B62D-14B58903BCE7}.Release|Any CPU.Build.0 = Release|Any CPU {AEE52DF2-26C4-479B-B62D-14B58903BCE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEE52DF2-26C4-479B-B62D-14B58903BCE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B17E4577-6391-4669-9B12-083CA18ADF33}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B17E4577-6391-4669-9B12-083CA18ADF33}.Release|Any CPU.Build.0 = Release|Any CPU + {B17E4577-6391-4669-9B12-083CA18ADF33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B17E4577-6391-4669-9B12-083CA18ADF33}.Debug|Any CPU.Build.0 = Debug|Any CPU {62571679-675C-4E85-AB3F-5C243920EE6B}.Release|Any CPU.ActiveCfg = Release|Any CPU {62571679-675C-4E85-AB3F-5C243920EE6B}.Release|Any CPU.Build.0 = Release|Any CPU {62571679-675C-4E85-AB3F-5C243920EE6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU From 5c20435e3b86ea696a1ee6ab04917413d8385377 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 05:19:19 +0300 Subject: [PATCH 43/75] iteration --- .../Bootsharp.Generate.Test/FunctionTest.cs | 21 +++++++++++++++++-- src/cs/Bootsharp.Generate/PartialClass.cs | 1 - 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cs/Bootsharp.Generate.Test/FunctionTest.cs b/src/cs/Bootsharp.Generate.Test/FunctionTest.cs index b82cef08..638380c7 100644 --- a/src/cs/Bootsharp.Generate.Test/FunctionTest.cs +++ b/src/cs/Bootsharp.Generate.Test/FunctionTest.cs @@ -27,7 +27,7 @@ namespace File.Scoped; public static partial class Foo { - [JSFunction] private static partial Task BarAsync (string a, int b); + [JSFunction] private static partial Task BarAsync (string[] a, int? b); } """, """ @@ -37,7 +37,7 @@ namespace File.Scoped; public static partial class Foo { - private static partial global::System.Threading.Tasks.Task BarAsync (global::System.String a, global::System.Int32 b) => global::Bootsharp.Proxies.Get>("File.Scoped.Foo.BarAsync")(a, b); + private static partial global::System.Threading.Tasks.Task BarAsync (global::System.String[] a, global::System.Int32? b) => global::Bootsharp.Proxies.Get>("File.Scoped.Foo.BarAsync")(a, b); } """ ], @@ -64,6 +64,23 @@ public static partial class Foo } """ ], + // Can generate custom types. + [ + """ + public record Record; + + partial class Foo + { + [JSFunction] partial void Bar (Record a); + } + """, + """ + partial class Foo + { + partial void Bar (global::Record a) => global::Bootsharp.Proxies.Get>("Foo.Bar")(a); + } + """ + ], // Can generate under classic namespace. [ """ diff --git a/src/cs/Bootsharp.Generate/PartialClass.cs b/src/cs/Bootsharp.Generate/PartialClass.cs index dd28638c..6d606a86 100644 --- a/src/cs/Bootsharp.Generate/PartialClass.cs +++ b/src/cs/Bootsharp.Generate/PartialClass.cs @@ -34,7 +34,6 @@ private string EmitUsings () private string EmitMethods () { - if (methods.Count == 0) return ""; var sources = methods.Select(m => " " + m.EmitSource(compilation)); return "\n" + string.Join("\n", sources); } From 2fabfa0a1c806cd43e2c72e70777c7e26d58dde7 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 05:20:11 +0300 Subject: [PATCH 44/75] etc --- src/cs/Bootsharp.Generate/PartialMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Bootsharp.Generate/PartialMethod.cs b/src/cs/Bootsharp.Generate/PartialMethod.cs index 05a56f93..50ff5962 100644 --- a/src/cs/Bootsharp.Generate/PartialMethod.cs +++ b/src/cs/Bootsharp.Generate/PartialMethod.cs @@ -12,7 +12,7 @@ internal sealed class PartialMethod (MethodDeclarationSyntax syntax) public string EmitSource (Compilation compilation) { method = compilation.GetSemanticModel(syntax.SyntaxTree).GetDeclaredSymbol(syntax)!; - return $"{syntax.Modifiers} {EmitSignature()} => {EmitBody()};".Replace("partial async", "async partial"); + return $"{syntax.Modifiers} {EmitSignature()} => {EmitBody()};"; } private string EmitSignature () From 23a5cf73be181f1efda107afd697c452c6d399da Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 05:25:16 +0300 Subject: [PATCH 45/75] etc --- src/cs/Bootsharp.Generate/PartialMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Bootsharp.Generate/PartialMethod.cs b/src/cs/Bootsharp.Generate/PartialMethod.cs index 50ff5962..04f0bd82 100644 --- a/src/cs/Bootsharp.Generate/PartialMethod.cs +++ b/src/cs/Bootsharp.Generate/PartialMethod.cs @@ -7,7 +7,7 @@ namespace Bootsharp.Generate; internal sealed class PartialMethod (MethodDeclarationSyntax syntax) { - private IMethodSymbol method; + private IMethodSymbol method = null!; public string EmitSource (Compilation compilation) { From ee0343b1d6e9fabd11afc92d33f2613fd10bb539 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 20:07:39 +0300 Subject: [PATCH 46/75] iteration --- src/cs/Bootsharp.Common.Test/NamespaceTest.cs | 35 ---------------- src/cs/Bootsharp.Common.Test/TypesTest.cs | 35 +--------------- .../Attributes/JSConfigurationAttribute.cs | 28 +++++++++++++ .../Attributes/JSExportAttribute.cs | 13 ++++-- .../Attributes/JSImportAttribute.cs | 13 ++++-- .../Attributes/JSNamespaceAttribute.cs | 30 ------------- .../Attributes/JSTypeAttribute.cs | 34 --------------- src/cs/Bootsharp.Common/Preferences.cs | 28 +++++++++---- .../Pack/BindingTest.cs | 2 +- .../Pack/DeclarationTest.cs | 2 +- .../AssemblyInspector/AssemblyInspector.cs | 8 ++-- .../Common/JSSpaceBuilder/JSSpaceBuilder.cs | 34 --------------- .../Common/JSSpaceBuilder/JSSpaceConverter.cs | 12 ------ .../Common/PreferencesResolver.cs | 42 +++++++++++++++++++ .../Common/TypeConverter/TypeConverter.cs | 6 +-- .../Bootsharp.Publish/Common/TypeUtilities.cs | 9 ++++ .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 15 ++++--- .../Pack/BindingGenerator.cs | 4 +- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 27 ++++++------ .../DeclarationGenerator.cs | 4 +- .../TypeDeclarationGenerator.cs | 8 ++-- 21 files changed, 158 insertions(+), 231 deletions(-) delete mode 100644 src/cs/Bootsharp.Common.Test/NamespaceTest.cs create mode 100644 src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs delete mode 100644 src/cs/Bootsharp.Common/Attributes/JSNamespaceAttribute.cs delete mode 100644 src/cs/Bootsharp.Common/Attributes/JSTypeAttribute.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs create mode 100644 src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs diff --git a/src/cs/Bootsharp.Common.Test/NamespaceTest.cs b/src/cs/Bootsharp.Common.Test/NamespaceTest.cs deleted file mode 100644 index f44488b8..00000000 --- a/src/cs/Bootsharp.Common.Test/NamespaceTest.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using Bootsharp; - -[assembly: JSNamespace("pattern", "replacement")] - -namespace Bootsharp.Common.Test; - -public class NamespaceTest -{ - private readonly CustomAttributeData attribute = GetAttributeData(); - - [Fact] - public void ParametersEqualArguments () - { - var attribute = new JSNamespaceAttribute("foo", "bar"); - Assert.Equal("foo", attribute.Pattern); - Assert.Equal("bar", attribute.Replacement); - } - - [Fact] - public void PatternParameterIsTheFirstPositionalArgument () - { - Assert.Equal("pattern", attribute.ConstructorArguments[0].Value); - } - - [Fact] - public void ReplacementParameterIsTheSecondPositionalArgument () - { - Assert.Equal("replacement", attribute.ConstructorArguments[1].Value); - } - - private static CustomAttributeData GetAttributeData () => - typeof(NamespaceTest).Assembly.CustomAttributes - .First(a => a.AttributeType == typeof(JSNamespaceAttribute)); -} diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index d67d83e4..f4e1af46 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -2,21 +2,8 @@ using Bootsharp; using Bootsharp.Common.Test; -[assembly: JSExport( - typeof(IBackend), - NamePattern = "ExportNamePattern", - NameReplacement = "ExportNameReplacement", - InvokePattern = "ExportInvokePattern", - InvokeReplacement = "ExportInvokeReplacement" -)] - -[assembly: JSImport( - typeof(IFrontend), - NamePattern = "ImportNamePattern", - NameReplacement = "ImportNameReplacement", - InvokePattern = "ImportInvokePattern", - InvokeReplacement = "ImportInvokeReplacement" -)] +[assembly: JSExport(typeof(IBackend))] +[assembly: JSImport(typeof(IFrontend))] namespace Bootsharp.Common.Test; @@ -48,25 +35,11 @@ public void TypesAreAssigned () Assert.Equal([typeof(IFrontend)], new JSImportAttribute(typeof(IFrontend)).Types); } - [Fact] - public void NameAndInvokeParametersAreNullByDefault () - { - var attribute = new JSExportAttribute(typeof(IBackend)); - Assert.Null(attribute.NamePattern); - Assert.Null(attribute.NameReplacement); - Assert.Null(attribute.InvokePattern); - Assert.Null(attribute.InvokeReplacement); - } - [Fact] public void ExportParametersEqualArguments () { Assert.Equal([typeof(IBackend)], (export.ConstructorArguments[0].Value as IReadOnlyCollection).Select(a => a.Value)); - Assert.Equal("ExportNamePattern", GetNamedValue(export.NamedArguments, nameof(JSTypeAttribute.NamePattern))); - Assert.Equal("ExportNameReplacement", GetNamedValue(export.NamedArguments, nameof(JSTypeAttribute.NameReplacement))); - Assert.Equal("ExportInvokePattern", GetNamedValue(export.NamedArguments, nameof(JSTypeAttribute.InvokePattern))); - Assert.Equal("ExportInvokeReplacement", GetNamedValue(export.NamedArguments, nameof(JSTypeAttribute.InvokeReplacement))); } [Fact] @@ -74,10 +47,6 @@ public void ImportParametersEqualArguments () { Assert.Equal([typeof(IFrontend)], (import.ConstructorArguments[0].Value as IReadOnlyCollection).Select(a => a.Value)); - Assert.Equal("ImportNamePattern", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.NamePattern))); - Assert.Equal("ImportNameReplacement", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.NameReplacement))); - Assert.Equal("ImportInvokePattern", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.InvokePattern))); - Assert.Equal("ImportInvokeReplacement", GetNamedValue(import.NamedArguments, nameof(JSTypeAttribute.InvokeReplacement))); } private static object GetNamedValue (IList args, string key) => diff --git a/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs new file mode 100644 index 00000000..919647b8 --- /dev/null +++ b/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs @@ -0,0 +1,28 @@ +namespace Bootsharp; + +/// +/// When applied to WASM entry point assembly, configures Bootsharp behaviour at build time. +/// +/// +/// Override a method in the inherited class to configure associated +/// operation. Each method has "default" argument containing default result of the operation; +/// return custom result to override it or return it as-is to keep default behaviour. +/// +/// +/// Make generated JS namespaces equal last part of the associated C# namespace: +/// ] +/// +/// public class MyPrefs : Preferences +/// { +/// public override string BuildSpace (Type type, string @default) +/// { +/// var lastDotIdx = @default.LastIndexOf('.'); +/// if (lastDotIdx >= 0) return @default[lastDotIdx..]; +/// return @default; +/// } +/// } +/// ]]> +/// +[AttributeUsage(AttributeTargets.Assembly)] +public sealed class JSConfigurationAttribute : Attribute where T : Preferences, new(); diff --git a/src/cs/Bootsharp.Common/Attributes/JSExportAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSExportAttribute.cs index de99320c..07426262 100644 --- a/src/cs/Bootsharp.Common/Attributes/JSExportAttribute.cs +++ b/src/cs/Bootsharp.Common/Attributes/JSExportAttribute.cs @@ -21,9 +21,16 @@ ///
/// [AttributeUsage(AttributeTargets.Assembly)] -public sealed class JSExportAttribute : JSTypeAttribute +public sealed class JSExportAttribute : Attribute { - /// + /// + /// The interface types to generated export bindings for. + /// + public Type[] Types { get; } + + /// The interface types to generate export bindings for. public JSExportAttribute (params Type[] types) - : base(types) { } + { + Types = types; + } } diff --git a/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs index d057adcb..db6c3bb4 100644 --- a/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs +++ b/src/cs/Bootsharp.Common/Attributes/JSImportAttribute.cs @@ -22,9 +22,16 @@ /// /// [AttributeUsage(AttributeTargets.Assembly)] -public sealed class JSImportAttribute : JSTypeAttribute +public sealed class JSImportAttribute : Attribute { - /// + /// + /// The interface types to generated import bindings for. + /// + public Type[] Types { get; } + + /// The interface types to generate import bindings for. public JSImportAttribute (params Type[] types) - : base(types) { } + { + Types = types; + } } diff --git a/src/cs/Bootsharp.Common/Attributes/JSNamespaceAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSNamespaceAttribute.cs deleted file mode 100644 index 79dc3f87..00000000 --- a/src/cs/Bootsharp.Common/Attributes/JSNamespaceAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Bootsharp; - -/// -/// When applied to WASM entry point assembly, overrides namespace -/// generated for JavaScript bindings and type definitions. -/// -/// -/// Transform "Company.Product.Space" into "Space": -/// [assembly:JSNamespace(@"Company\.Product\.(\S+)", "$1")] -/// -[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] -public sealed class JSNamespaceAttribute : Attribute -{ - /// - /// Regex pattern to match. - /// - public string Pattern { get; } - /// - /// Replacement for the pattern matches. - /// - public string Replacement { get; } - - /// Regex pattern to match. - /// Replacement for the pattern matches. - public JSNamespaceAttribute (string pattern, string replacement) - { - Pattern = pattern; - Replacement = replacement; - } -} diff --git a/src/cs/Bootsharp.Common/Attributes/JSTypeAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSTypeAttribute.cs deleted file mode 100644 index eac6b16d..00000000 --- a/src/cs/Bootsharp.Common/Attributes/JSTypeAttribute.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Bootsharp; - -/// -/// The attribute for automatic JS bindings generation of specified types. -/// -public abstract class JSTypeAttribute : Attribute -{ - /// - /// The types to generated bindings for. - /// - public Type[] Types { get; } - /// - /// Regex pattern to match generated method names. - /// - public string? NamePattern { get; init; } - /// - /// Replacement for the pattern matches of the generated method names. - /// - public string? NameReplacement { get; init; } - /// - /// Regex pattern to match generated method invocations. - /// - public string? InvokePattern { get; init; } - /// - /// Replacement for the pattern matches of the generated method invocations. - /// - public string? InvokeReplacement { get; init; } - - /// The types to generated bindings for. - protected JSTypeAttribute (Type[] types) - { - Types = types; - } -} diff --git a/src/cs/Bootsharp.Common/Preferences.cs b/src/cs/Bootsharp.Common/Preferences.cs index 562cc803..9ec6728f 100644 --- a/src/cs/Bootsharp.Common/Preferences.cs +++ b/src/cs/Bootsharp.Common/Preferences.cs @@ -1,9 +1,21 @@ -// namespace Bootsharp; +namespace Bootsharp; -// /// -// /// User preferences for Bootsharp behaviour. -// /// -// public record Preferences -// { -// -// } +/// +/// User preferences for Bootsharp behaviour. Inherit, override required methods and +/// supply inherited class to . +/// +public class Preferences +{ + /// + /// Builds JavaScript namespace (object chain) for specified C# type. + /// + /// + /// This affect both objects generated to host bindings and type names + /// of the values referenced in the bindings. When building binding host + /// object name, the is declaring type of the + /// associated method. + /// + /// C# type to build namespace for. + /// Result when processed w/o the override. + public virtual string BuildSpace (Type type, string @default) => @default; +} diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index cb3cb7a4..f59aea2f 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -280,7 +280,7 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () } [Fact] - public void NamespaceAttributeOverrideObjectNames () + public void PrefsAllowsOverridingObjectNames () { AddAssembly( With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 6c9cf6ba..abfa6cda 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -152,7 +152,7 @@ public void WhenNoSpaceTypesAreDeclaredUnderGlobalSpace () } [Fact] - public void NamespaceAttributeOverrideSpaceNames () + public void PrefsAllowsOverridingSpaceNames () { AddAssembly( With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 08854cbd..dac0c151 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -2,13 +2,13 @@ namespace Bootsharp.Publish; -internal sealed class AssemblyInspector (JSSpaceBuilder spaceBuilder, string entryAssemblyName) +internal sealed class AssemblyInspector (Preferences prefs, string entryAssemblyName) { private readonly List assemblies = []; private readonly List interfaces = []; private readonly List methods = []; private readonly List warnings = []; - private readonly TypeConverter converter = new(spaceBuilder); + private readonly TypeConverter converter = new(prefs); public AssemblyInspection InspectInDirectory (string directory) { @@ -101,7 +101,7 @@ private void AddInterface (Type iType, InterfaceKind kind) Void = IsVoid(info.ReturnType), Serialized = ShouldSerialize(info.ReturnType) }, - JSSpace = spaceBuilder.Build(info.DeclaringType), + JSSpace = prefs.BuildSpace(info.DeclaringType, BuildJSSpace(info.DeclaringType)), JSName = ToFirstLower(info.Name) }; @@ -140,7 +140,7 @@ private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKin : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event : MethodKind.Function; var name = mKind == MethodKind.Event ? $"On{info.Name[6..]}" : info.Name; - var jsSpace = spaceBuilder.Build(info.DeclaringType!); + var jsSpace = prefs.BuildSpace(info.DeclaringType!, BuildJSSpace(info.DeclaringType!)); jsSpace = jsSpace[..(jsSpace.LastIndexOf('.') + 1)] + jsSpace[(jsSpace.LastIndexOf('.') + 2)..]; return new() { Name = info.Name, diff --git a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs deleted file mode 100644 index 4c518314..00000000 --- a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceBuilder.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; - -namespace Bootsharp.Publish; - -internal sealed class JSSpaceBuilder -{ - private readonly List converters = []; - - public void CollectConverters (string outDir, string entryAssembly) - { - using var context = CreateLoadContext(outDir); - var assemblyPath = Path.Combine(outDir, entryAssembly); - var assembly = context.LoadFromAssemblyPath(assemblyPath); - foreach (var attribute in CollectAttributes(assembly)) - converters.Add(new JSSpaceConverter(attribute)); - } - - public string Build (Type type) - { - var space = type.FullName ?? type.Name; - if (type.Namespace is null) space = $"Global.{space}"; - if (type.IsGenericType) space = GetGenericNameWithoutArgs(space); - if (space.Contains('+')) space = space.Replace("+", "."); - foreach (var converter in converters) - space = converter.Convert(space); - return space; - } - - private IEnumerable CollectAttributes (Assembly assembly) - { - return assembly.CustomAttributes.Where(a => - a.AttributeType.FullName == typeof(JSNamespaceAttribute).FullName); - } -} diff --git a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs b/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs deleted file mode 100644 index e6a58ee1..00000000 --- a/src/cs/Bootsharp.Publish/Common/JSSpaceBuilder/JSSpaceConverter.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reflection; -using System.Text.RegularExpressions; - -namespace Bootsharp.Publish; - -internal sealed class JSSpaceConverter (CustomAttributeData attribute) -{ - private readonly string pattern = (string)attribute.ConstructorArguments[0].Value!; - private readonly string replacement = (string)attribute.ConstructorArguments[1].Value!; - - public string Convert (string space) => Regex.Replace(space, pattern, replacement); -} diff --git a/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs new file mode 100644 index 00000000..ecb4d48f --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs @@ -0,0 +1,42 @@ +using System.Reflection; + +namespace Bootsharp.Publish; + +internal sealed class PreferencesResolver (string entryAssemblyName) +{ + private static readonly string cfgAttrFullName = typeof(JSConfigurationAttribute<>).FullName!; + + public Preferences Resolve (string outDir) + { + using var ctx = CreateLoadContext(outDir); + var assembly = LoadEntryAssembly(ctx, outDir); + if (FindConfigurationAttribute(assembly) is not { } attr) return new(); + return InstantiateCustomPrefs(attr.AttributeType); + } + + private Assembly LoadEntryAssembly (MetadataLoadContext ctx, string outDir) + { + var path = Path.Combine(outDir, entryAssemblyName); + return ctx.LoadFromAssemblyPath(path); + } + + private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) + { + foreach (var attr in assembly.CustomAttributes) + if (IsConfigurationAttribute(attr.AttributeType)) + return attr; + return null; + } + + private bool IsConfigurationAttribute (Type attributeType) + { + if (attributeType.FullName is null) return false; + return attributeType.FullName.StartsWith(cfgAttrFullName, StringComparison.Ordinal); + } + + private Preferences InstantiateCustomPrefs (Type attributeType) + { + var prefsType = attributeType.GenericTypeArguments[0]; + return (Preferences)Activator.CreateInstance(prefsType)!; + } +} diff --git a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs index 0440d9e4..f17c664a 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; -internal sealed class TypeConverter (JSSpaceBuilder spaceBuilder) +internal sealed class TypeConverter (Preferences prefs) { public IReadOnlyCollection CrawledTypes => crawler.Crawled; @@ -69,13 +69,13 @@ private string ConvertGeneric (Type type) { EnterNullability(type); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); - return $"{spaceBuilder.Build(type)}<{args}>"; + return $"{prefs.BuildSpace(type, BuildJSSpace(type))}<{args}>"; } private string ConvertFinal (Type type) { if (type.Name == "Void") return "void"; - if (CrawledTypes.Contains(type)) return spaceBuilder.Build(type); + if (CrawledTypes.Contains(type)) return prefs.BuildSpace(type, BuildJSSpace(type)); return Type.GetTypeCode(type) switch { TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or diff --git a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs index 00374a58..ed563f95 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs @@ -166,6 +166,15 @@ public static bool ShouldSerialize (Type type) return !native.Contains(type.FullName!); } + public static string BuildJSSpace (Type type) + { + var space = type.FullName ?? type.Name; + if (type.Namespace is null) space = $"Global.{space}"; + if (type.IsGenericType) space = GetGenericNameWithoutArgs(space); + if (space.Contains('+')) space = space.Replace("+", "."); + return space; + } + public static string BuildSyntax (Type type) => BuildSyntax(type, null, false); public static string BuildSyntax (Type type, ParameterInfo info) => BuildSyntax(type, GetNullability(info)); diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index d7c0694f..81950a34 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -16,8 +16,8 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task public override bool Execute () { - var spaceBuilder = CreateNamespaceBuilder(); - using var inspection = InspectAssemblies(spaceBuilder); + var prefs = ResolvePreferences(); + using var inspection = InspectAssemblies(prefs); GenerateInterfaces(inspection); GenerateDependencies(inspection); GenerateSerializer(inspection); @@ -25,16 +25,15 @@ public override bool Execute () return true; } - private JSSpaceBuilder CreateNamespaceBuilder () + private Preferences ResolvePreferences () { - var builder = new JSSpaceBuilder(); - builder.CollectConverters(InspectedDirectory, EntryAssemblyName); - return builder; + var resolver = new PreferencesResolver(EntryAssemblyName); + return resolver.Resolve(InspectedDirectory); } - private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) + private AssemblyInspection InspectAssemblies (Preferences prefs) { - var inspector = new AssemblyInspector(spaceBuilder, EntryAssemblyName); + var inspector = new AssemblyInspector(prefs, EntryAssemblyName); var inspection = inspector.InspectInDirectory(InspectedDirectory); new InspectionReporter(Log).Report(inspection); return inspection; diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 37a7a676..73a8f656 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; -internal sealed class BindingGenerator (JSSpaceBuilder spaceBuilder) +internal sealed class BindingGenerator (Preferences prefs) { private record Binding (MethodMeta? Method, Type? Enum, string Namespace); @@ -20,7 +20,7 @@ public string Generate (AssemblyInspection inspection) bindings = inspection.Methods .Select(m => new Binding(m, null, m.JSSpace)) .Concat(inspection.Crawled.Where(t => t.IsEnum) - .Select(t => new Binding(null, t, spaceBuilder.Build(t)))) + .Select(t => new Binding(null, t, prefs.BuildSpace(t, BuildJSSpace(t))))) .OrderBy(m => m.Namespace).ToArray(); if (bindings.Length == 0) return ""; EmitImports(); diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index eb1be59e..8d482480 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -16,40 +16,39 @@ public sealed class BootsharpPack : Microsoft.Build.Utilities.Task public override bool Execute () { - var spaceBuilder = CreateNamespaceBuilder(); - using var inspection = InspectAssemblies(spaceBuilder); - GenerateBindings(inspection, spaceBuilder); - GenerateDeclarations(inspection, spaceBuilder); + var prefs = ResolvePreferences(); + using var inspection = InspectAssemblies(prefs); + GenerateBindings(prefs, inspection); + GenerateDeclarations(prefs, inspection); GenerateResources(inspection); PatchModules(); return true; } - private JSSpaceBuilder CreateNamespaceBuilder () + private Preferences ResolvePreferences () { - var builder = new JSSpaceBuilder(); - builder.CollectConverters(InspectedDirectory, EntryAssemblyName); - return builder; + var resolver = new PreferencesResolver(EntryAssemblyName); + return resolver.Resolve(InspectedDirectory); } - private AssemblyInspection InspectAssemblies (JSSpaceBuilder spaceBuilder) + private AssemblyInspection InspectAssemblies (Preferences prefs) { - var inspector = new AssemblyInspector(spaceBuilder, EntryAssemblyName); + var inspector = new AssemblyInspector(prefs, EntryAssemblyName); var inspection = inspector.InspectInDirectory(InspectedDirectory); new InspectionReporter(Log).Report(inspection); return inspection; } - private void GenerateBindings (AssemblyInspection inspection, JSSpaceBuilder spaceBuilder) + private void GenerateBindings (Preferences prefs, AssemblyInspection inspection) { - var generator = new BindingGenerator(spaceBuilder); + var generator = new BindingGenerator(prefs); var content = generator.Generate(inspection); File.WriteAllText(Path.Combine(BuildDirectory, "bindings.g.js"), content); } - private void GenerateDeclarations (AssemblyInspection inspection, JSSpaceBuilder spaceBuilder) + private void GenerateDeclarations (Preferences prefs, AssemblyInspection inspection) { - var generator = new DeclarationGenerator(spaceBuilder); + var generator = new DeclarationGenerator(prefs); var content = generator.Generate(inspection); File.WriteAllText(Path.Combine(BuildDirectory, "bindings.g.d.ts"), content); } diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs index c82360fd..0e9b507b 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs @@ -1,9 +1,9 @@ namespace Bootsharp.Publish; -internal sealed class DeclarationGenerator (JSSpaceBuilder spaceBuilder) +internal sealed class DeclarationGenerator (Preferences prefs) { private readonly MethodDeclarationGenerator methodsGenerator = new(); - private readonly TypeDeclarationGenerator typesGenerator = new(spaceBuilder); + private readonly TypeDeclarationGenerator typesGenerator = new(prefs); public string Generate (AssemblyInspection inspection) => JoinLines(0, """import type { Event } from "./event";""", diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index 8c71646f..a0c5fdf1 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -3,10 +3,10 @@ namespace Bootsharp.Publish; -internal sealed class TypeDeclarationGenerator (JSSpaceBuilder spaceBuilder) +internal sealed class TypeDeclarationGenerator (Preferences prefs) { private readonly StringBuilder builder = new(); - private readonly TypeConverter converter = new(spaceBuilder); + private readonly TypeConverter converter = new(prefs); private Type type => GetTypeAt(index); private Type? prevType => index == 0 ? null : GetTypeAt(index - 1); @@ -81,7 +81,7 @@ private void DeclareEnum () private string GetNamespace (Type type) { - var space = spaceBuilder.Build(type); + var space = prefs.BuildSpace(type, BuildJSSpace(type)); return space[..space.LastIndexOf('.')]; } @@ -130,7 +130,7 @@ private void Append (string content, int level) private string BuildTypeName (Type type) { - var space = spaceBuilder.Build(type); + var space = prefs.BuildSpace(type, BuildJSSpace(type)); var name = space[(space.LastIndexOf('.') + 1)..]; if (!type.IsGenericType) return name; var args = string.Join(", ", type.GetGenericArguments().Select(BuildTypeName)); From e83d4fd6870d3f6543678646f288d17065c30eaf Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 22:47:49 +0300 Subject: [PATCH 47/75] iteration --- .../Bootsharp.Common.Test/PreferencesTest.cs | 11 +++++ src/cs/Bootsharp.Common/Preferences.cs | 6 ++- .../Mock/MockProject.cs | 14 +++++- .../Pack/BindingTest.cs | 10 +++- src/cs/Bootsharp.Publish.Test/TaskTest.cs | 6 +-- .../Common/Configuration/Configuration.cs | 10 ++++ .../Configuration/ConfigurationResolver.cs | 46 +++++++++++++++++++ .../Common/PreferencesResolver.cs | 42 ----------------- .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 8 ++-- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 12 ++--- 10 files changed, 106 insertions(+), 59 deletions(-) create mode 100644 src/cs/Bootsharp.Common.Test/PreferencesTest.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs diff --git a/src/cs/Bootsharp.Common.Test/PreferencesTest.cs b/src/cs/Bootsharp.Common.Test/PreferencesTest.cs new file mode 100644 index 00000000..1eb9f9ff --- /dev/null +++ b/src/cs/Bootsharp.Common.Test/PreferencesTest.cs @@ -0,0 +1,11 @@ +namespace Bootsharp.Common.Test; + +public class PreferencesTest +{ + [Fact] + public void ReturnDefaultsByDefault () + { + var prefs = new Preferences(); + Assert.Equal("foo", prefs.BuildSpace(default, "foo")); + } +} diff --git a/src/cs/Bootsharp.Common/Preferences.cs b/src/cs/Bootsharp.Common/Preferences.cs index 9ec6728f..fd67055a 100644 --- a/src/cs/Bootsharp.Common/Preferences.cs +++ b/src/cs/Bootsharp.Common/Preferences.cs @@ -1,9 +1,13 @@ -namespace Bootsharp; +using System.Diagnostics.CodeAnalysis; + +namespace Bootsharp; /// /// User preferences for Bootsharp behaviour. Inherit, override required methods and /// supply inherited class to . /// +[SuppressMessage("ReSharper", "UnusedParameter.Global", + Justification = "Accessed in Bootsharp.Publish.Test via generated code.")] public class Preferences { /// diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs index 83b5d531..72330105 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs @@ -14,7 +14,16 @@ public MockProject () CreateBuildResources(); } - public void Dispose () => Directory.Delete(Root, true); + public void Dispose () + { + try { Directory.Delete(Root, true); } + catch + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + Dispose(); + } + } public void AddAssembly (MockAssembly assembly) { @@ -38,7 +47,8 @@ private static string CreateUniqueRootDirectory () { var testAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location; var assemblyDir = Path.Combine(Path.GetDirectoryName(testAssembly)); - return Directory.CreateDirectory(Path.Combine(assemblyDir, $"temp{Guid.NewGuid():N}")).FullName; + var dir = $"bootsharp-temp-{Guid.NewGuid():N}"; + return Directory.CreateDirectory(Path.Combine(assemblyDir, dir)).FullName; } private void CreateBuildResources () diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index f59aea2f..a563d193 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -283,7 +283,15 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () public void PrefsAllowsOverridingObjectNames () { AddAssembly( - With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), + // With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), + With( + """ + [assembly:JSConfiguration] + public class Prefs : Bootsharp.Preferences + { + public override string BuildSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); + } + """), WithClass("Foo.Bar.Nya", "[JSInvokable] public static Task GetNya () => Task.CompletedTask;"), WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun () {}")); Execute(); diff --git a/src/cs/Bootsharp.Publish.Test/TaskTest.cs b/src/cs/Bootsharp.Publish.Test/TaskTest.cs index 7b64d325..8c06b17a 100644 --- a/src/cs/Bootsharp.Publish.Test/TaskTest.cs +++ b/src/cs/Bootsharp.Publish.Test/TaskTest.cs @@ -10,14 +10,14 @@ public abstract class TaskTest : IDisposable protected string LastAddedAssemblyName { get; private set; } protected virtual string TestedContent { get; } = ""; - public virtual void Dispose () + public abstract void Execute (); + + public void Dispose () { Project.Dispose(); GC.SuppressFinalize(this); } - public abstract void Execute (); - protected void AddAssembly (string assemblyName, params MockSource[] sources) { LastAddedAssemblyName = assemblyName; diff --git a/src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs b/src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs new file mode 100644 index 00000000..7705dcc1 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs @@ -0,0 +1,10 @@ +using System.Runtime.Loader; + +namespace Bootsharp.Publish; + +internal sealed class Configuration (Preferences prefs, AssemblyLoadContext ctx) : IDisposable +{ + public Preferences Preferences => prefs; + + public void Dispose () => ctx.Unload(); +} diff --git a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs b/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs new file mode 100644 index 00000000..dc1974fb --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs @@ -0,0 +1,46 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Bootsharp.Publish; + +internal sealed class ConfigurationResolver (string entryAssemblyName) +{ + public Configuration Resolve (string outDir) + { + var ctx = new AssemblyLoadContext(entryAssemblyName, true); + var assembly = LoadMainAssembly(ctx, outDir); + return new(GetPreferences(assembly), ctx); + } + + private Assembly LoadMainAssembly (AssemblyLoadContext ctx, string outDir) + { + var path = Path.Combine(outDir, entryAssemblyName); + return ctx.LoadFromAssemblyPath(path); + } + + private Preferences GetPreferences (Assembly assembly) + { + if (FindConfigurationAttribute(assembly) is not { } attr) return new(); + return InstantiateCustomPrefs(assembly, attr.AttributeType); + } + + private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) + { + foreach (var attr in assembly.CustomAttributes) + if (IsConfigurationAttribute(attr.AttributeType)) + return attr; + return null; + } + + private bool IsConfigurationAttribute (Type attr) + { + if (attr.FullName is null) return false; + return attr.FullName.StartsWith(typeof(JSConfigurationAttribute<>).FullName!, StringComparison.Ordinal); + } + + private Preferences InstantiateCustomPrefs (Assembly assembly, Type attributeType) + { + var prefsType = attributeType.GenericTypeArguments[0]; + return (Preferences)assembly.CreateInstance(prefsType.FullName!)!; + } +} diff --git a/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs deleted file mode 100644 index ecb4d48f..00000000 --- a/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Reflection; - -namespace Bootsharp.Publish; - -internal sealed class PreferencesResolver (string entryAssemblyName) -{ - private static readonly string cfgAttrFullName = typeof(JSConfigurationAttribute<>).FullName!; - - public Preferences Resolve (string outDir) - { - using var ctx = CreateLoadContext(outDir); - var assembly = LoadEntryAssembly(ctx, outDir); - if (FindConfigurationAttribute(assembly) is not { } attr) return new(); - return InstantiateCustomPrefs(attr.AttributeType); - } - - private Assembly LoadEntryAssembly (MetadataLoadContext ctx, string outDir) - { - var path = Path.Combine(outDir, entryAssemblyName); - return ctx.LoadFromAssemblyPath(path); - } - - private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) - { - foreach (var attr in assembly.CustomAttributes) - if (IsConfigurationAttribute(attr.AttributeType)) - return attr; - return null; - } - - private bool IsConfigurationAttribute (Type attributeType) - { - if (attributeType.FullName is null) return false; - return attributeType.FullName.StartsWith(cfgAttrFullName, StringComparison.Ordinal); - } - - private Preferences InstantiateCustomPrefs (Type attributeType) - { - var prefsType = attributeType.GenericTypeArguments[0]; - return (Preferences)Activator.CreateInstance(prefsType)!; - } -} diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 81950a34..92edbb99 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -16,8 +16,8 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task public override bool Execute () { - var prefs = ResolvePreferences(); - using var inspection = InspectAssemblies(prefs); + using var cfg = ResolveConfiguration(); + using var inspection = InspectAssemblies(cfg.Preferences); GenerateInterfaces(inspection); GenerateDependencies(inspection); GenerateSerializer(inspection); @@ -25,9 +25,9 @@ public override bool Execute () return true; } - private Preferences ResolvePreferences () + private Configuration ResolveConfiguration () { - var resolver = new PreferencesResolver(EntryAssemblyName); + var resolver = new ConfigurationResolver(EntryAssemblyName); return resolver.Resolve(InspectedDirectory); } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index 8d482480..c4cf6d89 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -16,18 +16,18 @@ public sealed class BootsharpPack : Microsoft.Build.Utilities.Task public override bool Execute () { - var prefs = ResolvePreferences(); - using var inspection = InspectAssemblies(prefs); - GenerateBindings(prefs, inspection); - GenerateDeclarations(prefs, inspection); + using var cfg = ResolveConfiguration(); + using var inspection = InspectAssemblies(cfg.Preferences); + GenerateBindings(cfg.Preferences, inspection); + GenerateDeclarations(cfg.Preferences, inspection); GenerateResources(inspection); PatchModules(); return true; } - private Preferences ResolvePreferences () + private Configuration ResolveConfiguration () { - var resolver = new PreferencesResolver(EntryAssemblyName); + var resolver = new ConfigurationResolver(EntryAssemblyName); return resolver.Resolve(InspectedDirectory); } From 2069b0c6bd62329bb2afd476e3dc2c8bf49c4875 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 22:49:24 +0300 Subject: [PATCH 48/75] etc --- src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index a563d193..59035783 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -283,7 +283,6 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () public void PrefsAllowsOverridingObjectNames () { AddAssembly( - // With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), With( """ [assembly:JSConfiguration] From 89fd742096be0b7bd4be6b31c49d3b1ae7748d97 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Thu, 18 Jan 2024 22:51:43 +0300 Subject: [PATCH 49/75] iteration --- src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs | 2 +- src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs index 91d724cc..63e358f1 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockTest.cs @@ -7,7 +7,7 @@ public class MockTest [Fact] public void WhenCompileFailsIncludesSourceAndError () { - var project = new MockProject(); + using var project = new MockProject(); var asm = new MockAssembly("asm.dll", [new(null, "foo", false)]); Assert.Contains("Invalid test source code", Assert.Throws(() => project.AddAssembly(asm)).Message); Assert.Contains("foo", Assert.Throws(() => project.AddAssembly(asm)).Message); diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index abfa6cda..6dbdfd9e 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -155,7 +155,14 @@ public void WhenNoSpaceTypesAreDeclaredUnderGlobalSpace () public void PrefsAllowsOverridingSpaceNames () { AddAssembly( - With("""[assembly:JSNamespace(@"Foo\.Bar\.(\S+)", "$1")]"""), + With( + """ + [assembly:JSConfiguration] + public class Prefs : Bootsharp.Preferences + { + public override string BuildSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); + } + """), With("Foo.Bar.Nya", "public class Nya { }"), WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun (Nya.Nya nya) { }")); Execute(); From fe82b579722597d69d8ef5849fd7779286106c1f Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 00:04:58 +0300 Subject: [PATCH 50/75] iteration --- .../Emit/InteropTest.cs | 39 +++++++++++++++++++ .../Configuration/ConfigurationResolver.cs | 9 +---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index ccfc9907..71b6e1fb 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -155,4 +155,43 @@ public class Class Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvAsyncBytes () => Serialize(await global::Space.Class.InvAsyncBytes());"); Contains("""JSImport("Space.Class.funAsyncBytesSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_FunAsyncBytes ();"""); } + + [Fact] + public void RespectsSpacePrefs () + { + AddAssembly(With( + """ + [assembly:JSConfiguration] + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(Space.IImported))] + + namespace Space; + + public class Prefs : Bootsharp.Preferences + { + public override string BuildSpace (Type type, string @default) => @default.Replace("Space", "Foo"); + } + + public interface IExported { void Inv (); } + public interface IImported { void Fun (); void NotifyEvt(); } + + public class Class + { + [JSInvokable] public static void Inv () {} + [JSFunction] public static void Fun () => Proxies.Get("Class.Fun")(); + [JSEvent] public static void Evt () => Proxies.Get("Class.Evt")(); + } + """)); + Execute(); + Contains("""Proxies.Set("Bootsharp.Generated.Imports.Space.JSImported.Fun", () => Bootsharp_Generated_Imports_Space_JSImported_Fun());"""); + Contains("""Proxies.Set("Bootsharp.Generated.Imports.Space.JSImported.OnEvt", () => Bootsharp_Generated_Imports_Space_JSImported_OnEvt());"""); + Contains("""Proxies.Set("Space.Class.Fun", () => Space_Class_Fun());"""); + Contains("""Proxies.Set("Space.Class.Evt", () => Space_Class_Evt());"""); + Contains("JSExport] internal static void Space_Class_Inv () => global::Space.Class.Inv();"); + Contains("""JSImport("Foo.Class.funSerialized", "Bootsharp")] internal static partial void Space_Class_Fun ();"""); + Contains("""JSImport("Foo.Class.evtSerialized", "Bootsharp")] internal static partial void Space_Class_Evt ();"""); + Contains("JSExport] internal static void Bootsharp_Generated_Exports_Space_JSExported_Inv () => global::Bootsharp.Generated.Exports.Space.JSExported.Inv();"); + Contains("""JSImport("Foo.Imported.funSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_Space_JSImported_Fun ();"""); + Contains("""JSImport("Foo.Imported.onEvtSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_Space_JSImported_OnEvt ();"""); + } } diff --git a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs b/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs index dc1974fb..f07a6efb 100644 --- a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs +++ b/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs @@ -26,18 +26,13 @@ private Preferences GetPreferences (Assembly assembly) private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) { + var cfgAttr = typeof(JSConfigurationAttribute<>).FullName!; foreach (var attr in assembly.CustomAttributes) - if (IsConfigurationAttribute(attr.AttributeType)) + if (attr.AttributeType.FullName!.StartsWith(cfgAttr, StringComparison.Ordinal)) return attr; return null; } - private bool IsConfigurationAttribute (Type attr) - { - if (attr.FullName is null) return false; - return attr.FullName.StartsWith(typeof(JSConfigurationAttribute<>).FullName!, StringComparison.Ordinal); - } - private Preferences InstantiateCustomPrefs (Assembly assembly, Type attributeType) { var prefsType = attributeType.GenericTypeArguments[0]; From 96a6f4c24453040da1c90e0f8919dfa46b7285ff Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 00:10:51 +0300 Subject: [PATCH 51/75] etc --- src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs | 16 +++++++--------- src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs | 14 ++++++-------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 92edbb99..31587715 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -1,18 +1,16 @@ -using Microsoft.Build.Framework; - -namespace Bootsharp.Publish; +namespace Bootsharp.Publish; /// /// First pass: emits C# sources to be picked by .NET's source generators. /// public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task { - [Required] public required string InspectedDirectory { get; set; } - [Required] public required string EntryAssemblyName { get; set; } - [Required] public required string InterfacesFilePath { get; set; } - [Required] public required string DependenciesFilePath { get; set; } - [Required] public required string SerializerFilePath { get; set; } - [Required] public required string InteropFilePath { get; set; } + public required string InspectedDirectory { get; set; } + public required string EntryAssemblyName { get; set; } + public required string InterfacesFilePath { get; set; } + public required string DependenciesFilePath { get; set; } + public required string SerializerFilePath { get; set; } + public required string InteropFilePath { get; set; } public override bool Execute () { diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index c4cf6d89..3ef3229a 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -1,5 +1,3 @@ -using Microsoft.Build.Framework; - namespace Bootsharp.Publish; /// @@ -7,12 +5,12 @@ namespace Bootsharp.Publish; /// public sealed class BootsharpPack : Microsoft.Build.Utilities.Task { - [Required] public required string BuildDirectory { get; set; } - [Required] public required string InspectedDirectory { get; set; } - [Required] public required string EntryAssemblyName { get; set; } - [Required] public required bool TrimmingEnabled { get; set; } - [Required] public required bool EmbedBinaries { get; set; } - [Required] public required bool Threading { get; set; } + public required string BuildDirectory { get; set; } + public required string InspectedDirectory { get; set; } + public required string EntryAssemblyName { get; set; } + public required bool TrimmingEnabled { get; set; } + public required bool EmbedBinaries { get; set; } + public required bool Threading { get; set; } public override bool Execute () { From 55f0972b8ec265030fd315b4276ad3d86c4bce5a Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 05:15:58 +0300 Subject: [PATCH 52/75] iteration --- .../Bootsharp.Common.Test/PreferencesTest.cs | 2 +- .../Attributes/JSConfigurationAttribute.cs | 2 +- src/cs/Bootsharp.Common/Preferences.cs | 36 +++++++++-- .../Emit/InteropTest.cs | 4 +- .../Pack/BindingTest.cs | 64 +++++++++---------- .../Pack/DeclarationTest.cs | 57 +++++++++++------ .../AssemblyInspector/AssemblyInspector.cs | 4 +- .../Common/TypeConverter/TypeConverter.cs | 8 +-- .../Pack/BindingGenerator.cs | 2 +- .../TypeDeclarationGenerator.cs | 6 +- 10 files changed, 113 insertions(+), 72 deletions(-) diff --git a/src/cs/Bootsharp.Common.Test/PreferencesTest.cs b/src/cs/Bootsharp.Common.Test/PreferencesTest.cs index 1eb9f9ff..1077b117 100644 --- a/src/cs/Bootsharp.Common.Test/PreferencesTest.cs +++ b/src/cs/Bootsharp.Common.Test/PreferencesTest.cs @@ -6,6 +6,6 @@ public class PreferencesTest public void ReturnDefaultsByDefault () { var prefs = new Preferences(); - Assert.Equal("foo", prefs.BuildSpace(default, "foo")); + Assert.Equal("foo", prefs.ResolveSpace(default, "foo")); } } diff --git a/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs index 919647b8..75fa390a 100644 --- a/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs +++ b/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs @@ -15,7 +15,7 @@ /// /// public class MyPrefs : Preferences /// { -/// public override string BuildSpace (Type type, string @default) +/// public override string ResolveSpace (Type type, string @default) /// { /// var lastDotIdx = @default.LastIndexOf('.'); /// if (lastDotIdx >= 0) return @default[lastDotIdx..]; diff --git a/src/cs/Bootsharp.Common/Preferences.cs b/src/cs/Bootsharp.Common/Preferences.cs index fd67055a..11de07cc 100644 --- a/src/cs/Bootsharp.Common/Preferences.cs +++ b/src/cs/Bootsharp.Common/Preferences.cs @@ -1,17 +1,18 @@ using System.Diagnostics.CodeAnalysis; +using System.Reflection; namespace Bootsharp; /// -/// User preferences for Bootsharp behaviour. Inherit, override required methods and -/// supply inherited class to . +/// User preferences for Bootsharp behaviour. Inherit, override methods and +/// supply inherited class type to . /// [SuppressMessage("ReSharper", "UnusedParameter.Global", Justification = "Accessed in Bootsharp.Publish.Test via generated code.")] public class Preferences { /// - /// Builds JavaScript namespace (object chain) for specified C# type. + /// Resolves JavaScript namespace (object names chain) for specified C# type. /// /// /// This affect both objects generated to host bindings and type names @@ -19,7 +20,30 @@ public class Preferences /// object name, the is declaring type of the /// associated method. /// - /// C# type to build namespace for. - /// Result when processed w/o the override. - public virtual string BuildSpace (Type type, string @default) => @default; + /// C# type to resolve namespace from. + /// Result when resolved w/o the override. + public virtual string ResolveSpace (Type type, string @default) => @default; + /// + /// Resolves TypeScript type syntax of a function member or object property from associated C# type. + /// + /// C# type to resolve TypeScript syntax from. + /// C# nullability info, ie whether associated member is nullable. + /// Result when resolved w/o the override. + public virtual string ResolveType (Type type, NullabilityInfo? nullability, string @default) => @default; + /// + /// Resolves interop-specific metadata for an interop interface specified under + /// or . + /// + /// The interface type. + /// Whether the interface exports to or imports APIs from JavaScript. + /// Result when resolved w/o the override. + public virtual InterfaceMeta ResolveInterface (Type type, InterfaceKind kind, InterfaceMeta @default) => @default; + /// + /// Resolves interop-specific metadata for an interop method attributed with either + /// , or . + /// + /// Info about the interop method. + /// Whether the method is intended to be invoked in JavaScript or vice-versa. + /// Result when resolved w/o the override. + public virtual MethodMeta ResolveMethod (MethodInfo info, MethodKind kind, MethodMeta @default) => @default; } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index 71b6e1fb..e5ad5f0f 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -157,7 +157,7 @@ public class Class } [Fact] - public void RespectsSpacePrefs () + public void RespectsResolveSpacePref () { AddAssembly(With( """ @@ -169,7 +169,7 @@ namespace Space; public class Prefs : Bootsharp.Preferences { - public override string BuildSpace (Type type, string @default) => @default.Replace("Space", "Foo"); + public override string ResolveSpace (Type type, string @default) => @default.Replace("Space", "Foo"); } public interface IExported { void Inv (); } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 59035783..3ade293f 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -279,38 +279,6 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () """); } - [Fact] - public void PrefsAllowsOverridingObjectNames () - { - AddAssembly( - With( - """ - [assembly:JSConfiguration] - public class Prefs : Bootsharp.Preferences - { - public override string BuildSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); - } - """), - WithClass("Foo.Bar.Nya", "[JSInvokable] public static Task GetNya () => Task.CompletedTask;"), - WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun () {}")); - Execute(); - Contains( - """ - export const Fun = { - Class: { - get onFun() { return this.onFunHandler; }, - set onFun(handler) { this.onFunHandler = handler; this.onFunSerializedHandler = () => this.onFunHandler(); }, - get onFunSerialized() { if (typeof this.onFunHandler !== "function") throw Error("Failed to invoke 'Fun.Class.onFun' from C#. Make sure to assign function in JavaScript."); return this.onFunSerializedHandler; } - } - }; - export const Nya = { - Class: { - getNya: () => getExports().Foo_Bar_Nya_Class.GetNya() - } - }; - """); - } - [Fact] public void VariablesConflictingWithJSTypesAreRenamed () { @@ -419,4 +387,36 @@ public void CustomEnumIndexesArePreservedInJS () }; """); } + + [Fact] + public void RespectsResolveSpacePref () + { + AddAssembly( + With( + """ + [assembly:JSConfiguration] + public class Prefs : Bootsharp.Preferences + { + public override string ResolveSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); + } + """), + WithClass("Foo.Bar.Nya", "[JSInvokable] public static Task GetNya () => Task.CompletedTask;"), + WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun () {}")); + Execute(); + Contains( + """ + export const Fun = { + Class: { + get onFun() { return this.onFunHandler; }, + set onFun(handler) { this.onFunHandler = handler; this.onFunSerializedHandler = () => this.onFunHandler(); }, + get onFunSerialized() { if (typeof this.onFunHandler !== "function") throw Error("Failed to invoke 'Fun.Class.onFun' from C#. Make sure to assign function in JavaScript."); return this.onFunSerializedHandler; } + } + }; + export const Nya = { + Class: { + getNya: () => getExports().Foo_Bar_Nya_Class.GetNya() + } + }; + """); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 6dbdfd9e..532f99eb 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -151,25 +151,6 @@ public void WhenNoSpaceTypesAreDeclaredUnderGlobalSpace () Contains("export namespace Global.Class {\n export let onFoo: (foo: Global.Foo) => void;\n}"); } - [Fact] - public void PrefsAllowsOverridingSpaceNames () - { - AddAssembly( - With( - """ - [assembly:JSConfiguration] - public class Prefs : Bootsharp.Preferences - { - public override string BuildSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); - } - """), - With("Foo.Bar.Nya", "public class Nya { }"), - WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun (Nya.Nya nya) { }")); - Execute(); - Contains("export namespace Nya {\n export interface Nya {\n }\n}"); - Contains("export namespace Fun.Class {\n export let onFun: (nya: Nya.Nya) => void;\n}"); - } - [Fact] public void NumericsTranslatedToNumber () { @@ -618,4 +599,42 @@ public void WhenTypeReferencedMultipleTimesItsDeclaredOnlyOnce () Assert.Single(Matches("export interface Bar")); Assert.Single(Matches("export interface Far")); } + + [Fact] + public void RespectsResolveSpacePref () + { + AddAssembly( + With( + """ + [assembly:JSConfiguration] + public class Prefs : Bootsharp.Preferences + { + public override string ResolveSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); + } + """), + With("Foo.Bar.Nya", "public class Nya { }"), + WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun (Nya.Nya nya) { }")); + Execute(); + Contains("export namespace Nya {\n export interface Nya {\n }\n}"); + Contains("export namespace Fun.Class {\n export let onFun: (nya: Nya.Nya) => void;\n}"); + } + + [Fact] + public void RespectsResolveTypePref () + { + AddAssembly( + With( + """ + using System.Reflection; + [assembly:JSConfiguration] + public class Prefs : Bootsharp.Preferences + { + public override string ResolveType (Type _, NullabilityInfo? __, string @default) => @default.Replace("Record", "Foo"); + } + """), + With("public record Record;"), + WithClass("[JSInvokable] public static void Inv (Record r) {}")); + Execute(); + Contains("inv(r: Global.Foo): void"); + } } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index dac0c151..f15dcd59 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -101,7 +101,7 @@ private void AddInterface (Type iType, InterfaceKind kind) Void = IsVoid(info.ReturnType), Serialized = ShouldSerialize(info.ReturnType) }, - JSSpace = prefs.BuildSpace(info.DeclaringType, BuildJSSpace(info.DeclaringType)), + JSSpace = prefs.ResolveSpace(info.DeclaringType, BuildJSSpace(info.DeclaringType)), JSName = ToFirstLower(info.Name) }; @@ -140,7 +140,7 @@ private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKin : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event : MethodKind.Function; var name = mKind == MethodKind.Event ? $"On{info.Name[6..]}" : info.Name; - var jsSpace = prefs.BuildSpace(info.DeclaringType!, BuildJSSpace(info.DeclaringType!)); + var jsSpace = prefs.ResolveSpace(info.DeclaringType!, BuildJSSpace(info.DeclaringType!)); jsSpace = jsSpace[..(jsSpace.LastIndexOf('.') + 1)] + jsSpace[(jsSpace.LastIndexOf('.') + 2)..]; return new() { Name = info.Name, diff --git a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs index f17c664a..ce1052ea 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs @@ -9,14 +9,12 @@ internal sealed class TypeConverter (Preferences prefs) private readonly TypeCrawler crawler = new(); private NullabilityInfo? nullability; - public string ToTypeScript (Type type) => ToTypeScript(type, null); - public string ToTypeScript (Type type, NullabilityInfo? nullability) { this.nullability = nullability; // nullability of topmost type declarations is evaluated outside (method/property info) if (IsNullable(type)) type = GetNullableUnderlyingType(type); - return Convert(type); + return prefs.ResolveType(type, nullability, Convert(type)); } private string Convert (Type type) @@ -69,13 +67,13 @@ private string ConvertGeneric (Type type) { EnterNullability(type); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); - return $"{prefs.BuildSpace(type, BuildJSSpace(type))}<{args}>"; + return $"{prefs.ResolveSpace(type, BuildJSSpace(type))}<{args}>"; } private string ConvertFinal (Type type) { if (type.Name == "Void") return "void"; - if (CrawledTypes.Contains(type)) return prefs.BuildSpace(type, BuildJSSpace(type)); + if (CrawledTypes.Contains(type)) return prefs.ResolveSpace(type, BuildJSSpace(type)); return Type.GetTypeCode(type) switch { TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 73a8f656..71df1291 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -20,7 +20,7 @@ public string Generate (AssemblyInspection inspection) bindings = inspection.Methods .Select(m => new Binding(m, null, m.JSSpace)) .Concat(inspection.Crawled.Where(t => t.IsEnum) - .Select(t => new Binding(null, t, prefs.BuildSpace(t, BuildJSSpace(t))))) + .Select(t => new Binding(null, t, prefs.ResolveSpace(t, BuildJSSpace(t))))) .OrderBy(m => m.Namespace).ToArray(); if (bindings.Length == 0) return ""; EmitImports(); diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index a0c5fdf1..f550d916 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -81,7 +81,7 @@ private void DeclareEnum () private string GetNamespace (Type type) { - var space = prefs.BuildSpace(type, BuildJSSpace(type)); + var space = prefs.ResolveSpace(type, BuildJSSpace(type)); return space[..space.LastIndexOf('.')]; } @@ -91,7 +91,7 @@ private void AppendExtensions () if (type.BaseType is { } baseType && types.Contains(baseType)) extTypes.Insert(0, baseType); if (extTypes.Count > 0) - builder.Append(" extends ").AppendJoin(", ", extTypes.Select(converter.ToTypeScript)); + builder.Append(" extends ").AppendJoin(", ", extTypes.Select(t => prefs.ResolveSpace(t, BuildJSSpace(t)))); } private void AppendProperties () @@ -130,7 +130,7 @@ private void Append (string content, int level) private string BuildTypeName (Type type) { - var space = prefs.BuildSpace(type, BuildJSSpace(type)); + var space = prefs.ResolveSpace(type, BuildJSSpace(type)); var name = space[(space.LastIndexOf('.') + 1)..]; if (!type.IsGenericType) return name; var args = string.Join(", ", type.GetGenericArguments().Select(BuildTypeName)); From 057880c3063de228ce27e31393ea0011603b3b7e Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 06:24:46 +0300 Subject: [PATCH 53/75] iteration --- src/cs/Bootsharp.Common/Preferences.cs | 4 +- .../Emit/InterfacesTest.cs | 51 +++++++++++++++++++ .../Pack/BindingTest.cs | 27 ++++++++++ .../Pack/DeclarationTest.cs | 2 +- .../AssemblyInspector/AssemblyInspector.cs | 8 +-- .../Common/TypeConverter/TypeConverter.cs | 22 ++++---- 6 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/cs/Bootsharp.Common/Preferences.cs b/src/cs/Bootsharp.Common/Preferences.cs index 11de07cc..000aa389 100644 --- a/src/cs/Bootsharp.Common/Preferences.cs +++ b/src/cs/Bootsharp.Common/Preferences.cs @@ -27,9 +27,9 @@ public class Preferences /// Resolves TypeScript type syntax of a function member or object property from associated C# type. /// /// C# type to resolve TypeScript syntax from. - /// C# nullability info, ie whether associated member is nullable. + /// C# nullability status, ie whether associated member is nullable/optional. /// Result when resolved w/o the override. - public virtual string ResolveType (Type type, NullabilityInfo? nullability, string @default) => @default; + public virtual string ResolveType (Type type, NullabilityInfo nullability, string @default) => @default; /// /// Resolves interop-specific metadata for an interop interface specified under /// or . diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs index 9e289ade..7de8d733 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -197,4 +197,55 @@ public class JSImported : global::IImported } """); } + + [Fact] + public void RespectsResolveInterfacePref () + { + AddAssembly(With( + """ + [assembly:JSConfiguration] + [assembly:JSImport(typeof(IImported))] + + public class Prefs : Bootsharp.Preferences + { + public override InterfaceMeta ResolveInterface (Type _, InterfaceKind __, InterfaceMeta @default) + { + var method = ((IReadOnlyList)@default.Methods)[0]; + return @default with { + Name = "Foo", + Methods = [method with { Generated = method.Generated with { Space = "Bootsharp.Generated.Imports.Foo" } }] + }; + } + } + + public interface IImported { void NotifyEvt (); } + """)); + Execute(); + Contains( + """ + namespace Bootsharp.Generated.Imports + { + public class Foo : global::IImported + { + [JSEvent] public static void OnEvt () => Proxies.Get("Bootsharp.Generated.Imports.Foo.OnEvt")(); + + void global::IImported.NotifyEvt () => OnEvt(); + } + } + """); + Contains( + """ + namespace Bootsharp.Generated + { + internal static class InterfaceRegistrations + { + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void RegisterInterfaces () + { + Interfaces.Register(typeof(global::IImported), new ImportInterface(new Bootsharp.Generated.Imports.Foo())); + } + } + } + """); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 3ade293f..9e763c17 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -419,4 +419,31 @@ public class Prefs : Bootsharp.Preferences }; """); } + + [Fact] + public void RespectsResolveMethodPref () + { + AddAssembly( + With( + """ + [assembly:JSConfiguration] + + public class Prefs : Bootsharp.Preferences + { + public override MethodMeta ResolveMethod (System.Reflection.MethodInfo _, MethodKind __, MethodMeta @default) => + @default with { JSName = "foo" }; + } + """), + WithClass("Space", "[JSInvokable] public static void Inv () {}") + ); + Execute(); + Contains( + """ + export const Space = { + Class: { + foo: () => getExports().Space_Class.Inv() + } + }; + """); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 532f99eb..287aaf4d 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -629,7 +629,7 @@ public void RespectsResolveTypePref () [assembly:JSConfiguration] public class Prefs : Bootsharp.Preferences { - public override string ResolveType (Type _, NullabilityInfo? __, string @default) => @default.Replace("Record", "Foo"); + public override string ResolveType (Type _, NullabilityInfo __, string @default) => @default.Replace("Record", "Foo"); } """), With("public record Record;"), diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index f15dcd59..3c1ed1ae 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -86,7 +86,7 @@ private void AddInterface (Type iType, InterfaceKind kind) methods.Add(method.Generated); } - private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => new() { + private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => prefs.ResolveMethod(info, kind, new() { Kind = kind, Assembly = info.DeclaringType!.Assembly.GetName().Name!, Space = info.DeclaringType.FullName!, @@ -103,7 +103,7 @@ private void AddInterface (Type iType, InterfaceKind kind) }, JSSpace = prefs.ResolveSpace(info.DeclaringType, BuildJSSpace(info.DeclaringType)), JSName = ToFirstLower(info.Name) - }; + }); private ArgumentMeta CreateArgument (ParameterInfo info) => new() { Name = info.Name!, @@ -125,13 +125,13 @@ private InterfaceMeta CreateInterface (Type iType, InterfaceKind kind) if (iType.Namespace != null) space += $".{iType.Namespace}"; var name = "JS" + iType.Name[1..]; var mSpace = $"{space}.{name}"; - return new InterfaceMeta { + return prefs.ResolveInterface(iType, kind, new InterfaceMeta { Kind = kind, TypeSyntax = BuildSyntax(iType), Namespace = space, Name = name, Methods = iType.GetMethods().Select(m => CreateInterfaceMethod(m, kind, mSpace)).ToArray() - }; + }); } private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKind iKind, string space) diff --git a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs index ce1052ea..47c2bbde 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs @@ -7,9 +7,9 @@ internal sealed class TypeConverter (Preferences prefs) public IReadOnlyCollection CrawledTypes => crawler.Crawled; private readonly TypeCrawler crawler = new(); - private NullabilityInfo? nullability; + private NullabilityInfo nullability = null!; - public string ToTypeScript (Type type, NullabilityInfo? nullability) + public string ToTypeScript (Type type, NullabilityInfo nullability) { this.nullability = nullability; // nullability of topmost type declarations is evaluated outside (method/property info) @@ -36,7 +36,7 @@ private string ConvertNullable (Type type) private string ConvertList (Type type) { var elementType = GetListElementType(type); - if (EnterNullability(type)) return $"Array<{Convert(elementType)} | null>"; + if (EnterNullability()) return $"Array<{Convert(elementType)} | null>"; return Type.GetTypeCode(elementType) switch { TypeCode.Byte => "Uint8Array", TypeCode.SByte => "Int8Array", @@ -58,14 +58,14 @@ private string ConvertDictionary (Type type) private string ConvertAwaitable (Type type) { - EnterNullability(type); + EnterNullability(); if (type.GenericTypeArguments.Length == 0) return "Promise"; return $"Promise<{Convert(type.GenericTypeArguments[0])}>"; } private string ConvertGeneric (Type type) { - EnterNullability(type); + EnterNullability(); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); return $"{prefs.ResolveSpace(type, BuildJSSpace(type))}<{args}>"; } @@ -86,13 +86,11 @@ TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or }; } - private bool EnterNullability (Type type) + private bool EnterNullability () { - if (nullability is null) return false; - var nullable = nullability.ElementType?.ReadState == NullabilityState.Nullable || - nullability.GenericTypeArguments.FirstOrDefault()?.ReadState == NullabilityState.Nullable; - if (type.IsArray) nullability = nullability.ElementType; - else nullability = nullability.GenericTypeArguments.FirstOrDefault(); - return nullable; + if (nullability.GenericTypeArguments.Length > 0) nullability = nullability.GenericTypeArguments[0]; + else if (nullability.ElementType != null) nullability = nullability.ElementType; + else return false; + return nullability.ReadState == NullabilityState.Nullable; } } From 1d91f0dddca2efd11689bc581dc245cf76d61323 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 20:35:51 +0300 Subject: [PATCH 54/75] pack --- src/cs/Bootsharp.Inject.Test/ExtensionsTest.cs | 4 ++-- src/cs/Bootsharp.Inject/Extensions.cs | 14 +++++++------- src/cs/Directory.Build.props | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cs/Bootsharp.Inject.Test/ExtensionsTest.cs b/src/cs/Bootsharp.Inject.Test/ExtensionsTest.cs index 4fee53df..6fffab6c 100644 --- a/src/cs/Bootsharp.Inject.Test/ExtensionsTest.cs +++ b/src/cs/Bootsharp.Inject.Test/ExtensionsTest.cs @@ -37,7 +37,7 @@ public void WhenMissingRequiredDependencyErrorIsThrown () // emulates auto-generated code behaviour on module initialization private static void AddAutogenerated () { - BindingRegistry.Register(typeof(JSBackend), new ExportBinding(typeof(IBackend), h => new JSBackend((IBackend)h))); - BindingRegistry.Register(typeof(IFrontend), new ImportBinding(new JSFrontend())); + Interfaces.Register(typeof(JSBackend), new ExportInterface(typeof(IBackend), h => new JSBackend((IBackend)h))); + Interfaces.Register(typeof(IFrontend), new ImportInterface(new JSFrontend())); } } diff --git a/src/cs/Bootsharp.Inject/Extensions.cs b/src/cs/Bootsharp.Inject/Extensions.cs index ae0c607c..3ef78707 100644 --- a/src/cs/Bootsharp.Inject/Extensions.cs +++ b/src/cs/Bootsharp.Inject/Extensions.cs @@ -12,14 +12,14 @@ public static class Extensions /// public static IServiceCollection AddBootsharp (this IServiceCollection services) { - foreach (var (impl, binding) in BindingRegistry.Exports) + foreach (var (impl, binding) in Interfaces.Exports) services.AddSingleton(impl, provider => { - var handler = provider.GetService(binding.Api); - if (handler is null) throw new Error($"Failed to run Bootsharp: '{binding.Api}' dependency is not registered."); - return binding.Factory(provider.GetRequiredService(binding.Api)); + var handler = provider.GetService(binding.Interface); + if (handler is null) throw new Error($"Failed to run Bootsharp: '{binding.Interface}' dependency is not registered."); + return binding.Factory(provider.GetRequiredService(binding.Interface)); }); - foreach (var (api, binding) in BindingRegistry.Imports) - services.AddSingleton(api, binding.Implementation); + foreach (var (api, binding) in Interfaces.Imports) + services.AddSingleton(api, binding.Instance); return services; } @@ -28,7 +28,7 @@ public static IServiceCollection AddBootsharp (this IServiceCollection services) /// public static IServiceProvider RunBootsharp (this IServiceProvider provider) { - foreach (var (impl, _) in BindingRegistry.Exports) + foreach (var (impl, _) in Interfaces.Exports) provider.GetRequiredService(impl); return provider; } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 94fb500e..91819f47 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.1 + 0.2.0-alpha.2 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From ba7c12e5aa96994202728bf05e4bfdb0b7f91fd6 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 21:00:04 +0300 Subject: [PATCH 55/75] iteration --- .../Common/Configuration/ConfigurationResolver.cs | 2 +- src/js/test/cs/Test/Invokable.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs b/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs index f07a6efb..a2dae492 100644 --- a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs +++ b/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs @@ -14,7 +14,7 @@ public Configuration Resolve (string outDir) private Assembly LoadMainAssembly (AssemblyLoadContext ctx, string outDir) { - var path = Path.Combine(outDir, entryAssemblyName); + var path = Path.GetFullPath(Path.Combine(outDir, entryAssemblyName)); return ctx.LoadFromAssemblyPath(path); } diff --git a/src/js/test/cs/Test/Invokable.cs b/src/js/test/cs/Test/Invokable.cs index 04dbb1bb..5724b605 100644 --- a/src/js/test/cs/Test/Invokable.cs +++ b/src/js/test/cs/Test/Invokable.cs @@ -5,7 +5,7 @@ namespace Test; -public static partial class Invokable +public static class Invokable { [JSInvokable] public static void InvokeVoid () { } From 759d0ae0176d1c262c9182f3a4d20e3d145af0a9 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Fri, 19 Jan 2024 22:05:53 +0300 Subject: [PATCH 56/75] iteration --- src/cs/Bootsharp.Common/Interop/Proxies.cs | 4 ++-- src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs | 8 ++++---- src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cs/Bootsharp.Common/Interop/Proxies.cs b/src/cs/Bootsharp.Common/Interop/Proxies.cs index 8bf16c39..f7b95c2b 100644 --- a/src/cs/Bootsharp.Common/Interop/Proxies.cs +++ b/src/cs/Bootsharp.Common/Interop/Proxies.cs @@ -20,8 +20,8 @@ /// /// Proxies.Set("Space.Class.Foo", (arg) => Bootsharp.Generated.Interop.Space_Class_Foo(arg)); ///
-/// Registered proxy is accessed as follows -/// (emitted by source generator to implement original partial "Foo" method):
+/// Registered proxy is accessed as follows (emitted by source generator to +/// implement original partial "Foo" method):
/// /// public static int Foo (string arg) => >("Space.Class.Foo")(arg);]]> ///
diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index e5ad5f0f..ade62155 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -114,8 +114,8 @@ public class Class } """)); Execute(); - Contains("""Proxies.Set("Space.Class.Fun", (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) => Space_Class_Fun(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16));"""); - Contains("""Proxies.Set("Space.Class.FunNull", (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) => Space_Class_FunNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16));"""); + Contains("""Proxies.Set("Space.Class.Fun", (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, global::System.DateTime a10, global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => Space_Class_Fun(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16));"""); + Contains("""Proxies.Set("Space.Class.FunNull", (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, global::System.DateTime? a10, global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte[]? a13, global::System.Int32[]? a14, global::System.Double[]? a15, global::System.String[]? a16) => Space_Class_FunNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16));"""); Contains("JSExport] internal static global::System.Threading.Tasks.Task Space_Class_Inv (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16) => global::Space.Class.Inv(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("JSExport] [return: JSMarshalAs>] internal static global::System.Threading.Tasks.Task Space_Class_InvNull (global::System.Boolean? a1, global::System.Byte? a2, global::System.Char? a3, global::System.Int16? a4, [JSMarshalAs] global::System.Int64? a5, global::System.Int32? a6, global::System.Single? a7, global::System.Double? a8, global::System.IntPtr? a9, [JSMarshalAs] global::System.DateTime? a10, [JSMarshalAs] global::System.DateTimeOffset? a11, global::System.String? a12, global::System.Byte[]? a13, global::System.Int32[]? a14, global::System.Double[]? a15, global::System.String[]? a16) => global::Space.Class.InvNull(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);"); Contains("""JSImport("Space.Class.funSerialized", "Bootsharp")] internal static partial global::System.Threading.Tasks.Task Space_Class_Fun (global::System.Boolean a1, global::System.Byte a2, global::System.Char a3, global::System.Int16 a4, [JSMarshalAs] global::System.Int64 a5, global::System.Int32 a6, global::System.Single a7, global::System.Double a8, global::System.IntPtr a9, [JSMarshalAs] global::System.DateTime a10, [JSMarshalAs] global::System.DateTimeOffset a11, global::System.String a12, global::System.Byte[] a13, global::System.Int32[] a14, global::System.Double[] a15, global::System.String[] a16);"""); @@ -143,8 +143,8 @@ public class Class } """)); Execute(); - Contains("""Proxies.Set("Space.Class.FunA", (a) => Deserialize(Space_Class_FunA(Serialize(a))));"""); - Contains("""Proxies.Set("Space.Class.FunB", async (a) => Deserialize(await Space_Class_FunB(Serialize(a))));"""); + Contains("""Proxies.Set("Space.Class.FunA", (global::Space.Record a) => Deserialize(Space_Class_FunA(Serialize(a))));"""); + Contains("""Proxies.Set("Space.Class.FunB", async (global::Space.Record?[]? a) => Deserialize(await Space_Class_FunB(Serialize(a))));"""); Contains("JSExport] internal static global::System.String Space_Class_InvA (global::System.String a) => Serialize(global::Space.Class.InvA(Deserialize(a)));"); Contains("JSExport] internal static async global::System.Threading.Tasks.Task Space_Class_InvB (global::System.String? a) => Serialize(await global::Space.Class.InvB(Deserialize(a)));"); Contains("""JSImport("Space.Class.funASerialized", "Bootsharp")] internal static partial global::System.String Space_Class_FunA (global::System.String a);"""); diff --git a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs index ee2c2cf8..58bd74e7 100644 --- a/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InteropGenerator.cs @@ -82,7 +82,7 @@ string BuildBodyArg (ArgumentMeta arg) private void AddProxy (MethodMeta method) { var id = $"{method.Space}.{method.Name}"; - var args = string.Join(", ", method.Arguments.Select(arg => arg.Name)); + var args = string.Join(", ", method.Arguments.Select(arg => $"{arg.Value.TypeSyntax} {arg.Name}")); var wait = method.ReturnValue.Async && method.ReturnValue.Serialized; var async = wait ? "async " : ""; proxies.Add($"""Proxies.Set("{id}", {async}({args}) => {BuildBody()});"""); From 14063c4554216621d2c441f3d20d6c79584089fb Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:28:28 +0300 Subject: [PATCH 57/75] iteration --- .../Bootsharp.Publish.Test/Mock/MockProject.cs | 11 +---------- .../Common/Configuration/Configuration.cs | 10 ---------- ...urationResolver.cs => PreferencesResolver.cs} | 16 ++++++---------- src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs | 8 ++++---- src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs | 12 ++++++------ src/cs/Directory.Build.props | 2 +- 6 files changed, 18 insertions(+), 41 deletions(-) delete mode 100644 src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs rename src/cs/Bootsharp.Publish/Common/{Configuration/ConfigurationResolver.cs => PreferencesResolver.cs} (79%) diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs index 72330105..ad20e423 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockProject.cs @@ -14,16 +14,7 @@ public MockProject () CreateBuildResources(); } - public void Dispose () - { - try { Directory.Delete(Root, true); } - catch - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - Dispose(); - } - } + public void Dispose () => Directory.Delete(Root, true); public void AddAssembly (MockAssembly assembly) { diff --git a/src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs b/src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs deleted file mode 100644 index 7705dcc1..00000000 --- a/src/cs/Bootsharp.Publish/Common/Configuration/Configuration.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Runtime.Loader; - -namespace Bootsharp.Publish; - -internal sealed class Configuration (Preferences prefs, AssemblyLoadContext ctx) : IDisposable -{ - public Preferences Preferences => prefs; - - public void Dispose () => ctx.Unload(); -} diff --git a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs similarity index 79% rename from src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs rename to src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs index a2dae492..5a4ac754 100644 --- a/src/cs/Bootsharp.Publish/Common/Configuration/ConfigurationResolver.cs +++ b/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs @@ -3,25 +3,21 @@ namespace Bootsharp.Publish; -internal sealed class ConfigurationResolver (string entryAssemblyName) +internal sealed class PreferencesResolver (string entryAssemblyName) { - public Configuration Resolve (string outDir) + public Preferences Resolve (string outDir) { var ctx = new AssemblyLoadContext(entryAssemblyName, true); var assembly = LoadMainAssembly(ctx, outDir); - return new(GetPreferences(assembly), ctx); + if (FindConfigurationAttribute(assembly) is not { } attr) return new(); + return InstantiateCustomPrefs(assembly, attr.AttributeType); } private Assembly LoadMainAssembly (AssemblyLoadContext ctx, string outDir) { var path = Path.GetFullPath(Path.Combine(outDir, entryAssemblyName)); - return ctx.LoadFromAssemblyPath(path); - } - - private Preferences GetPreferences (Assembly assembly) - { - if (FindConfigurationAttribute(assembly) is not { } attr) return new(); - return InstantiateCustomPrefs(assembly, attr.AttributeType); + using var stream = File.OpenRead(path); + return ctx.LoadFromStream(stream); } private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 31587715..37da354f 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -14,8 +14,8 @@ public sealed class BootsharpEmit : Microsoft.Build.Utilities.Task public override bool Execute () { - using var cfg = ResolveConfiguration(); - using var inspection = InspectAssemblies(cfg.Preferences); + var prefs = ResolvePreferences(); + using var inspection = InspectAssemblies(prefs); GenerateInterfaces(inspection); GenerateDependencies(inspection); GenerateSerializer(inspection); @@ -23,9 +23,9 @@ public override bool Execute () return true; } - private Configuration ResolveConfiguration () + private Preferences ResolvePreferences () { - var resolver = new ConfigurationResolver(EntryAssemblyName); + var resolver = new PreferencesResolver(EntryAssemblyName); return resolver.Resolve(InspectedDirectory); } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index 3ef3229a..dda7baaa 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -14,18 +14,18 @@ public sealed class BootsharpPack : Microsoft.Build.Utilities.Task public override bool Execute () { - using var cfg = ResolveConfiguration(); - using var inspection = InspectAssemblies(cfg.Preferences); - GenerateBindings(cfg.Preferences, inspection); - GenerateDeclarations(cfg.Preferences, inspection); + var prefs = ResolvePreferences(); + using var inspection = InspectAssemblies(prefs); + GenerateBindings(prefs, inspection); + GenerateDeclarations(prefs, inspection); GenerateResources(inspection); PatchModules(); return true; } - private Configuration ResolveConfiguration () + private Preferences ResolvePreferences () { - var resolver = new ConfigurationResolver(EntryAssemblyName); + var resolver = new PreferencesResolver(EntryAssemblyName); return resolver.Resolve(InspectedDirectory); } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 91819f47..54429c21 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.2 + 0.2.0-alpha.5 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From d43efb3e92d9eacc8d7672663c8244daa1b42331 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 04:10:37 +0300 Subject: [PATCH 58/75] iteration --- .../Emit/DependenciesTest.cs | 11 +- .../Pack/BindingTest.cs | 38 +++---- .../Emit/DependencyGenerator.cs | 10 +- .../Pack/BindingGenerator.cs | 2 +- src/cs/Directory.Build.props | 2 +- src/js/src/exports.ts | 2 +- src/js/test/cs.mjs | 4 +- src/js/test/spec/boot.spec.mjs | 14 +-- src/js/test/spec/interop.spec.mjs | 100 +++++++++--------- src/js/test/spec/platform.spec.mjs | 6 +- 10 files changed, 96 insertions(+), 93 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs index 2e08c6e5..06465edd 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs @@ -43,17 +43,18 @@ public void AddsGeneratedImportTypes () [Fact] public void AddsClassesWithInteropMethods () { - AddAssembly("Assembly.dll", + AddAssembly("Assembly.With.Dots.dll", With("SpaceA", "public class ClassA { [JSInvokable] public static void Foo () {} }"), With("SpaceB.SpaceC", "public class ClassB { [JSFunction] public static void Foo () {} }"), With("public class ClassC { [JSEvent] public static void Foo () {} }")); Execute(); - Added(All, "SpaceA.ClassA", "Assembly"); - Added(All, "SpaceB.SpaceC.ClassB", "Assembly"); - Added(All, "ClassC", "Assembly"); + Added(All, "SpaceA.ClassA", "Assembly.With.Dots"); + Added(All, "SpaceB.SpaceC.ClassB", "Assembly.With.Dots"); + Added(All, "ClassC", "Assembly.With.Dots"); } - private void Added (DynamicallyAccessedMemberTypes types, string name) => Added(types, name, Task.EntryAssemblyName); + private void Added (DynamicallyAccessedMemberTypes types, string name) => + Added(types, name, Path.GetFileNameWithoutExtension(Task.EntryAssemblyName)); private void Added (DynamicallyAccessedMemberTypes types, string name, string assembly) { diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 9e763c17..c2027b40 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -36,7 +36,7 @@ public void BindingForInvokableMethodIsGenerated () export const Foo = { Bar: { Class: { - nya: () => getExports().Foo_Bar_Class.Nya() + nya: () => getExports().Foo_Bar_Class_Nya() } } }; @@ -94,7 +94,7 @@ public void LibraryExportsNamespaceObject () """ export const Foo = { Class: { - bar: () => getExports().Foo_Class.Bar() + bar: () => getExports().Foo_Class_Bar() } }; """); @@ -111,7 +111,7 @@ public void WhenSpaceContainDotsObjectCreatedForEachPart () Bar: { Nya: { Class: { - bar: () => getExports().Foo_Bar_Nya_Class.Bar() + bar: () => getExports().Foo_Bar_Nya_Class_Bar() } } } @@ -139,7 +139,7 @@ public void WhenMultipleSpacesEachGetItsOwnObject () }; export const Foo = { Class: { - foo: () => getExports().Foo_Class.Foo() + foo: () => getExports().Foo_Class_Foo() } }; """); @@ -152,7 +152,7 @@ public void WhenMultipleAssembliesWithEqualSpaceObjectDeclaredOnlyOnce () AddAssembly(WithClass("Foo", "[JSFunction] public static void Fun () {}")); Execute(); Assert.Single(Matches("export const Foo")); - Contains("bar: () => getExports().Foo_Class.Bar()"); + Contains("bar: () => getExports().Foo_Class_Bar()"); Contains( """ get fun() { return this.funHandler; }, @@ -180,7 +180,7 @@ public void DifferentSpacesWithSameRootAssignedUnderSameObject () }, Foo: { Class: { - foo: () => getExports().Nya_Foo_Class.Foo() + foo: () => getExports().Nya_Foo_Class_Foo() } } }; @@ -199,13 +199,13 @@ public void DifferentSpacesStartingEquallyAreNotAssignedToSameObject () """ export const Foo = { Class: { - method: () => getExports().Foo_Class.Method() + method: () => getExports().Foo_Class_Method() } }; export const FooBar = { Baz: { Class: { - method: () => getExports().FooBar_Baz_Class.Method() + method: () => getExports().FooBar_Baz_Class_Method() } } }; @@ -231,7 +231,7 @@ public void BindingsFromMultipleSpacesAssignedToRespectiveObjects () }; export const Foo = { Class: { - foo: () => getExports().Foo_Class.Foo() + foo: () => getExports().Foo_Class_Foo() } }; """); @@ -248,7 +248,7 @@ public void BindingsFromMultipleClassesAssignedToRespectiveObjects () """ export const Global = { ClassA: { - inv: () => getExports().ClassA.Inv() + inv: () => getExports().ClassA_Inv() }, ClassB: { get fun() { return this.funHandler; }, @@ -270,7 +270,7 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () """ export const Global = { Class: { - nya: () => getExports().Class.Nya(), + nya: () => getExports().Class_Nya(), get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Global.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } @@ -288,7 +288,7 @@ public void VariablesConflictingWithJSTypesAreRenamed () """ export const Global = { Class: { - fun: (fn) => getExports().Class.Fun(fn) + fun: (fn) => getExports().Class_Fun(fn) } }; """); @@ -308,7 +308,7 @@ public void SerializesCustomType () """ export const Global = { Class: { - foo: (i) => deserialize(getExports().Class.Foo(serialize(i))), + foo: (i) => deserialize(getExports().Class_Foo(serialize(i))), get bar() { return this.barHandler; }, set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i))); }, get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, @@ -335,11 +335,11 @@ public void AwaitsWhenSerializingInAsyncFunctions () """ export const Global = { Class: { - foo: async (i) => deserialize(await getExports().Class.Foo(serialize(i))), + foo: async (i) => deserialize(await getExports().Class_Foo(serialize(i))), get bar() { return this.barHandler; }, set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i))); }, get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, - baz: async () => deserialize(await getExports().Class.Baz()), + baz: async () => deserialize(await getExports().Class_Baz()), get yaz() { return this.yazHandler; }, set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler()); }, get yazSerialized() { if (typeof this.yazHandler !== "function") throw Error("Failed to invoke 'Global.Class.yaz' from C#. Make sure to assign function in JavaScript."); return this.yazSerializedHandler; } @@ -359,7 +359,7 @@ public void ExportedEnumsAreDeclaredInJS () """ export const n = { Class: { - getFoo: () => deserialize(getExports().n_Class.GetFoo()), + getFoo: () => deserialize(getExports().n_Class_GetFoo()), Foo: { "0": "A", "1": "B", "A": 0, "B": 1 } @@ -379,7 +379,7 @@ public void CustomEnumIndexesArePreservedInJS () """ export const n = { Class: { - getFoo: () => deserialize(getExports().n_Class.GetFoo()), + getFoo: () => deserialize(getExports().n_Class_GetFoo()), Foo: { "1": "A", "6": "B", "A": 1, "B": 6 } @@ -414,7 +414,7 @@ public class Prefs : Bootsharp.Preferences }; export const Nya = { Class: { - getNya: () => getExports().Foo_Bar_Nya_Class.GetNya() + getNya: () => getExports().Foo_Bar_Nya_Class_GetNya() } }; """); @@ -441,7 +441,7 @@ public override MethodMeta ResolveMethod (System.Reflection.MethodInfo _, Method """ export const Space = { Class: { - foo: () => getExports().Space_Class.Inv() + foo: () => getExports().Space_Class_Inv() } }; """); diff --git a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index f9d6ed8c..4c6d3a1e 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -33,15 +33,17 @@ internal static void RegisterDynamicDependencies () { } private void AddGeneratedCommon () { - Add(All, "Bootsharp.Generated.Dependencies", entryAssembly); - Add(All, "Bootsharp.Generated.SerializerContext", entryAssembly); - Add(All, "Bootsharp.Generated.Interop", entryAssembly); + var asm = Path.GetFileNameWithoutExtension(entryAssembly); + Add(All, "Bootsharp.Generated.Dependencies", asm); + Add(All, "Bootsharp.Generated.SerializerContext", asm); + Add(All, "Bootsharp.Generated.Interop", asm); } private void AddGeneratedInteropClasses (AssemblyInspection inspection) { + var asm = Path.GetFileNameWithoutExtension(entryAssembly); foreach (var inter in inspection.Interfaces) - Add(All, inter.FullName, entryAssembly); + Add(All, inter.FullName, asm); } private void AddClassesWithInteropMethods (AssemblyInspection inspection) diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 71df1291..04f5b7a8 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -99,7 +99,7 @@ private void EmitMethod (MethodMeta method) private void EmitInvokable (MethodMeta method) { var wait = ShouldWait(method); - var endpoint = $"getExports().{method.Space.Replace('.', '_')}.{method.Name}"; + var endpoint = $"getExports().{method.Space.Replace('.', '_')}_{method.Name}"; var funcArgs = string.Join(", ", method.Arguments.Select(a => a.JSName)); var invArgs = string.Join(", ", method.Arguments.Select(arg => arg.Value.Serialized ? $"serialize({arg.JSName})" : arg.JSName diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 54429c21..b278291a 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.5 + 0.2.0-alpha.10 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/src/exports.ts b/src/js/src/exports.ts index c9ab1d72..853e3fef 100644 --- a/src/js/src/exports.ts +++ b/src/js/src/exports.ts @@ -4,5 +4,5 @@ export let exports: unknown; export async function bindExports(runtime: RuntimeAPI, assembly: string) { const asm = await runtime.getAssemblyExports(assembly); - exports = asm["Bootsharp"]?.["Exports"]; + exports = asm["Bootsharp"]["Generated"]["Interop"]; } diff --git a/src/js/test/cs.mjs b/src/js/test/cs.mjs index e080a401..900c829f 100644 --- a/src/js/test/cs.mjs +++ b/src/js/test/cs.mjs @@ -23,12 +23,12 @@ export const bins = { }; export async function bootEmbedded() { - EmbeddedTest.onMainInvoked = () => {}; + EmbeddedTest.Program.onMainInvoked = () => {}; await embedded.boot({}); } export async function bootSideload() { - SideloadTest.onMainInvoked = () => {}; + SideloadTest.Program.onMainInvoked = () => {}; await sideload.boot({ root }); } diff --git a/src/js/test/spec/boot.spec.mjs b/src/js/test/spec/boot.spec.mjs index 1c70d00e..efc590a2 100644 --- a/src/js/test/spec/boot.spec.mjs +++ b/src/js/test/spec/boot.spec.mjs @@ -7,7 +7,7 @@ async function setup() { // this is a workaround to simulate clean environment in each test vi.resetModules(); const cs = await import("../cs.mjs"); - cs.SideloadTest.onMainInvoked = vi.fn(); + cs.SideloadTest.Program.onMainInvoked = vi.fn(); return { ...cs, bootsharp: cs.sideload, Test: cs.SideloadTest }; } @@ -45,14 +45,14 @@ describe("boot", () => { it("can boot in embedded mode", async () => { vi.resetModules(); const cs = await import("../cs.mjs"); - cs.EmbeddedTest.onMainInvoked = vi.fn(); + cs.EmbeddedTest.Program.onMainInvoked = vi.fn(); await cs.embedded.boot({}); - expect(cs.EmbeddedTest.onMainInvoked).toHaveBeenCalledOnce(); + expect(cs.EmbeddedTest.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot while streaming bins from root", async () => { const { bootsharp, root, Test } = await setup(); await bootsharp.boot({ root }); - expect(Test.onMainInvoked).toHaveBeenCalledOnce(); + expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with bins content pre-assigned", async () => { const { bootsharp, Test, root, bins } = await setup(); @@ -61,7 +61,7 @@ describe("boot", () => { for (const asm of resources.assemblies) asm.content = bins.assemblies.find(a => a.name === asm.name).content; await bootsharp.boot({ resources, root }); - expect(Test.onMainInvoked).toHaveBeenCalledOnce(); + expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with base64 content", async () => { const { bootsharp, Test, root, bins } = await setup(); @@ -70,7 +70,7 @@ describe("boot", () => { for (const asm of resources.assemblies) asm.content = bins.assemblies.find(a => a.name === asm.name).content.toString("base64"); await bootsharp.boot({ resources, root }); - expect(Test.onMainInvoked).toHaveBeenCalledOnce(); + expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with base64 content w/o native encoder available", async () => { const { bootsharp, Test, root, bins } = await setup(); @@ -80,7 +80,7 @@ describe("boot", () => { for (const asm of resources.assemblies) asm.content = bins.assemblies.find(a => a.name === asm.name).content.toString("base64"); await bootsharp.boot({ resources, root }); - expect(Test.onMainInvoked).toHaveBeenCalledOnce(); + expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("attempts to use atob when window is defined in global", async () => { const { bootsharp, root, bins } = await setup(); diff --git a/src/js/test/spec/interop.spec.mjs b/src/js/test/spec/interop.spec.mjs index 27fdda08..2956a290 100644 --- a/src/js/test/spec/interop.spec.mjs +++ b/src/js/test/spec/interop.spec.mjs @@ -5,7 +5,7 @@ const TrackType = Test.Types.TrackType; describe("while bootsharp is not booted", () => { it("throws when attempting to invoke C# APIs", () => { - expect(Test.invokeVoid).throw(/Boot the runtime before invoking C# APIs/); + expect(Test.Invokable.invokeVoid).throw(/Boot the runtime before invoking C# APIs/); }); }); @@ -13,53 +13,53 @@ describe("while bootsharp is booted", () => { beforeAll(bootSideload); it("throws when invoking un-assigned JS function from C#", () => { const error = /Failed to invoke '.+' from C#. Make sure to assign function in JavaScript/; - Test.onMainInvoked = undefined; - expect(Test.getBytes).toBeUndefined(); - expect(Test.getString).toBeUndefined(); - expect(Test.getStringAsync).toBeUndefined(); - expect(Test.throwJS).toBeUndefined(); - expect(Test.onMainInvoked).toBeUndefined(); - expect(Test.Types.getRegistry).toBeUndefined(); - expect(Test.Types.getRegistries).toBeUndefined(); - expect(Test.Types.getRegistryMap).toBeUndefined(); - expect(() => Test.getStringSerialized()).throw(error); - expect(() => Test.getStringAsyncSerialized()).throw(error); - expect(() => Test.getBytesSerialized()).throw(error); - expect(() => Test.throwJSSerialized()).throw(error); - expect(() => Test.onMainInvokedSerialized()).throw(error); - expect(() => Test.Types.getRegistrySerialized()).throw(error); - expect(() => Test.Types.getRegistriesSerialized()).throw(error); - expect(() => Test.Types.getRegistryMapSerialized()).throw(error); + Test.Program.onMainInvoked = undefined; + expect(Test.Functions.getBytes).toBeUndefined(); + expect(Test.Functions.getString).toBeUndefined(); + expect(Test.Functions.getStringAsync).toBeUndefined(); + expect(Test.Platform.throwJS).toBeUndefined(); + expect(Test.Program.onMainInvoked).toBeUndefined(); + expect(Test.Types.Registry.getRegistry).toBeUndefined(); + expect(Test.Types.Registry.getRegistries).toBeUndefined(); + expect(Test.Types.Registry.getRegistryMap).toBeUndefined(); + expect(() => Test.Functions.getStringSerialized()).throw(error); + expect(() => Test.Functions.getStringAsyncSerialized()).throw(error); + expect(() => Test.Functions.getBytesSerialized()).throw(error); + expect(() => Test.Platform.throwJSSerialized()).throw(error); + expect(() => Test.Program.onMainInvokedSerialized()).throw(error); + expect(() => Test.Types.Registry.getRegistrySerialized()).throw(error); + expect(() => Test.Types.Registry.getRegistriesSerialized()).throw(error); + expect(() => Test.Types.Registry.getRegistryMapSerialized()).throw(error); }); it("can invoke C# method", async () => { - expect(Test.joinStrings("foo", "bar")).toStrictEqual("foobar"); + expect(Test.Invokable.joinStrings("foo", "bar")).toStrictEqual("foobar"); }); it("can invoke async C# method", async () => { - expect(await Test.joinStringsAsync("foo", "bar")).toStrictEqual("foobar"); + expect(await Test.Invokable.joinStringsAsync("foo", "bar")).toStrictEqual("foobar"); }); it("can transfer strings", () => { - Test.getString = () => "foo"; - expect(Test.echoString()).toStrictEqual("foo"); + Test.Functions.getString = () => "foo"; + expect(Test.Functions.echoString()).toStrictEqual("foo"); }); it("can transfer decimals", () => { - expect(Test.sumDoubles(-1, 2.75)).toStrictEqual(1.75); + expect(Test.Invokable.sumDoubles(-1, 2.75)).toStrictEqual(1.75); }); it("can transfer dates", () => { const date = new Date(1977, 3, 2); const expected = new Date(1977, 3, 9); - const actual = new Date(Test.addDays(date, 7)); + const actual = new Date(Test.Invokable.addDays(date, 7)); expect(actual).toStrictEqual(expected); }); it("can transfer byte array", () => { - Test.getBytes = () => new Uint8Array([ + Test.Functions.getBytes = () => new Uint8Array([ 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79, 0x2c, 0x20, 0x43, 0x61, 0x70, 0x74, 0x61, 0x69, 0x6e, 0x2e, 0x20, 0x4e, 0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e ]); - const echo = Test.echoBytes(); - expect(Test.bytesToString(echo)).toStrictEqual("Everything's shiny, Captain. Not to fret."); + const echo = Test.Functions.echoBytes(); + expect(Test.Invokable.bytesToString(echo)).toStrictEqual("Everything's shiny, Captain. Not to fret."); }); it("can transfer structs", () => { const expected = { @@ -72,12 +72,12 @@ describe("while bootsharp is booted", () => { { id: "tractor", trackType: TrackType.Rubber, maxSpeed: 15.9 } ] }; - const actual = Test.Types.echoRegistry(expected); + const actual = Test.Types.Registry.echoRegistry(expected); expect(actual).toStrictEqual(expected); }); it("can transfer lists as arrays", async () => { - Test.Types.getRegistries = () => [{ wheeled: [{ id: "foo", maxSpeed: 1, wheelCount: 0 }] }]; - const result = await Test.Types.concatRegistriesAsync([ + Test.Types.Registry.getRegistries = () => [{ wheeled: [{ id: "foo", maxSpeed: 1, wheelCount: 0 }] }]; + const result = await Test.Types.Registry.concatRegistriesAsync([ { wheeled: [{ id: "bar", maxSpeed: 1, wheelCount: 9 }] }, { tracked: [{ id: "baz", maxSpeed: 5, trackType: TrackType.Rubber }] } ]); @@ -90,11 +90,11 @@ describe("while bootsharp is booted", () => { it("can transfer dictionaries as maps", async () => { // ES6 Map doesn't natively support JSON serialization, so using plain objects. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map - Test.Types.getRegistryMap = () => ({ + Test.Types.Registry.getRegistryMap = () => ({ foo: { wheeled: [{ id: "foo", maxSpeed: 1, wheelCount: 0 }] }, bar: { wheeled: [{ id: "bar", maxSpeed: 15, wheelCount: 5 }] } }); - const result = await Test.Types.mapRegistriesAsync({ + const result = await Test.Types.Registry.mapRegistriesAsync({ baz: { tracked: [{ id: "baz", maxSpeed: 5, trackType: TrackType.Rubber }] } }); expect(result).toStrictEqual({ @@ -104,23 +104,23 @@ describe("while bootsharp is booted", () => { }); }); it("can invoke assigned JS functions in C#", () => { - Test.Types.getRegistry = () => ({ wheeled: [{ maxSpeed: 1 }], tracked: [{ maxSpeed: 2 }] }); - expect(Test.Types.countTotalSpeed()).toStrictEqual(3); + Test.Types.Registry.getRegistry = () => ({ wheeled: [{ maxSpeed: 1 }], tracked: [{ maxSpeed: 2 }] }); + expect(Test.Types.Registry.countTotalSpeed()).toStrictEqual(3); }); it("can subscribe to events", () => { let eventArg1, multipleArg1, multipleArg2, multipleArg3; - Test.onEvent.subscribe(v => eventArg1 = v); - Test.onEventMultiple.subscribe((a1, a2, a3) => { + Test.Event.onEvent.subscribe(v => eventArg1 = v); + Test.Event.onEventMultiple.subscribe((a1, a2, a3) => { multipleArg1 = a1; multipleArg2 = a2; multipleArg3 = a3; }); - Test.broadcastEvent("foo"); - Test.broadcastEventMultiple(1, { id: "foo", maxSpeed: 50 }, TrackType.Rubber); + Test.Event.broadcastEvent("foo"); + Test.Event.broadcastEventMultiple(1, { id: "foo", maxSpeed: 50 }, TrackType.Rubber); expect(multipleArg1).toStrictEqual(1); expect(multipleArg2).toStrictEqual({ id: "foo", maxSpeed: 50 }); expect(multipleArg3).toStrictEqual(TrackType.Rubber); - Test.broadcastEventMultiple(255, undefined, TrackType.Chain); + Test.Event.broadcastEventMultiple(255, undefined, TrackType.Chain); expect(multipleArg1).toStrictEqual(255); expect(multipleArg2).toBeUndefined(); expect(multipleArg3).toStrictEqual(TrackType.Chain); @@ -128,25 +128,25 @@ describe("while bootsharp is booted", () => { it("can un-subscribe from events", () => { let result = ""; const assigner = v => result = v; - Test.onEvent.subscribe(assigner); - Test.broadcastEvent("foo"); - Test.onEvent.unsubscribe(assigner); - Test.broadcastEvent("bar"); + Test.Event.onEvent.subscribe(assigner); + Test.Event.broadcastEvent("foo"); + Test.Event.onEvent.unsubscribe(assigner); + Test.Event.broadcastEvent("bar"); expect(result).toStrictEqual("foo"); }); it("can catch js exception", () => { - Test.throwJS = function () { throw new Error("foo"); }; - expect(Test.catchException().split("\n")[0]).toStrictEqual("Error: foo"); + Test.Platform.throwJS = function () { throw new Error("foo"); }; + expect(Test.Platform.catchException().split("\n")[0]).toStrictEqual("Error: foo"); }); it("can catch dotnet exceptions", () => { - expect(() => Test.throwCS("bar")).throw("bar"); + expect(() => Test.Platform.throwCS("bar")).throw("bar"); }); it("can invoke async method with async js callback", async () => { - Test.getStringAsync = async () => { + Test.Functions.getStringAsync = async () => { await new Promise(res => setTimeout(res, 100)); return "foo"; }; - expect(await Test.echoStringAsync()).toStrictEqual("foo"); + expect(await Test.Functions.echoStringAsync()).toStrictEqual("foo"); }); it("maps enums by both indexes and strings", () => { expect(TrackType[0]).toStrictEqual("Rubber"); @@ -155,7 +155,7 @@ describe("while bootsharp is booted", () => { expect(TrackType[TrackType.Chain]).toStrictEqual("Chain"); }); it("can compare indexed enums", () => { - expect(Test.getIdxEnumOne() === Test.IdxEnum.One).toBeTruthy(); - expect(Test.getIdxEnumOne() === Test.IdxEnum.Two).not.toBeTruthy(); + expect(Test.Invokable.getIdxEnumOne() === Test.IdxEnum.One).toBeTruthy(); + expect(Test.Invokable.getIdxEnumOne() === Test.IdxEnum.Two).not.toBeTruthy(); }); }); diff --git a/src/js/test/spec/platform.spec.mjs b/src/js/test/spec/platform.spec.mjs index 219f2fc3..ce439a36 100644 --- a/src/js/test/spec/platform.spec.mjs +++ b/src/js/test/spec/platform.spec.mjs @@ -5,8 +5,8 @@ import { WebSocket, WebSocketServer } from "ws"; describe("platform", () => { beforeAll(bootSideload); it("can provide unique guid", () => { - const guid1 = Test.getGuid(); - const guid2 = Test.getGuid(); + const guid1 = Test.Platform.getGuid(); + const guid2 = Test.Platform.getGuid(); expect(guid1.length).toStrictEqual(36); expect(guid2.length).toStrictEqual(36); expect(guid1).not.toStrictEqual(guid2); @@ -17,7 +17,7 @@ describe("platform", () => { global.WebSocket = WebSocket; const wss = new WebSocketServer({ port: 8877 }); wss.on("connection", socket => socket.on("message", socket.send)); - expect(await Test.echoWebSocket("ws://localhost:8877", "foo", 3000)).toStrictEqual("foo"); + expect(await Test.Platform.echoWebSocket("ws://localhost:8877", "foo", 3000)).toStrictEqual("foo"); wss.close(); }); }); From f2dd7ec31c1d336a35719dc3580d5dd01872c396 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 06:09:00 +0300 Subject: [PATCH 59/75] iteration --- src/cs/Bootsharp/Build/Bootsharp.targets | 6 +- src/cs/Bootsharp/Build/PackageTemplate.json | 4 +- src/cs/Directory.Build.props | 2 +- src/js/package.json | 7 +- src/js/src/config.ts | 3 + src/js/src/{bootsharp.ts => index.ts} | 4 +- src/js/test/{cs.mjs => cs.ts} | 19 ++++-- src/js/test/cs/Test/Test.csproj | 2 +- .../test/spec/{boot.spec.mjs => boot.spec.ts} | 67 ++++++++++--------- .../spec/{event.spec.mjs => event.spec.ts} | 14 ++-- .../spec/{export.spec.mjs => export.spec.ts} | 2 +- .../{interop.spec.mjs => interop.spec.ts} | 37 +++++----- .../{platform.spec.mjs => platform.spec.ts} | 4 +- src/js/tsconfig.json | 3 +- 14 files changed, 97 insertions(+), 77 deletions(-) rename src/js/src/{bootsharp.ts => index.ts} (72%) rename src/js/test/{cs.mjs => cs.ts} (78%) rename src/js/test/spec/{boot.spec.mjs => boot.spec.ts} (71%) rename src/js/test/spec/{event.spec.mjs => event.spec.ts} (89%) rename src/js/test/spec/{export.spec.mjs => export.spec.ts} (92%) rename src/js/test/spec/{interop.spec.mjs => interop.spec.ts} (83%) rename src/js/test/spec/{platform.spec.mjs => platform.spec.ts} (90%) diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index da68136f..f4bc7f8c 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -94,7 +94,7 @@ $(BootsharpPublishDirectory)/types $(BootsharpPublishDirectory)/bin $(BootsharpPublishDirectory) - npx rollup bootsharp.js -o $(BootsharpName).mjs -f es -g process,module --output.inlineDynamicImports + npx rollup index.js -o index.mjs -f es -g process,module --output.inlineDynamicImports true false false @@ -105,8 +105,8 @@
- + - 0.2.0-alpha.10 + 0.2.0-alpha.14 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/package.json b/src/js/package.json index 7f988c5b..7c60dcd3 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -8,9 +8,10 @@ }, "devDependencies": { "typescript": "^5.3.3", - "vitest": "^1.1.3", - "@vitest/coverage-v8": "^1.1.3", - "@types/node": "^20.10.6", + "@types/node": "^20.11.5", + "@types/ws": "^8.5.10", + "vitest": "^1.2.1", + "@vitest/coverage-v8": "^1.2.1", "ws": "^8.16.0" } } diff --git a/src/js/src/config.ts b/src/js/src/config.ts index 024d11ba..2b67ba98 100644 --- a/src/js/src/config.ts +++ b/src/js/src/config.ts @@ -2,6 +2,9 @@ import { RuntimeConfig, AssetEntry, AssetBehaviors, getRuntime, getNative, getMa import { BinaryResource, BootResources } from "./resources"; import { decodeBase64 } from "./decoder"; +/** Builds .NET runtime configuration. + * @param resources Resources required for runtime initialization. + * @param root When specified, assumes boot resources are side-loaded from the specified root. */ export async function buildConfig(resources: BootResources, root?: string): Promise { const embed = root == null; const main = embed ? await getMain() : undefined; diff --git a/src/js/src/bootsharp.ts b/src/js/src/index.ts similarity index 72% rename from src/js/src/bootsharp.ts rename to src/js/src/index.ts index feaa4e02..ec6a98c8 100644 --- a/src/js/src/bootsharp.ts +++ b/src/js/src/index.ts @@ -1,6 +1,7 @@ import { boot, exit, getStatus, BootStatus } from "./boot"; import { getMain, getNative, getRuntime } from "./modules"; import { resources } from "./resources"; +import { buildConfig } from "./config"; export default { boot, @@ -8,7 +9,8 @@ export default { getStatus, BootStatus, resources, - dotnet: { getMain, getNative, getRuntime } + /** .NET internal modules and associated utilities. */ + dotnet: { getMain, getNative, getRuntime, buildConfig } }; export * from "./event"; diff --git a/src/js/test/cs.mjs b/src/js/test/cs.ts similarity index 78% rename from src/js/test/cs.mjs rename to src/js/test/cs.ts index 900c829f..70dc95c6 100644 --- a/src/js/test/cs.mjs +++ b/src/js/test/cs.ts @@ -1,5 +1,5 @@ -import emb, { Test as EmbTest } from "./cs/Test/bin/embedded/bootsharp.mjs"; -import sid, { Test as SidTest } from "./cs/Test/bin/sideload/bootsharp.mjs"; +import emb, { Test as EmbTest } from "./cs/Test/bin/embedded"; +import sid, { Test as SidTest } from "./cs/Test/bin/sideload"; import assert from "node:assert"; import { resolve, parse, basename } from "node:path"; import { readdirSync, readFileSync, existsSync } from "node:fs"; @@ -11,10 +11,10 @@ export const EmbeddedTest = EmbTest; export const SideloadTest = SidTest; export const root = pathToFileURL("./test/cs/Test/bin/sideload/bin").toString(); -export * from "./cs/Test/bin/sideload/bootsharp.mjs"; +export * from "./cs/Test/bin/sideload"; -assertPathExists("test/cs/Test/bin/embedded/bootsharp.mjs"); -assertPathExists("test/cs/Test/bin/sideload/bootsharp.mjs"); +assertPathExists("test/cs/Test/bin/embedded/index.mjs"); +assertPathExists("test/cs/Test/bin/sideload/index.mjs"); export const bins = { wasm: loadWasmBinary(), @@ -38,6 +38,11 @@ export function getDeclarations() { return readFileSync(file).toString(); } +// Just casting to triggers codefactor. +export function any(obj: unknown): Record { + return >obj; +} + function loadWasmBinary() { const file = resolve("test/cs/Test/bin/sideload/bin/dotnet.native.wasm"); assertPathExists(file); @@ -61,14 +66,14 @@ function findAssemblies() { return assemblyPaths; } -function loadAssembly(assemblyPath) { +function loadAssembly(assemblyPath: string) { return { name: parse(assemblyPath).base, content: readFileSync(assemblyPath) }; } -function assertPathExists(pathToCheck) { +function assertPathExists(pathToCheck: string) { const name = basename(pathToCheck); assert(existsSync(pathToCheck), `Missing test project artifact: '${name}'. Run 'scripts/compile-test.sh'.`); } diff --git a/src/js/test/cs/Test/Test.csproj b/src/js/test/cs/Test/Test.csproj index 7e222cd6..4ffbd8a1 100644 --- a/src/js/test/cs/Test/Test.csproj +++ b/src/js/test/cs/Test/Test.csproj @@ -7,7 +7,7 @@ true bin/codegen false - npx rollup bootsharp.js -d ./ -f es -g process,module --output.preserveModules --entryFileNames [name].mjs + npx rollup index.js -d ./ -f es -g process,module --output.preserveModules --entryFileNames [name].mjs true true diff --git a/src/js/test/spec/boot.spec.mjs b/src/js/test/spec/boot.spec.ts similarity index 71% rename from src/js/test/spec/boot.spec.mjs rename to src/js/test/spec/boot.spec.ts index efc590a2..69c961dc 100644 --- a/src/js/test/spec/boot.spec.mjs +++ b/src/js/test/spec/boot.spec.ts @@ -1,12 +1,13 @@ import { describe, expect, it, vi } from "vitest"; import { resolve } from "node:path"; +import type { BootOptions } from "../cs/Test/bin/sideload"; async function setup() { // dotnet merges with the host node process, so it's not possible // to exit w/o killing the test process (which is bound to test file); // this is a workaround to simulate clean environment in each test vi.resetModules(); - const cs = await import("../cs.mjs"); + const cs = await import("../cs"); cs.SideloadTest.Program.onMainInvoked = vi.fn(); return { ...cs, bootsharp: cs.sideload, Test: cs.SideloadTest }; } @@ -26,25 +27,25 @@ describe("boot", () => { }); it("defines module exports when root is not specified", async () => { const { bootsharp } = await setup(); - const module = await import("../cs/Test/bin/sideload/config"); - const config = await module.buildConfig(bootsharp.resources); - expect(config.assets[0].moduleExports).toBeDefined(); - expect(config.assets[1].moduleExports).toBeDefined(); - expect(config.assets[2].moduleExports).toBeDefined(); + const module = await import("../cs/Test/bin/sideload"); + const config = await module.default.dotnet.buildConfig(bootsharp.resources); + expect(config.assets![0].moduleExports).toBeDefined(); + expect(config.assets![1].moduleExports).toBeDefined(); + expect(config.assets![2].moduleExports).toBeDefined(); }); it("overrides name to url in multithreading mode", async () => { const { bootsharp, root } = await setup(); vi.doMock("../cs/Test/bin/sideload/dotnet.g", () => ({ mt: true })); - const module = await import("../cs/Test/bin/sideload/config"); - const config = await module.buildConfig(bootsharp.resources, root); - expect(config.assets[0].name.endsWith("/bin/dotnet.js")).toBeTruthy(); - expect(config.assets[1].name.endsWith("/bin/dotnet.native.js")).toBeTruthy(); - expect(config.assets[2].name.endsWith("/bin/dotnet.runtime.js")).toBeTruthy(); + const module = await import("../cs/Test/bin/sideload"); + const config = await module.default.dotnet.buildConfig(bootsharp.resources, root); + expect(config.assets![0].name.endsWith("/bin/dotnet.js")).toBeTruthy(); + expect(config.assets![1].name.endsWith("/bin/dotnet.native.js")).toBeTruthy(); + expect(config.assets![2].name.endsWith("/bin/dotnet.runtime.js")).toBeTruthy(); vi.doUnmock("../cs/Test/bin/sideload/dotnet.g"); }); it("can boot in embedded mode", async () => { vi.resetModules(); - const cs = await import("../cs.mjs"); + const cs = await import("../cs"); cs.EmbeddedTest.Program.onMainInvoked = vi.fn(); await cs.embedded.boot({}); expect(cs.EmbeddedTest.Program.onMainInvoked).toHaveBeenCalledOnce(); @@ -55,41 +56,40 @@ describe("boot", () => { expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with bins content pre-assigned", async () => { - const { bootsharp, Test, root, bins } = await setup(); + const { bootsharp, Test, root, bins, any } = await setup(); const resources = { ...bootsharp.resources }; - resources.wasm.content = bins.wasm; + any(resources.wasm).content = bins.wasm; for (const asm of resources.assemblies) - asm.content = bins.assemblies.find(a => a.name === asm.name).content; + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content; await bootsharp.boot({ resources, root }); expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with base64 content", async () => { - const { bootsharp, Test, root, bins } = await setup(); + const { bootsharp, Test, root, bins, any } = await setup(); const resources = { ...bootsharp.resources }; - resources.wasm.content = bins.wasm.toString("base64"); + any(resources.wasm).content = bins.wasm.toString("base64"); for (const asm of resources.assemblies) - asm.content = bins.assemblies.find(a => a.name === asm.name).content.toString("base64"); + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); await bootsharp.boot({ resources, root }); expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with base64 content w/o native encoder available", async () => { - const { bootsharp, Test, root, bins } = await setup(); - global.Buffer = undefined; + const { bootsharp, Test, root, bins, any } = await setup(); + any(global).Buffer = undefined; const resources = { ...bootsharp.resources }; - resources.wasm.content = bins.wasm.toString("base64"); + any(resources.wasm).content = bins.wasm.toString("base64"); for (const asm of resources.assemblies) - asm.content = bins.assemblies.find(a => a.name === asm.name).content.toString("base64"); + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); await bootsharp.boot({ resources, root }); expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("attempts to use atob when window is defined in global", async () => { - const { bootsharp, root, bins } = await setup(); - // noinspection JSValidateTypes - global.window = { atob: vi.fn() }; + const { bootsharp, root, bins, any } = await setup(); + any(global).window = { atob: vi.fn() }; const resources = { ...bootsharp.resources }; - resources.wasm.content = bins.wasm.toString("base64"); + any(resources.wasm).content = bins.wasm.toString("base64"); for (const asm of resources.assemblies) - asm.content = bins.assemblies.find(a => a.name === asm.name).content.toString("base64"); + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); try { await bootsharp.boot({ resources, root }); } catch {} expect(global.window.atob).toHaveBeenCalledOnce(); @@ -116,8 +116,8 @@ describe("boot", () => { expect(bootsharp.getStatus()).toStrictEqual(0); }); it("respects boot customs", async () => { - const { bootsharp, bins } = await setup(); - const customs = { + const { bootsharp, bins, root } = await setup(); + const customs: BootOptions = { config: { mainAssemblyName: bins.entryAssemblyName, assets: [ @@ -138,10 +138,15 @@ describe("boot", () => { buffer: bins.wasm, behavior: "dotnetwasm" }, - ...bins.assemblies.map(a => ({ name: a.name, buffer: a.content, behavior: "assembly" })) + ...bins.assemblies.map(a => ({ name: a.name, buffer: a.content, behavior: "assembly" })) ] }, - create: vi.fn(async () => (await import("../cs/Test/bin/sideload/bin/dotnet.js")).dotnet.withConfig(customs.config).create()), + create: vi.fn(async () => { + const bootsharp = (await import("../cs/Test/bin/sideload")).default; + const dotnet = (await bootsharp.dotnet.getMain(root)).dotnet; + console.log(dotnet); + return await dotnet.withConfig(customs.config).create(); + }), import: vi.fn(), run: vi.fn(), export: vi.fn() diff --git a/src/js/test/spec/event.spec.mjs b/src/js/test/spec/event.spec.ts similarity index 89% rename from src/js/test/spec/event.spec.mjs rename to src/js/test/spec/event.spec.ts index d70e7c95..ddfbedda 100644 --- a/src/js/test/spec/event.spec.mjs +++ b/src/js/test/spec/event.spec.ts @@ -1,23 +1,23 @@ import { describe, expect, it } from "vitest"; -import { Event } from "../cs.mjs"; +import { Event } from "../cs"; describe("event", () => { it("can broadcast without subscribers", () => { new Event().broadcast(); }); it("doesn't mind unsubscribing null handler", () => { - new Event().unsubscribe(null); + new Event().unsubscribe(null); }); it("warns when unsubscribing handler which is not subscribed", () => { let warning; - new Event({ warn: msg => warning = msg }).unsubscribe(it); + new Event({ warn: msg => warning = msg }).unsubscribe(it); expect(warning).include("handler is not subscribed"); }); it("warns when subscribing handler which is already subscribed", () => { let warning; const event = new Event({ warn: msg => warning = msg }); - event.subscribe(it); - event.subscribe(it); + event.subscribe(it); + event.subscribe(it); expect(warning).include("handler is already subscribed"); }); it("invokes subscribed handlers in order", () => { @@ -31,7 +31,7 @@ describe("event", () => { it("doesn't invoke un-subscribed handler", () => { let result = false; const event = new Event(); - const handler = v => result = v; + const handler = (v: unknown) => result = v; event.subscribe(handler); event.broadcast(true); event.unsubscribe(handler); @@ -41,7 +41,7 @@ describe("event", () => { it("delivers broadcast argument to the handlers", () => { let result = ""; const event = new Event(); - event.subscribe(v => result = v); + event.subscribe(v => result = v); event.broadcast("foo"); expect(result).toStrictEqual("foo"); }); diff --git a/src/js/test/spec/export.spec.mjs b/src/js/test/spec/export.spec.ts similarity index 92% rename from src/js/test/spec/export.spec.mjs rename to src/js/test/spec/export.spec.ts index 8ab8e1ad..3a8eea5c 100644 --- a/src/js/test/spec/export.spec.mjs +++ b/src/js/test/spec/export.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { embedded, getDeclarations } from "../cs.mjs"; +import { embedded, getDeclarations } from "../cs"; describe("export", () => { it("exports bootsharp api", () => { diff --git a/src/js/test/spec/interop.spec.mjs b/src/js/test/spec/interop.spec.ts similarity index 83% rename from src/js/test/spec/interop.spec.mjs rename to src/js/test/spec/interop.spec.ts index 2956a290..55530b36 100644 --- a/src/js/test/spec/interop.spec.mjs +++ b/src/js/test/spec/interop.spec.ts @@ -1,5 +1,5 @@ import { describe, it, beforeAll, expect } from "vitest"; -import { bootSideload, Test } from "../cs.mjs"; +import { Test, bootSideload, any } from "../cs"; const TrackType = Test.Types.TrackType; @@ -13,7 +13,7 @@ describe("while bootsharp is booted", () => { beforeAll(bootSideload); it("throws when invoking un-assigned JS function from C#", () => { const error = /Failed to invoke '.+' from C#. Make sure to assign function in JavaScript/; - Test.Program.onMainInvoked = undefined; + any(Test.Program).onMainInvoked = undefined; expect(Test.Functions.getBytes).toBeUndefined(); expect(Test.Functions.getString).toBeUndefined(); expect(Test.Functions.getStringAsync).toBeUndefined(); @@ -22,14 +22,14 @@ describe("while bootsharp is booted", () => { expect(Test.Types.Registry.getRegistry).toBeUndefined(); expect(Test.Types.Registry.getRegistries).toBeUndefined(); expect(Test.Types.Registry.getRegistryMap).toBeUndefined(); - expect(() => Test.Functions.getStringSerialized()).throw(error); - expect(() => Test.Functions.getStringAsyncSerialized()).throw(error); - expect(() => Test.Functions.getBytesSerialized()).throw(error); - expect(() => Test.Platform.throwJSSerialized()).throw(error); - expect(() => Test.Program.onMainInvokedSerialized()).throw(error); - expect(() => Test.Types.Registry.getRegistrySerialized()).throw(error); - expect(() => Test.Types.Registry.getRegistriesSerialized()).throw(error); - expect(() => Test.Types.Registry.getRegistryMapSerialized()).throw(error); + expect(() => any<() => void>(Test.Functions).getStringSerialized()).throw(error); + expect(() => any<() => void>(Test.Functions).getStringAsyncSerialized()).throw(error); + expect(() => any<() => void>(Test.Functions).getBytesSerialized()).throw(error); + expect(() => any<() => void>(Test.Platform).throwJSSerialized()).throw(error); + expect(() => any<() => void>(Test.Program).onMainInvokedSerialized()).throw(error); + expect(() => any<() => void>(Test.Types.Registry).getRegistrySerialized()).throw(error); + expect(() => any<() => void>(Test.Types.Registry).getRegistriesSerialized()).throw(error); + expect(() => any<() => void>(Test.Types.Registry).getRegistryMapSerialized()).throw(error); }); it("can invoke C# method", async () => { expect(Test.Invokable.joinStrings("foo", "bar")).toStrictEqual("foobar"); @@ -76,8 +76,8 @@ describe("while bootsharp is booted", () => { expect(actual).toStrictEqual(expected); }); it("can transfer lists as arrays", async () => { - Test.Types.Registry.getRegistries = () => [{ wheeled: [{ id: "foo", maxSpeed: 1, wheelCount: 0 }] }]; - const result = await Test.Types.Registry.concatRegistriesAsync([ + Test.Types.Registry.getRegistries = () => [{ wheeled: [{ id: "foo", maxSpeed: 1, wheelCount: 0 }] }]; + const result = await Test.Types.Registry.concatRegistriesAsync([ { wheeled: [{ id: "bar", maxSpeed: 1, wheelCount: 9 }] }, { tracked: [{ id: "baz", maxSpeed: 5, trackType: TrackType.Rubber }] } ]); @@ -90,11 +90,11 @@ describe("while bootsharp is booted", () => { it("can transfer dictionaries as maps", async () => { // ES6 Map doesn't natively support JSON serialization, so using plain objects. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map - Test.Types.Registry.getRegistryMap = () => ({ + Test.Types.Registry.getRegistryMap = () => ({ foo: { wheeled: [{ id: "foo", maxSpeed: 1, wheelCount: 0 }] }, bar: { wheeled: [{ id: "bar", maxSpeed: 15, wheelCount: 5 }] } }); - const result = await Test.Types.Registry.mapRegistriesAsync({ + const result = await Test.Types.Registry.mapRegistriesAsync({ baz: { tracked: [{ id: "baz", maxSpeed: 5, trackType: TrackType.Rubber }] } }); expect(result).toStrictEqual({ @@ -104,7 +104,10 @@ describe("while bootsharp is booted", () => { }); }); it("can invoke assigned JS functions in C#", () => { - Test.Types.Registry.getRegistry = () => ({ wheeled: [{ maxSpeed: 1 }], tracked: [{ maxSpeed: 2 }] }); + Test.Types.Registry.getRegistry = () => ({ + wheeled: [{ id: "", maxSpeed: 1, wheelCount: 0 }], + tracked: [{ id: "", maxSpeed: 2, trackType: TrackType.Chain }] + }); expect(Test.Types.Registry.countTotalSpeed()).toStrictEqual(3); }); it("can subscribe to events", () => { @@ -120,14 +123,14 @@ describe("while bootsharp is booted", () => { expect(multipleArg1).toStrictEqual(1); expect(multipleArg2).toStrictEqual({ id: "foo", maxSpeed: 50 }); expect(multipleArg3).toStrictEqual(TrackType.Rubber); - Test.Event.broadcastEventMultiple(255, undefined, TrackType.Chain); + Test.Event.broadcastEventMultiple(255, undefined, TrackType.Chain); expect(multipleArg1).toStrictEqual(255); expect(multipleArg2).toBeUndefined(); expect(multipleArg3).toStrictEqual(TrackType.Chain); }); it("can un-subscribe from events", () => { let result = ""; - const assigner = v => result = v; + const assigner = (v: string) => result = v; Test.Event.onEvent.subscribe(assigner); Test.Event.broadcastEvent("foo"); Test.Event.onEvent.unsubscribe(assigner); diff --git a/src/js/test/spec/platform.spec.mjs b/src/js/test/spec/platform.spec.ts similarity index 90% rename from src/js/test/spec/platform.spec.mjs rename to src/js/test/spec/platform.spec.ts index ce439a36..3cb290b4 100644 --- a/src/js/test/spec/platform.spec.mjs +++ b/src/js/test/spec/platform.spec.ts @@ -1,5 +1,5 @@ import { describe, it, beforeAll, expect } from "vitest"; -import { bootSideload, Test } from "../cs.mjs"; +import { Test, bootSideload, any } from "../cs"; import { WebSocket, WebSocketServer } from "ws"; describe("platform", () => { @@ -14,7 +14,7 @@ describe("platform", () => { it("can communicate via websocket", async () => { // .NET requires ws package when running on node: // https://github.com/dotnet/runtime/blob/main/src/mono/wasm/features.md#websocket - global.WebSocket = WebSocket; + any(global).WebSocket = WebSocket; const wss = new WebSocketServer({ port: 8877 }); wss.on("connection", socket => socket.on("message", socket.send)); expect(await Test.Platform.echoWebSocket("ws://localhost:8877", "foo", 3000)).toStrictEqual("foo"); diff --git a/src/js/tsconfig.json b/src/js/tsconfig.json index 32015a40..f8019424 100644 --- a/src/js/tsconfig.json +++ b/src/js/tsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "target": "esnext", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, "strict": true }, "include": ["src"] From d50c90de447d1fa114410f535918c3fd79f26bfd Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 06:21:16 +0300 Subject: [PATCH 60/75] sharping --- src/js/test/cs.ts | 4 ++-- src/js/test/spec/interop.spec.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/test/cs.ts b/src/js/test/cs.ts index 70dc95c6..14b2ef57 100644 --- a/src/js/test/cs.ts +++ b/src/js/test/cs.ts @@ -50,14 +50,14 @@ function loadWasmBinary() { } function loadAssemblies() { - let assemblies = []; + const assemblies = []; for (const assemblyPath of findAssemblies()) assemblies.push(loadAssembly(assemblyPath)); return assemblies; } function findAssemblies() { - let assemblyPaths = []; + const assemblyPaths = []; const dirPath = resolve("test/cs/Test/bin/sideload/bin"); assertPathExists(dirPath); for (const fileName of readdirSync(dirPath)) diff --git a/src/js/test/spec/interop.spec.ts b/src/js/test/spec/interop.spec.ts index 55530b36..1a8c1fcb 100644 --- a/src/js/test/spec/interop.spec.ts +++ b/src/js/test/spec/interop.spec.ts @@ -119,6 +119,7 @@ describe("while bootsharp is booted", () => { multipleArg3 = a3; }); Test.Event.broadcastEvent("foo"); + expect(eventArg1).toStrictEqual("foo"); Test.Event.broadcastEventMultiple(1, { id: "foo", maxSpeed: 50 }, TrackType.Rubber); expect(multipleArg1).toStrictEqual(1); expect(multipleArg2).toStrictEqual({ id: "foo", maxSpeed: 50 }); From 09c5d77b2f43b93ee2da2d80ef18692097b88c9f Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 19:16:29 +0300 Subject: [PATCH 61/75] iteration --- samples/minimal/main.mjs | 8 +++---- .../Emit/DependenciesTest.cs | 19 +++++++++++++---- .../Emit/SerializerTest.cs | 21 ++----------------- .../Emit/DependencyGenerator.cs | 1 - src/cs/Directory.Build.props | 2 +- 5 files changed, 22 insertions(+), 29 deletions(-) diff --git a/samples/minimal/main.mjs b/samples/minimal/main.mjs index af6461cb..99ae4d6e 100644 --- a/samples/minimal/main.mjs +++ b/samples/minimal/main.mjs @@ -1,18 +1,18 @@ // Named exports are auto-generated on C# build. -import bootsharp, { Global } from "./cs/bin/bootsharp/bootsharp.mjs"; +import bootsharp, { Global } from "./cs/bin/bootsharp/index.mjs"; // Binding 'Program.GetFrontendName' endpoint invoked in C#. -Global.getFrontendName = () => +Global.Program.getFrontendName = () => typeof Bun === "object" ? `Bun ${Bun.version}` : typeof Deno === "object" ? `Deno ${Deno.version.deno}` : typeof process === "object" ? `Node ${process.version}` : "Unknown JavaScript Runtime"; // Subscribing to 'Program.OnMainInvoked' C# event. -Global.onMainInvoked.subscribe(console.log); +Global.Program.onMainInvoked.subscribe(console.log); // Initializing dotnet runtime and invoking entry point. await bootsharp.boot(); // Invoking 'Program.GetBackendName' C# method. -console.log(`Hello ${Global.getBackendName()}!`); +console.log(`Hello ${Global.Program.getBackendName()}!`); diff --git a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs index 06465edd..2509208a 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/DependenciesTest.cs @@ -8,12 +8,23 @@ public class DependenciesTest : EmitTest protected override string TestedContent => GeneratedDependencies; [Fact] - public void AddsCommonGeneratedTypesByDefault () + public void WhenNothingInspectedIncludesCommonDependencies () { Execute(); - Added(All, "Bootsharp.Generated.Dependencies"); - Added(All, "Bootsharp.Generated.SerializerContext"); - Added(All, "Bootsharp.Generated.Interop"); + Contains( + """ + using System.Diagnostics.CodeAnalysis; + + namespace Bootsharp.Generated; + + public static class Dependencies + { + [System.Runtime.CompilerServices.ModuleInitializer] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Generated.Dependencies", "System.Runtime")] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Bootsharp.Generated.Interop", "System.Runtime")] + internal static void RegisterDynamicDependencies () { } + } + """); } [Fact] diff --git a/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs index 2e2acdd8..73c6e48a 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/SerializerTest.cs @@ -19,24 +19,7 @@ public void WhenNoSerializableTypesIsEmpty () WithClass("[JSInvokable] public static byte[] Bar (int[] a, double[] b, string[] c) => default;") ); Execute(); - Assert.Empty(TestedContent); - } - - [Fact] - public void AssignsTypeInfoResolver () - { - AddAssembly( - With("n", "public record Info;"), - WithClass("n", "[JSInvokable] public static void Foo (Info i) {}")); - Execute(); - Contains( - """ - [System.Runtime.CompilerServices.ModuleInitializer] - internal static void InjectTypeInfoResolver () - { - Serializer.Options.TypeInfoResolverChain.Add(SerializerContext.Default); - } - """); + Assert.DoesNotContain("JsonSerializable", TestedContent); } [Fact] // .NET's generator indexes types by short names (w/o namespace) and fails on duplicates. @@ -102,7 +85,7 @@ public void DoesntAddProxiesForTaskWithoutResult () { AddAssembly(WithClass("[JSInvokable] public static Task Foo (Task bar) => default;")); Execute(); - Assert.Empty(TestedContent); + Assert.DoesNotContain("JsonSerializable", TestedContent); } [Fact] diff --git a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index 4c6d3a1e..a85a9d9a 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -35,7 +35,6 @@ private void AddGeneratedCommon () { var asm = Path.GetFileNameWithoutExtension(entryAssembly); Add(All, "Bootsharp.Generated.Dependencies", asm); - Add(All, "Bootsharp.Generated.SerializerContext", asm); Add(All, "Bootsharp.Generated.Interop", asm); } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index d8f209bb..780cfba6 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.14 + 0.2.0-alpha.16 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 5a76fabfda4e63625cf56c569f04ca8294e7be63 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:36:59 +0300 Subject: [PATCH 62/75] iteration --- samples/minimal/index.html | 8 +- samples/minimal/main.mjs | 8 +- samples/trimming/main.mjs | 4 +- samples/vscode/src/extension.js | 6 +- .../Emit/InteropTest.cs | 10 +- .../Pack/BindingTest.cs | 92 ++++++++----------- .../Pack/DeclarationTest.cs | 72 ++++++++++++--- .../Bootsharp.Publish/Common/TypeUtilities.cs | 1 - .../TypeDeclarationGenerator.cs | 24 +++-- src/cs/Directory.Build.props | 2 +- src/js/package.json | 1 - src/js/src/exports.ts | 2 +- src/js/test/spec/boot.spec.ts | 15 +++ 13 files changed, 147 insertions(+), 98 deletions(-) diff --git a/samples/minimal/index.html b/samples/minimal/index.html index 1b5b6e9a..db0a8f0f 100644 --- a/samples/minimal/index.html +++ b/samples/minimal/index.html @@ -5,18 +5,18 @@

Loading...

diff --git a/samples/minimal/main.mjs b/samples/minimal/main.mjs index 99ae4d6e..9d597e41 100644 --- a/samples/minimal/main.mjs +++ b/samples/minimal/main.mjs @@ -1,18 +1,18 @@ // Named exports are auto-generated on C# build. -import bootsharp, { Global } from "./cs/bin/bootsharp/index.mjs"; +import bootsharp, { Program } from "./cs/bin/bootsharp/index.mjs"; // Binding 'Program.GetFrontendName' endpoint invoked in C#. -Global.Program.getFrontendName = () => +Program.getFrontendName = () => typeof Bun === "object" ? `Bun ${Bun.version}` : typeof Deno === "object" ? `Deno ${Deno.version.deno}` : typeof process === "object" ? `Node ${process.version}` : "Unknown JavaScript Runtime"; // Subscribing to 'Program.OnMainInvoked' C# event. -Global.Program.onMainInvoked.subscribe(console.log); +Program.onMainInvoked.subscribe(console.log); // Initializing dotnet runtime and invoking entry point. await bootsharp.boot(); // Invoking 'Program.GetBackendName' C# method. -console.log(`Hello ${Global.Program.getBackendName()}!`); +console.log(`Hello ${Program.getBackendName()}!`); diff --git a/samples/trimming/main.mjs b/samples/trimming/main.mjs index 81b999ed..8cf4c2b7 100644 --- a/samples/trimming/main.mjs +++ b/samples/trimming/main.mjs @@ -1,4 +1,4 @@ -import bootsharp, { Global } from "./cs/bin/bootsharp/bootsharp.mjs"; +import bootsharp, { Program } from "./cs/bin/bootsharp/index.mjs"; import zlib from "node:zlib"; import util from "node:util"; import fs from "node:fs/promises"; @@ -12,7 +12,7 @@ await Promise.all([ ...resources.assemblies.map(fetchBro) ]); -Global.log = console.log; +Program.log = console.log; await bootsharp.boot({ root: "./bin", resources }); async function measure(dir) { diff --git a/samples/vscode/src/extension.js b/samples/vscode/src/extension.js index 60df3603..65e069b1 100644 --- a/samples/vscode/src/extension.js +++ b/samples/vscode/src/extension.js @@ -1,8 +1,8 @@ import * as vscode from "vscode"; -import bootsharp, { Global } from "../../Minimal/cs/bin/bootsharp/bootsharp.mjs"; +import bootsharp, { Program } from "../../Minimal/cs/bin/bootsharp/index.mjs"; export async function activate(context) { - Global.getFrontendName = () => "VS Code"; + Program.getFrontendName = () => "VS Code"; try { await bootsharp.boot(); } catch (e) { vscode.window.showErrorMessage(e.message); } const command = vscode.commands.registerCommand("bootsharp.hello", greet); @@ -14,6 +14,6 @@ export function deactivate() { } function greet() { - const message = `Welcome, ${Global.getBackendName()}! Enjoy your VS Code extension space.`; + const message = `Welcome, ${Program.getBackendName()}! Enjoy your VS Code extension space.`; vscode.window.showInformationMessage(message); } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index ade62155..e6fbe815 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -23,7 +23,7 @@ internal static void RegisterProxies () } [Fact] - public void GeneratesForMethodsInGlobalSpace () + public void GeneratesForMethodsWithoutNamespace () { AddAssembly(With( """ @@ -38,8 +38,8 @@ [JSInvokable] public static void Inv () {} Contains("""Proxies.Set("Class.Fun", () => Class_Fun());"""); Contains("""Proxies.Set("Class.Evt", () => Class_Evt());"""); Contains("[System.Runtime.InteropServices.JavaScript.JSExport] internal static void Class_Inv () => global::Class.Inv();"); - Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); - Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Global.Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); + Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Class.funSerialized", "Bootsharp")] internal static partial void Class_Fun ();"""); + Contains("""[System.Runtime.InteropServices.JavaScript.JSImport("Class.evtSerialized", "Bootsharp")] internal static partial void Class_Evt ();"""); } [Fact] @@ -94,8 +94,8 @@ public interface IImported { void Fun (); void NotifyEvt(); } Contains("""Proxies.Set("Bootsharp.Generated.Imports.JSImported.Fun", () => Bootsharp_Generated_Imports_JSImported_Fun());"""); Contains("""Proxies.Set("Bootsharp.Generated.Imports.JSImported.OnEvt", () => Bootsharp_Generated_Imports_JSImported_OnEvt());"""); Contains("JSExport] internal static void Bootsharp_Generated_Exports_Space_JSExported_Inv () => global::Bootsharp.Generated.Exports.Space.JSExported.Inv();"); - Contains("""JSImport("Global.Imported.funSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_JSImported_Fun ();"""); - Contains("""JSImport("Global.Imported.onEvtSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_JSImported_OnEvt ();"""); + Contains("""JSImport("Imported.funSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_JSImported_Fun ();"""); + Contains("""JSImport("Imported.onEvtSerialized", "Bootsharp")] internal static partial void Bootsharp_Generated_Imports_JSImported_OnEvt ();"""); } [Fact] diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index c2027b40..19d49cd4 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -72,15 +72,13 @@ public void BindingForEventMethodIsGenerated () Execute(); Contains( """ - export const Global = { - Class: { - onFoo: new Event(), - onFooSerialized: () => Global.Class.onFoo.broadcast(), - onBar: new Event(), - onBarSerialized: (a) => Global.Class.onBar.broadcast(a), - onBaz: new Event(), - onBazSerialized: (a, b) => Global.Class.onBaz.broadcast(a, b) - } + export const Class = { + onFoo: new Event(), + onFooSerialized: () => Class.onFoo.broadcast(), + onBar: new Event(), + onBarSerialized: (a) => Class.onBar.broadcast(a), + onBaz: new Event(), + onBazSerialized: (a, b) => Class.onBaz.broadcast(a, b) }; """); } @@ -246,21 +244,19 @@ public void BindingsFromMultipleClassesAssignedToRespectiveObjects () Execute(); Contains( """ - export const Global = { - ClassA: { - inv: () => getExports().ClassA_Inv() - }, - ClassB: { - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Global.ClassB.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } - } + export const ClassA = { + inv: () => getExports().ClassA_Inv() + }; + export const ClassB = { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'ClassB.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } }; """); } [Fact] - public void WhenNoSpaceBindingsAreAssignedToGlobalObject () + public void WhenNoSpaceBindingsAreAssignedToClassObject () { AddAssembly( WithClass("[JSInvokable] public static Task Nya () => Task.FromResult(0);"), @@ -268,13 +264,11 @@ public void WhenNoSpaceBindingsAreAssignedToGlobalObject () Execute(); Contains( """ - export const Global = { - Class: { - nya: () => getExports().Class_Nya(), - get fun() { return this.funHandler; }, - set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Global.Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } - } + export const Class = { + nya: () => getExports().Class_Nya(), + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Class.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; } }; """); } @@ -286,10 +280,8 @@ public void VariablesConflictingWithJSTypesAreRenamed () Execute(); Contains( """ - export const Global = { - Class: { - fun: (fn) => getExports().Class_Fun(fn) - } + export const Class = { + fun: (fn) => getExports().Class_Fun(fn) }; """); } @@ -306,17 +298,15 @@ public void SerializesCustomType () Execute(); Contains( """ - export const Global = { - Class: { - foo: (i) => deserialize(getExports().Class_Foo(serialize(i))), - get bar() { return this.barHandler; }, - set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i))); }, - get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, - baz: new Event(), - bazSerialized: (i) => Global.Class.baz.broadcast(deserialize(i)), - yaz: new Event(), - yazSerialized: (a, i) => Global.Class.yaz.broadcast(a, deserialize(i)) - } + export const Class = { + foo: (i) => deserialize(getExports().Class_Foo(serialize(i))), + get bar() { return this.barHandler; }, + set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i))); }, + get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, + baz: new Event(), + bazSerialized: (i) => Class.baz.broadcast(deserialize(i)), + yaz: new Event(), + yazSerialized: (a, i) => Class.yaz.broadcast(a, deserialize(i)) }; """); } @@ -333,17 +323,15 @@ public void AwaitsWhenSerializingInAsyncFunctions () Execute(); Contains( """ - export const Global = { - Class: { - foo: async (i) => deserialize(await getExports().Class_Foo(serialize(i))), - get bar() { return this.barHandler; }, - set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i))); }, - get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Global.Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, - baz: async () => deserialize(await getExports().Class_Baz()), - get yaz() { return this.yazHandler; }, - set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler()); }, - get yazSerialized() { if (typeof this.yazHandler !== "function") throw Error("Failed to invoke 'Global.Class.yaz' from C#. Make sure to assign function in JavaScript."); return this.yazSerializedHandler; } - } + export const Class = { + foo: async (i) => deserialize(await getExports().Class_Foo(serialize(i))), + get bar() { return this.barHandler; }, + set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i))); }, + get barSerialized() { if (typeof this.barHandler !== "function") throw Error("Failed to invoke 'Class.bar' from C#. Make sure to assign function in JavaScript."); return this.barSerializedHandler; }, + baz: async () => deserialize(await getExports().Class_Baz()), + get yaz() { return this.yazHandler; }, + set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler()); }, + get yazSerialized() { if (typeof this.yazHandler !== "function") throw Error("Failed to invoke 'Class.yaz' from C#. Make sure to assign function in JavaScript."); return this.yazSerializedHandler; } }; """); } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 287aaf4d..1531e4cf 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -37,6 +37,29 @@ export namespace Foo.Bar.Nya.Class { """); } + [Fact] + public void WhenNoNamespaceDeclaresUnderRoot () + { + AddAssembly( + With("public record Record;"), + With("public enum Enum { A, B }"), + WithClass("[JSInvokable] public static Enum Inv (Record r) => default;")); + Execute(); + Contains( + """ + export enum Enum { + A, + B + } + export interface Record { + } + + export namespace Class { + export function inv(r: Record): Enum; + } + """); + } + [Fact] public void FunctionDeclarationIsExportedForInvokableMethod () { @@ -123,7 +146,7 @@ export interface Bar { } } - export namespace Global.Class { + export namespace Class { export function getFoo(bar: SpaceB.Bar): SpaceA.Foo; } """); @@ -141,14 +164,21 @@ public void DifferentSpacesWithSameRootAreDeclaredIndividually () } [Fact] - public void WhenNoSpaceTypesAreDeclaredUnderGlobalSpace () + public void WhenNoNamespaceTypesAreDeclaredUnderRoot () { AddAssembly( With("public class Foo { }"), WithClass("[JSFunction] public static void OnFoo (Foo foo) { }")); Execute(); - Contains("export namespace Global {\n export interface Foo {\n }\n}"); - Contains("export namespace Global.Class {\n export let onFoo: (foo: Global.Foo) => void;\n}"); + Contains( + """ + export interface Foo { + } + + export namespace Class { + export let onFoo: (foo: Foo) => void; + } + """); } [Fact] @@ -377,9 +407,23 @@ public void DefinitionIsGeneratedForNestedGenericTypes () With("Bar", "public interface GenericInterface { public T Value { get; set; } }"), WithClass("n", "[JSInvokable] public static void Method (Foo.GenericClass> p) { }")); Execute(); - Matches(@"export namespace Foo {\s*export interface GenericClass {\s*value\?: T;\s*}\s*}"); - Matches(@"export namespace Bar {\s*export interface GenericInterface {\s*value\?: T;\s*}\s*}"); - Contains("method(p: Foo.GenericClass>): void"); + Contains( + """ + export namespace Bar { + export interface GenericInterface { + value?: T; + } + } + export namespace Foo { + export interface GenericClass { + value?: T; + } + } + + export namespace n.Class { + export function method(p: Foo.GenericClass>): void; + } + """); } [Fact] @@ -510,13 +554,11 @@ public void NullableCollectionElementTypesUnionWithNull () Execute(); Contains( """ - export namespace Global { - export interface Foo { - } + export interface Foo { } - export namespace Global.Class { - export let fun: (bar: Array | undefined, nya: Array | null> | undefined, far: Array | null> | undefined) => Array | null; + export namespace Class { + export let fun: (bar: Array | undefined, nya: Array | null> | undefined, far: Array | null> | undefined) => Array | null; } """); } @@ -529,8 +571,8 @@ public void NullableCollectionElementTypesOfCustomTypeUnionWithNull () With("public record Foo (List?>?>? Bar, IFoo?[]?[]? Nya) : IFoo;"), WithClass("[JSFunction] public static IFoo Fun (Foo foo) => default;")); Execute(); - Contains("bar?: Array | null> | null>;"); - Contains("nya?: Array | null> | null>;"); + Contains("bar?: Array | null> | null>;"); + Contains("nya?: Array | null> | null>;"); } [Fact] @@ -635,6 +677,6 @@ public class Prefs : Bootsharp.Preferences With("public record Record;"), WithClass("[JSInvokable] public static void Inv (Record r) {}")); Execute(); - Contains("inv(r: Global.Foo): void"); + Contains("inv(r: Foo): void"); } } diff --git a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs index ed563f95..c4a33422 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs @@ -169,7 +169,6 @@ public static bool ShouldSerialize (Type type) public static string BuildJSSpace (Type type) { var space = type.FullName ?? type.Name; - if (type.Namespace is null) space = $"Global.{space}"; if (type.IsGenericType) space = GetGenericNameWithoutArgs(space); if (space.Contains('+')) space = space.Replace("+", "."); return space; diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index f550d916..475f834b 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -39,7 +39,8 @@ private void DeclareType () private bool ShouldOpenNamespace () { - if (prevType is null) return true; + if (type.Namespace == null) return false; + if (prevType == null) return true; return GetNamespace(prevType) != GetNamespace(type); } @@ -51,6 +52,7 @@ private void OpenNamespace () private bool ShouldCloseNamespace () { + if (type.Namespace == null) return false; if (nextType is null) return true; return GetNamespace(nextType) != GetNamespace(type); } @@ -62,27 +64,30 @@ private void CloseNamespace () private void DeclareInterface () { - AppendLine($"export interface {BuildTypeName(type)}", 1); + var indent = type.Namespace != null ? 1 : 0; + AppendLine($"export interface {BuildTypeName(type)}", indent); AppendExtensions(); builder.Append(" {"); AppendProperties(); - AppendLine("}", 1); + AppendLine("}", indent); } private void DeclareEnum () { - AppendLine($"export enum {type.Name} {{", 1); + var indent = type.Namespace != null ? 1 : 0; + AppendLine($"export enum {type.Name} {{", indent); var names = Enum.GetNames(type); for (int i = 0; i < names.Length; i++) - if (i == names.Length - 1) AppendLine(names[i], 2); - else AppendLine($"{names[i]},", 2); - AppendLine("}", 1); + if (i == names.Length - 1) AppendLine(names[i], indent + 1); + else AppendLine($"{names[i]},", indent + 1); + AppendLine("}", indent); } private string GetNamespace (Type type) { var space = prefs.ResolveSpace(type, BuildJSSpace(type)); - return space[..space.LastIndexOf('.')]; + var lastDotIdx = space.LastIndexOf('.'); + return lastDotIdx >= 0 ? space[..lastDotIdx] : space; } private void AppendExtensions () @@ -104,7 +109,8 @@ private void AppendProperties () private void AppendProperty (PropertyInfo property) { - AppendLine(ToFirstLower(property.Name), 2); + var indent = type.Namespace != null ? 1 : 0; + AppendLine(ToFirstLower(property.Name), indent + 1); if (IsNullable(property)) builder.Append('?'); builder.Append($": {BuildType()};"); diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 780cfba6..6314f36d 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.16 + 0.2.0-alpha.18 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com diff --git a/src/js/package.json b/src/js/package.json index 7c60dcd3..794629fd 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -3,7 +3,6 @@ "compile": "sh scripts/compile-test.sh", "test": "vitest run --silent --pool=vmThreads", "cover": "npm test -- --coverage.enabled --coverage.100 --coverage.include=**/sideload/*.mjs --coverage.exclude=**/dotnet.* --coverage.allowExternal", - "serve": "serve coverage", "build": "sh scripts/build.sh" }, "devDependencies": { diff --git a/src/js/src/exports.ts b/src/js/src/exports.ts index 853e3fef..9d2ed966 100644 --- a/src/js/src/exports.ts +++ b/src/js/src/exports.ts @@ -4,5 +4,5 @@ export let exports: unknown; export async function bindExports(runtime: RuntimeAPI, assembly: string) { const asm = await runtime.getAssemblyExports(assembly); - exports = asm["Bootsharp"]["Generated"]["Interop"]; + exports = asm["Bootsharp"]?.["Generated"]["Interop"]; } diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index 69c961dc..383bb5fc 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -157,6 +157,21 @@ describe("boot", () => { expect(customs.run).toHaveBeenCalledOnce(); expect(customs.export).toHaveBeenCalledOnce(); }); + it("can boot when program has no exports", async () => { + const { bootsharp, root } = await setup(); + const options: BootOptions = { + create: vi.fn(async () => { + const bootsharp = (await import("../cs/Test/bin/sideload")).default; + const dotnet = (await bootsharp.dotnet.getMain(root)).dotnet; + console.log(dotnet); + const cfg = await bootsharp.dotnet.buildConfig(bootsharp.resources, root); + const runtime = await dotnet.withConfig(cfg).create(); + runtime.getAssemblyExports = _ => Promise.resolve({}); + return runtime; + }) + }; + await bootsharp.boot(options); + }); }); describe("boot status", () => { From 192acec33598d33b7e716510dc68639e7a34f819 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:41:54 +0300 Subject: [PATCH 63/75] etc --- src/js/test/spec/boot.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index 383bb5fc..cc1d21ee 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -166,7 +166,7 @@ describe("boot", () => { console.log(dotnet); const cfg = await bootsharp.dotnet.buildConfig(bootsharp.resources, root); const runtime = await dotnet.withConfig(cfg).create(); - runtime.getAssemblyExports = _ => Promise.resolve({}); + runtime.getAssemblyExports = () => Promise.resolve({}); return runtime; }) }; From 87a80d86de74c2cac3a81fa8948443a1f17d8508 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:43:36 +0300 Subject: [PATCH 64/75] etc --- src/js/test/spec/boot.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index cc1d21ee..ad2cfcb7 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -144,7 +144,6 @@ describe("boot", () => { create: vi.fn(async () => { const bootsharp = (await import("../cs/Test/bin/sideload")).default; const dotnet = (await bootsharp.dotnet.getMain(root)).dotnet; - console.log(dotnet); return await dotnet.withConfig(customs.config).create(); }), import: vi.fn(), @@ -161,10 +160,8 @@ describe("boot", () => { const { bootsharp, root } = await setup(); const options: BootOptions = { create: vi.fn(async () => { - const bootsharp = (await import("../cs/Test/bin/sideload")).default; - const dotnet = (await bootsharp.dotnet.getMain(root)).dotnet; - console.log(dotnet); const cfg = await bootsharp.dotnet.buildConfig(bootsharp.resources, root); + const dotnet = (await bootsharp.dotnet.getMain(root)).dotnet; const runtime = await dotnet.withConfig(cfg).create(); runtime.getAssemblyExports = () => Promise.resolve({}); return runtime; From c6c2af1fc38fa396f2de16ddfb1f37f84fafda1d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:51:21 +0300 Subject: [PATCH 65/75] sharping --- src/js/test/cs.ts | 6 +++++- src/js/test/spec/boot.spec.ts | 20 ++++++++++---------- src/js/test/spec/interop.spec.ts | 20 ++++++++++---------- src/js/test/spec/platform.spec.ts | 4 ++-- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/js/test/cs.ts b/src/js/test/cs.ts index 14b2ef57..8b7c6e17 100644 --- a/src/js/test/cs.ts +++ b/src/js/test/cs.ts @@ -39,7 +39,11 @@ export function getDeclarations() { } // Just casting to triggers codefactor. -export function any(obj: unknown): Record { +export function any(obj: unknown): Record { + return >obj; +} + +export function to(obj: unknown): Record { return >obj; } diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index ad2cfcb7..facb97b6 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -58,38 +58,38 @@ describe("boot", () => { it("can boot with bins content pre-assigned", async () => { const { bootsharp, Test, root, bins, any } = await setup(); const resources = { ...bootsharp.resources }; - any(resources.wasm).content = bins.wasm; + any(resources.wasm).content = bins.wasm; for (const asm of resources.assemblies) - any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content; + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content; await bootsharp.boot({ resources, root }); expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with base64 content", async () => { const { bootsharp, Test, root, bins, any } = await setup(); const resources = { ...bootsharp.resources }; - any(resources.wasm).content = bins.wasm.toString("base64"); + any(resources.wasm).content = bins.wasm.toString("base64"); for (const asm of resources.assemblies) - any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); await bootsharp.boot({ resources, root }); expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("can boot with base64 content w/o native encoder available", async () => { const { bootsharp, Test, root, bins, any } = await setup(); - any(global).Buffer = undefined; + any(global).Buffer = undefined; const resources = { ...bootsharp.resources }; - any(resources.wasm).content = bins.wasm.toString("base64"); + any(resources.wasm).content = bins.wasm.toString("base64"); for (const asm of resources.assemblies) - any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); await bootsharp.boot({ resources, root }); expect(Test.Program.onMainInvoked).toHaveBeenCalledOnce(); }); it("attempts to use atob when window is defined in global", async () => { const { bootsharp, root, bins, any } = await setup(); - any(global).window = { atob: vi.fn() }; + any(global).window = { atob: vi.fn() }; const resources = { ...bootsharp.resources }; - any(resources.wasm).content = bins.wasm.toString("base64"); + any(resources.wasm).content = bins.wasm.toString("base64"); for (const asm of resources.assemblies) - any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); + any(asm).content = bins.assemblies.find(a => a.name === asm.name)!.content.toString("base64"); try { await bootsharp.boot({ resources, root }); } catch {} expect(global.window.atob).toHaveBeenCalledOnce(); diff --git a/src/js/test/spec/interop.spec.ts b/src/js/test/spec/interop.spec.ts index 1a8c1fcb..e2236f09 100644 --- a/src/js/test/spec/interop.spec.ts +++ b/src/js/test/spec/interop.spec.ts @@ -1,5 +1,5 @@ import { describe, it, beforeAll, expect } from "vitest"; -import { Test, bootSideload, any } from "../cs"; +import { Test, bootSideload, any, to } from "../cs"; const TrackType = Test.Types.TrackType; @@ -13,7 +13,7 @@ describe("while bootsharp is booted", () => { beforeAll(bootSideload); it("throws when invoking un-assigned JS function from C#", () => { const error = /Failed to invoke '.+' from C#. Make sure to assign function in JavaScript/; - any(Test.Program).onMainInvoked = undefined; + any(Test.Program).onMainInvoked = undefined; expect(Test.Functions.getBytes).toBeUndefined(); expect(Test.Functions.getString).toBeUndefined(); expect(Test.Functions.getStringAsync).toBeUndefined(); @@ -22,14 +22,14 @@ describe("while bootsharp is booted", () => { expect(Test.Types.Registry.getRegistry).toBeUndefined(); expect(Test.Types.Registry.getRegistries).toBeUndefined(); expect(Test.Types.Registry.getRegistryMap).toBeUndefined(); - expect(() => any<() => void>(Test.Functions).getStringSerialized()).throw(error); - expect(() => any<() => void>(Test.Functions).getStringAsyncSerialized()).throw(error); - expect(() => any<() => void>(Test.Functions).getBytesSerialized()).throw(error); - expect(() => any<() => void>(Test.Platform).throwJSSerialized()).throw(error); - expect(() => any<() => void>(Test.Program).onMainInvokedSerialized()).throw(error); - expect(() => any<() => void>(Test.Types.Registry).getRegistrySerialized()).throw(error); - expect(() => any<() => void>(Test.Types.Registry).getRegistriesSerialized()).throw(error); - expect(() => any<() => void>(Test.Types.Registry).getRegistryMapSerialized()).throw(error); + expect(() => to<() => void>(Test.Functions).getStringSerialized()).throw(error); + expect(() => to<() => void>(Test.Functions).getStringAsyncSerialized()).throw(error); + expect(() => to<() => void>(Test.Functions).getBytesSerialized()).throw(error); + expect(() => to<() => void>(Test.Platform).throwJSSerialized()).throw(error); + expect(() => to<() => void>(Test.Program).onMainInvokedSerialized()).throw(error); + expect(() => to<() => void>(Test.Types.Registry).getRegistrySerialized()).throw(error); + expect(() => to<() => void>(Test.Types.Registry).getRegistriesSerialized()).throw(error); + expect(() => to<() => void>(Test.Types.Registry).getRegistryMapSerialized()).throw(error); }); it("can invoke C# method", async () => { expect(Test.Invokable.joinStrings("foo", "bar")).toStrictEqual("foobar"); diff --git a/src/js/test/spec/platform.spec.ts b/src/js/test/spec/platform.spec.ts index 3cb290b4..00699308 100644 --- a/src/js/test/spec/platform.spec.ts +++ b/src/js/test/spec/platform.spec.ts @@ -1,6 +1,6 @@ import { describe, it, beforeAll, expect } from "vitest"; -import { Test, bootSideload, any } from "../cs"; import { WebSocket, WebSocketServer } from "ws"; +import { Test, bootSideload, any } from "../cs"; describe("platform", () => { beforeAll(bootSideload); @@ -14,7 +14,7 @@ describe("platform", () => { it("can communicate via websocket", async () => { // .NET requires ws package when running on node: // https://github.com/dotnet/runtime/blob/main/src/mono/wasm/features.md#websocket - any(global).WebSocket = WebSocket; + any(global).WebSocket = WebSocket; const wss = new WebSocketServer({ port: 8877 }); wss.on("connection", socket => socket.on("message", socket.send)); expect(await Test.Platform.echoWebSocket("ws://localhost:8877", "foo", 3000)).toStrictEqual("foo"); From 5b7fa52376b1239370a745ebd5f496bb6211673d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sat, 20 Jan 2024 22:38:03 +0300 Subject: [PATCH 66/75] iteration --- .../backend/Backend.WASM/Backend.WASM.csproj | 3 ++- .../react/backend/Backend.WASM/Preferences.cs | 12 +++++++++ samples/react/backend/Backend.WASM/Program.cs | 12 ++++----- samples/react/package.json | 12 ++++----- .../Emit/InterfacesTest.cs | 4 +-- .../PreferencesResolver/PreferencesContext.cs | 25 +++++++++++++++++++ .../PreferencesResolver.cs | 15 +++-------- .../Emit/DependencyGenerator.cs | 11 ++++---- .../Emit/InterfaceGenerator.cs | 2 +- src/cs/Directory.Build.props | 2 +- 10 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 samples/react/backend/Backend.WASM/Preferences.cs create mode 100644 src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs rename src/cs/Bootsharp.Publish/Common/{ => PreferencesResolver}/PreferencesResolver.cs (63%) diff --git a/samples/react/backend/Backend.WASM/Backend.WASM.csproj b/samples/react/backend/Backend.WASM/Backend.WASM.csproj index 79f6e089..646cb936 100644 --- a/samples/react/backend/Backend.WASM/Backend.WASM.csproj +++ b/samples/react/backend/Backend.WASM/Backend.WASM.csproj @@ -3,9 +3,10 @@ net8.0 browser-wasm + enable backend - + $(SolutionDir) false diff --git a/samples/react/backend/Backend.WASM/Preferences.cs b/samples/react/backend/Backend.WASM/Preferences.cs new file mode 100644 index 00000000..1b68aa76 --- /dev/null +++ b/samples/react/backend/Backend.WASM/Preferences.cs @@ -0,0 +1,12 @@ +/// +/// Customizes Bootsharp behaviour at build time. +/// +public class Preferences : Bootsharp.Preferences +{ + // Group all generated JavaScript artifacts under 'Computer' namespace. + public override string ResolveSpace (Type type, string @default) + { + var dotIdx = @default.IndexOf('.'); + return dotIdx >= 0 ? "Computer" + @default[dotIdx..] : @default; + } +} diff --git a/samples/react/backend/Backend.WASM/Program.cs b/samples/react/backend/Backend.WASM/Program.cs index aa5b815e..4ea502a3 100644 --- a/samples/react/backend/Backend.WASM/Program.cs +++ b/samples/react/backend/Backend.WASM/Program.cs @@ -1,5 +1,3 @@ -using Backend; -using Backend.Prime; using Bootsharp; using Bootsharp.Inject; using Microsoft.Extensions.DependencyInjection; @@ -10,15 +8,15 @@ // and can be shared with other build targets (console, MAUI, etc). // Generate C# -> JavaScript interop handlers for specified contracts. -[assembly: JSExport(typeof(IComputer))] +[assembly: JSExport(typeof(Backend.IComputer))] // Generate JavaScript -> C# interop handlers for specified contracts. -[assembly: JSImport(typeof(IPrimeUI))] -// Group all generated JavaScript artifacts under 'Computer' namespace. -[assembly: JSNamespace("^.*$", "Computer")] +[assembly: JSImport(typeof(Backend.Prime.IPrimeUI))] +// Customize Bootsharp behaviour at build time. +[assembly: JSConfiguration] // Perform dependency injection. new ServiceCollection() - .AddSingleton() // use prime computer + .AddSingleton() // use prime computer .AddBootsharp() // inject generated interop handlers .BuildServiceProvider() .RunBootsharp(); // initialize interop services diff --git a/samples/react/package.json b/samples/react/package.json index a2fc5e9d..767f44be 100644 --- a/samples/react/package.json +++ b/samples/react/package.json @@ -7,19 +7,19 @@ }, "devDependencies": { "typescript": "^5.3.3", - "@types/react": "^18.2.46", + "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", - "vite": "^5.0.10", + "vite": "^5.0.12", "@vitejs/plugin-react-swc": "^3.5.0", - "vitest": "^1.1.1", - "@vitest/coverage-v8": "^1.1.1", - "happy-dom": "^12.10.3", + "vitest": "^1.2.1", + "@vitest/coverage-v8": "^1.2.1", + "happy-dom": "^13.2.0", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "eslint": "^8.56.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "@typescript-eslint/eslint-plugin": "^6.16.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", "npm-check-updates": "^16.14.12" }, "scripts": { diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs index 7de8d733..c1c69d32 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -53,7 +53,7 @@ internal static class InterfaceRegistrations [System.Runtime.CompilerServices.ModuleInitializer] internal static void RegisterInterfaces () { - Interfaces.Register(typeof(Bootsharp.Generated.Exports.JSExported), new ExportInterface(typeof(global::IExported), handler => new Bootsharp.Generated.Exports.JSExported(handler))); + Interfaces.Register(typeof(Bootsharp.Generated.Exports.JSExported), new ExportInterface(typeof(global::IExported), handler => new Bootsharp.Generated.Exports.JSExported((global::IExported)handler))); } } } @@ -166,7 +166,7 @@ internal static class InterfaceRegistrations [System.Runtime.CompilerServices.ModuleInitializer] internal static void RegisterInterfaces () { - Interfaces.Register(typeof(Bootsharp.Generated.Exports.Space.JSExported), new ExportInterface(typeof(global::Space.IExported), handler => new Bootsharp.Generated.Exports.Space.JSExported(handler))); + Interfaces.Register(typeof(Bootsharp.Generated.Exports.Space.JSExported), new ExportInterface(typeof(global::Space.IExported), handler => new Bootsharp.Generated.Exports.Space.JSExported((global::Space.IExported)handler))); Interfaces.Register(typeof(global::Space.IImported), new ImportInterface(new Bootsharp.Generated.Imports.Space.JSImported())); } } diff --git a/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs new file mode 100644 index 00000000..fe57e5a2 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Bootsharp.Publish; + +// https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability +internal sealed class PreferencesContext (string outDir) : AssemblyLoadContext +{ + public Assembly LoadAssembly (string assemblyFileName) + { + using var stream = StreamFile(assemblyFileName); + return LoadFromStream(stream); + } + + protected override Assembly Load (AssemblyName name) + { + return LoadAssembly($"{name.Name}.dll"); + } + + private Stream StreamFile (string fileName) + { + var path = Path.GetFullPath(Path.Combine(outDir, fileName)); + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } +} diff --git a/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs similarity index 63% rename from src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs rename to src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs index 5a4ac754..cf3e383d 100644 --- a/src/cs/Bootsharp.Publish/Common/PreferencesResolver.cs +++ b/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs @@ -1,5 +1,5 @@ using System.Reflection; -using System.Runtime.Loader; +using System.Runtime.CompilerServices; namespace Bootsharp.Publish; @@ -7,19 +7,12 @@ internal sealed class PreferencesResolver (string entryAssemblyName) { public Preferences Resolve (string outDir) { - var ctx = new AssemblyLoadContext(entryAssemblyName, true); - var assembly = LoadMainAssembly(ctx, outDir); + var ctx = new PreferencesContext(outDir); + var assembly = ctx.LoadAssembly(entryAssemblyName); if (FindConfigurationAttribute(assembly) is not { } attr) return new(); return InstantiateCustomPrefs(assembly, attr.AttributeType); } - private Assembly LoadMainAssembly (AssemblyLoadContext ctx, string outDir) - { - var path = Path.GetFullPath(Path.Combine(outDir, entryAssemblyName)); - using var stream = File.OpenRead(path); - return ctx.LoadFromStream(stream); - } - private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) { var cfgAttr = typeof(JSConfigurationAttribute<>).FullName!; @@ -32,6 +25,6 @@ private Assembly LoadMainAssembly (AssemblyLoadContext ctx, string outDir) private Preferences InstantiateCustomPrefs (Assembly assembly, Type attributeType) { var prefsType = attributeType.GenericTypeArguments[0]; - return (Preferences)assembly.CreateInstance(prefsType.FullName!)!; + return Unsafe.As(assembly.CreateInstance(prefsType.FullName!)!); } } diff --git a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs index a85a9d9a..2029d11a 100644 --- a/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/DependencyGenerator.cs @@ -33,16 +33,14 @@ internal static void RegisterDynamicDependencies () { } private void AddGeneratedCommon () { - var asm = Path.GetFileNameWithoutExtension(entryAssembly); - Add(All, "Bootsharp.Generated.Dependencies", asm); - Add(All, "Bootsharp.Generated.Interop", asm); + Add(All, "Bootsharp.Generated.Dependencies", entryAssembly); + Add(All, "Bootsharp.Generated.Interop", entryAssembly); } private void AddGeneratedInteropClasses (AssemblyInspection inspection) { - var asm = Path.GetFileNameWithoutExtension(entryAssembly); foreach (var inter in inspection.Interfaces) - Add(All, inter.FullName, asm); + Add(All, inter.FullName, entryAssembly); } private void AddClassesWithInteropMethods (AssemblyInspection inspection) @@ -53,6 +51,7 @@ private void AddClassesWithInteropMethods (AssemblyInspection inspection) private void Add (DynamicallyAccessedMemberTypes types, string name, string assembly) { - added.Add($"""[DynamicDependency(DynamicallyAccessedMemberTypes.{types}, "{name}", "{assembly}")]"""); + var asm = assembly.EndsWith(".dll", StringComparison.Ordinal) ? assembly[..^4] : assembly; + added.Add($"""[DynamicDependency(DynamicallyAccessedMemberTypes.{types}, "{name}", "{asm}")]"""); } } diff --git a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs index 65ed5819..edfd6c2d 100644 --- a/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Emit/InterfaceGenerator.cs @@ -74,7 +74,7 @@ public class {{i.Name}} : {{i.TypeSyntax}} private string EmitRegistration (InterfaceMeta i) => i.Kind == InterfaceKind.Import ? $"Interfaces.Register(typeof({i.TypeSyntax}), new ImportInterface(new {i.FullName}()));" : - $"Interfaces.Register(typeof({i.FullName}), new ExportInterface(typeof({i.TypeSyntax}), handler => new {i.FullName}(handler)));"; + $"Interfaces.Register(typeof({i.FullName}), new ExportInterface(typeof({i.TypeSyntax}), handler => new {i.FullName}(({i.TypeSyntax})handler)));"; private string EmitExportMethod (MethodMeta method) { diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 6314f36d..9f5d51cf 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.18 + 0.2.0-alpha.26 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From f6d449089e6cf62d5c61f51d4d5d66a887b4760e Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 21 Jan 2024 04:58:36 +0300 Subject: [PATCH 67/75] sigh --- .../react/backend/Backend.WASM/Preferences.cs | 12 ---- samples/react/backend/Backend.WASM/Program.cs | 4 +- samples/react/backend/package.json | 5 +- .../Bootsharp.Common.Test/PreferencesTest.cs | 11 ---- src/cs/Bootsharp.Common.Test/TypesTest.cs | 8 +-- .../Attributes/JSConfigurationAttribute.cs | 28 ---------- .../Attributes/JSPreferencesAttribute.cs | 56 +++++++++++++++++++ src/cs/Bootsharp.Common/Meta/ArgumentMeta.cs | 22 -------- src/cs/Bootsharp.Common/Meta/AssemblyMeta.cs | 16 ------ src/cs/Bootsharp.Common/Meta/InterfaceKind.cs | 18 ------ src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs | 33 ----------- .../Meta/InterfaceMethodMeta.cs | 17 ------ src/cs/Bootsharp.Common/Meta/MethodKind.cs | 23 -------- src/cs/Bootsharp.Common/Meta/MethodMeta.cs | 47 ---------------- src/cs/Bootsharp.Common/Meta/SolutionMeta.cs | 27 --------- src/cs/Bootsharp.Common/Meta/ValueMeta.cs | 36 ------------ src/cs/Bootsharp.Common/Preferences.cs | 49 ---------------- .../Emit/InterfacesTest.cs | 28 ++++------ .../Emit/InteropTest.cs | 9 +-- .../Pack/BindingTest.cs | 23 ++------ .../Pack/DeclarationTest.cs | 21 +++---- src/cs/Bootsharp.Publish.Test/TypesTest.cs | 17 ++++++ .../AssemblyInspector/AssemblyInspector.cs | 19 +++---- .../Common/Meta/ArgumentMeta.cs | 10 ++++ .../Common/Meta/AssemblyMeta.cs | 7 +++ .../Common/Meta/InterfaceKind.cs | 7 +++ .../Common/Meta/InterfaceMeta.cs | 11 ++++ .../Common/Meta/InterfaceMethodMeta.cs | 7 +++ .../Common/Meta/MethodKind.cs | 8 +++ .../Common/Meta/MethodMeta.cs | 19 +++++++ .../Common/Meta/ValueMeta.cs | 12 ++++ .../Common/Preferences/Preference.cs | 3 + .../Common/Preferences/Preferences.cs | 9 +++ .../Common/Preferences/PreferencesResolver.cs | 49 ++++++++++++++++ .../PreferencesResolver/PreferencesContext.cs | 25 --------- .../PreferencesResolver.cs | 30 ---------- .../Common/TypeConverter/TypeConverter.cs | 9 +-- .../Bootsharp.Publish/Common/TypeUtilities.cs | 9 +++ .../Pack/BindingGenerator.cs | 2 +- .../TypeDeclarationGenerator.cs | 6 +- src/cs/Directory.Build.props | 2 +- 41 files changed, 276 insertions(+), 478 deletions(-) delete mode 100644 samples/react/backend/Backend.WASM/Preferences.cs delete mode 100644 src/cs/Bootsharp.Common.Test/PreferencesTest.cs delete mode 100644 src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs create mode 100644 src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/ArgumentMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/AssemblyMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/InterfaceKind.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/MethodKind.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/MethodMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/SolutionMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Meta/ValueMeta.cs delete mode 100644 src/cs/Bootsharp.Common/Preferences.cs create mode 100644 src/cs/Bootsharp.Publish.Test/TypesTest.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/ArgumentMeta.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/InterfaceKind.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/InterfaceMeta.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/InterfaceMethodMeta.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/MethodKind.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/MethodMeta.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Meta/ValueMeta.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Preferences/Preferences.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs diff --git a/samples/react/backend/Backend.WASM/Preferences.cs b/samples/react/backend/Backend.WASM/Preferences.cs deleted file mode 100644 index 1b68aa76..00000000 --- a/samples/react/backend/Backend.WASM/Preferences.cs +++ /dev/null @@ -1,12 +0,0 @@ -/// -/// Customizes Bootsharp behaviour at build time. -/// -public class Preferences : Bootsharp.Preferences -{ - // Group all generated JavaScript artifacts under 'Computer' namespace. - public override string ResolveSpace (Type type, string @default) - { - var dotIdx = @default.IndexOf('.'); - return dotIdx >= 0 ? "Computer" + @default[dotIdx..] : @default; - } -} diff --git a/samples/react/backend/Backend.WASM/Program.cs b/samples/react/backend/Backend.WASM/Program.cs index 4ea502a3..52d943e4 100644 --- a/samples/react/backend/Backend.WASM/Program.cs +++ b/samples/react/backend/Backend.WASM/Program.cs @@ -11,8 +11,8 @@ [assembly: JSExport(typeof(Backend.IComputer))] // Generate JavaScript -> C# interop handlers for specified contracts. [assembly: JSImport(typeof(Backend.Prime.IPrimeUI))] -// Customize Bootsharp behaviour at build time. -[assembly: JSConfiguration] +// Group all generated JavaScript APIs under "Computer" namespace. +[assembly: JSPreferences(Space = [".+", "Computer"])] // Perform dependency injection. new ServiceCollection() diff --git a/samples/react/backend/package.json b/samples/react/backend/package.json index 98e899c4..c5fc7bfb 100644 --- a/samples/react/backend/package.json +++ b/samples/react/backend/package.json @@ -1,5 +1,6 @@ { "name": "backend", - "main": "Backend.WASM/bin/backend/backend.mjs", - "types": "Backend.WASM/bin/backend/types/backend.d.ts" + "type": "module", + "main": "Backend.WASM/bin/backend/index.mjs", + "types": "Backend.WASM/bin/backend/types/index.d.ts" } diff --git a/src/cs/Bootsharp.Common.Test/PreferencesTest.cs b/src/cs/Bootsharp.Common.Test/PreferencesTest.cs deleted file mode 100644 index 1077b117..00000000 --- a/src/cs/Bootsharp.Common.Test/PreferencesTest.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Bootsharp.Common.Test; - -public class PreferencesTest -{ - [Fact] - public void ReturnDefaultsByDefault () - { - var prefs = new Preferences(); - Assert.Equal("foo", prefs.ResolveSpace(default, "foo")); - } -} diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index f4e1af46..7d0a13b8 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -16,13 +16,6 @@ public class TypesTest public void Records () { // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 - _ = new SolutionMeta { Assemblies = [], Interfaces = [], Methods = [], Crawled = [] } with { Assemblies = default }; - _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; - _ = new InterfaceMeta { Kind = default, TypeSyntax = "", Name = "", Namespace = "", Methods = [] } with { Name = "foo" }; - _ = new InterfaceMethodMeta { Name = "", Generated = default } with { Name = "foo" }; - _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Kind = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; - _ = new ArgumentMeta { Name = "", JSName = "", Value = default } with { Name = "foo" }; - _ = new ValueMeta { Type = default, Nullable = true, TypeSyntax = "", Void = true, Serialized = true, Async = true, JSTypeSyntax = "" } with { TypeSyntax = "foo" }; _ = new MockItem("") with { Id = "foo" }; _ = new MockItemWithEnum(default) with { Enum = MockEnum.Bar }; _ = new MockRecord(default) with { Items = new[] { new MockItem("") } }; @@ -33,6 +26,7 @@ public void TypesAreAssigned () { Assert.Equal([typeof(IBackend)], new JSExportAttribute(typeof(IBackend)).Types); Assert.Equal([typeof(IFrontend)], new JSImportAttribute(typeof(IFrontend)).Types); + Assert.Equal("Space", (new JSPreferencesAttribute { Space = ["Space"] }).Space[0]); } [Fact] diff --git a/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs deleted file mode 100644 index 75fa390a..00000000 --- a/src/cs/Bootsharp.Common/Attributes/JSConfigurationAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Bootsharp; - -/// -/// When applied to WASM entry point assembly, configures Bootsharp behaviour at build time. -/// -/// -/// Override a method in the inherited class to configure associated -/// operation. Each method has "default" argument containing default result of the operation; -/// return custom result to override it or return it as-is to keep default behaviour. -/// -/// -/// Make generated JS namespaces equal last part of the associated C# namespace: -/// ] -/// -/// public class MyPrefs : Preferences -/// { -/// public override string ResolveSpace (Type type, string @default) -/// { -/// var lastDotIdx = @default.LastIndexOf('.'); -/// if (lastDotIdx >= 0) return @default[lastDotIdx..]; -/// return @default; -/// } -/// } -/// ]]> -/// -[AttributeUsage(AttributeTargets.Assembly)] -public sealed class JSConfigurationAttribute : Attribute where T : Preferences, new(); diff --git a/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs new file mode 100644 index 00000000..02bfab4d --- /dev/null +++ b/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs @@ -0,0 +1,56 @@ +namespace Bootsharp; + +/// +/// When applied to WASM entry point assembly, configures Bootsharp behaviour at build time. +/// +/// +/// Each attribute property expects array of pattern and replacement string pairs, which are +/// supplied to Regex.Replace when generating associated JavaScript code. Each consequent pair +/// is tested in order; on first match the result replaces the default. +/// +/// +/// Make all bindings declared under "Foo.Bar" C# namespace have "Baz" namespace in JavaScript: +/// +/// [assembly: Bootsharp.JSPreferences( +/// Space = ["^Foo\.Bar\.(\S+)", "Baz.$1"] +/// )] +/// +/// +[AttributeUsage(AttributeTargets.Assembly)] +public sealed class JSPreferencesAttribute : Attribute +{ + /// + /// Customize generated JavaScript object names and TypeScript namespaces. + /// + /// + /// The patterns are matched against full names of the declaring C# types, + /// affecting both objects generated to host bindings and type names + /// of the values referenced in the bindings. + /// + public string[] Space { get; init; } = []; + /// + /// Customize generated TypeScript type syntax. + /// + /// + /// The patterns are matched against full C# type names of + /// interop method arguments, return values and object properties. + /// + public string[] Type { get; init; } = []; + /// + /// Customize which C# methods should be transformed into JavaScript + /// events, as well as generated event names. + /// + /// + /// The patterns are matched against C# method names declared under + /// interfaces. By default, methods + /// starting with "Notify.." are matched. + /// + public string[] Event { get; init; } = []; + /// + /// Customize generated JavaScript function names. + /// + /// + /// The patterns are matched against C# interop method names. + /// + public string[] Function { get; init; } = []; +} diff --git a/src/cs/Bootsharp.Common/Meta/ArgumentMeta.cs b/src/cs/Bootsharp.Common/Meta/ArgumentMeta.cs deleted file mode 100644 index a3a95fac..00000000 --- a/src/cs/Bootsharp.Common/Meta/ArgumentMeta.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of an interop method argument. -/// -public sealed record ArgumentMeta -{ - /// - /// C# name of the argument, as specified in source code. - /// - public required string Name { get; init; } - /// - /// JavaScript name of the argument, to be specified in source code. - /// - public required string JSName { get; init; } - /// - /// Metadata of the argument's value. - /// - public required ValueMeta Value { get; init; } - - public override string ToString () => $"{Name}: {Value.JSTypeSyntax}"; -} diff --git a/src/cs/Bootsharp.Common/Meta/AssemblyMeta.cs b/src/cs/Bootsharp.Common/Meta/AssemblyMeta.cs deleted file mode 100644 index 7101118e..00000000 --- a/src/cs/Bootsharp.Common/Meta/AssemblyMeta.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of a C# assembly included in compiled solution. -/// -public sealed record AssemblyMeta -{ - /// - /// Name of the assembly; equals DLL file name, w/o the extension. - /// - public required string Name { get; init; } - /// - /// Raw binary content of the assembly. - /// - public required byte[] Bytes { get; init; } -} diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceKind.cs b/src/cs/Bootsharp.Common/Meta/InterfaceKind.cs deleted file mode 100644 index 77b5ea92..00000000 --- a/src/cs/Bootsharp.Common/Meta/InterfaceKind.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Bootsharp; - -/// -/// Type of interop interface. -/// -public enum InterfaceKind -{ - /// - /// The interface was supplied under and - /// is intended for exposing C# APIs to JavaScript. - /// - Export, - /// - /// The interface was supplied under and - /// is intended for exposing JavaScript APIs to C#. - /// - Import -} diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs b/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs deleted file mode 100644 index 6a61a24b..00000000 --- a/src/cs/Bootsharp.Common/Meta/InterfaceMeta.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of a C# interface supplied by user -/// under either or . -/// -public sealed record InterfaceMeta -{ - /// - /// Kind of the interface. - /// - public required InterfaceKind Kind { get; init; } - /// - /// C# syntax of the interface type, as specified in source code. - /// - public required string TypeSyntax { get; init; } - /// - /// Namespace of the generated interop class implementation. - /// - public required string Namespace { get; init; } - /// - /// Name of the generated interop class implementation. - /// - public required string Name { get; init; } - /// - /// Full type name of the generated interop class implementation. - /// - public string FullName => $"{Namespace}.{Name}"; - /// - /// Methods declared on the interface and associated interop counterparts. - /// - public required IReadOnlyCollection Methods { get; init; } -} diff --git a/src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs b/src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs deleted file mode 100644 index 2705da7b..00000000 --- a/src/cs/Bootsharp.Common/Meta/InterfaceMethodMeta.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of a method declared on either -/// or interface. -/// -public sealed record InterfaceMethodMeta -{ - /// - /// Name of the method as declared on the interface. - /// - public required string Name { get; set; } - /// - /// Metadata about the interop method generated for the interface method. - /// - public required MethodMeta Generated { get; set; } -} diff --git a/src/cs/Bootsharp.Common/Meta/MethodKind.cs b/src/cs/Bootsharp.Common/Meta/MethodKind.cs deleted file mode 100644 index ba3d1a8f..00000000 --- a/src/cs/Bootsharp.Common/Meta/MethodKind.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Bootsharp; - -/// -/// Type of interop method. -/// -public enum MethodKind -{ - /// - /// The method is implemented in C# and invoked from JavaScript; - /// implementation has . - /// - Invokable, - /// - /// The method is implemented in JavaScript and invoked from C#; - /// implementation has . - /// - Function, - /// - /// The method is invoked from C# to notify subscribers in JavaScript; - /// implementation has . - /// - Event -} diff --git a/src/cs/Bootsharp.Common/Meta/MethodMeta.cs b/src/cs/Bootsharp.Common/Meta/MethodMeta.cs deleted file mode 100644 index 8d7e3211..00000000 --- a/src/cs/Bootsharp.Common/Meta/MethodMeta.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of an interop method. -/// -public sealed record MethodMeta -{ - /// - /// Type of interop the method is implementing. - /// - public required MethodKind Kind { get; init; } - /// - /// C# assembly name (DLL file name, w/o the extension), under which the method is declared. - /// - public required string Assembly { get; init; } - /// - /// Full name of the C# type (including namespace), under which the method is declared. - /// - public required string Space { get; init; } - /// - /// JavaScript object name(s) (joined with dot when nested) under which the associated interop - /// function will be declared; resolved from with user-defined converters. - /// - public required string JSSpace { get; init; } - /// - /// C# name of the method, as specified in source code. - /// - public required string Name { get; init; } - /// - /// JavaScript name of the method (function), as will be specified in source code. - /// - public required string JSName { get; init; } - /// - /// Arguments of the method, in declaration order. - /// - public required IReadOnlyList Arguments { get; init; } - /// - /// Metadata of the value returned by the method. - /// - public required ValueMeta ReturnValue { get; init; } - - public override string ToString () - { - var args = string.Join(", ", Arguments.Select(a => a.ToString())); - return $"[{Kind}] {Assembly}.{Space}.{Name} ({args}) => {ReturnValue}"; - } -} diff --git a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs b/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs deleted file mode 100644 index d999d004..00000000 --- a/src/cs/Bootsharp.Common/Meta/SolutionMeta.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of the compiled solution. -/// -public sealed record SolutionMeta -{ - /// - /// Assemblies included in the solution. - /// - public required IReadOnlyCollection Assemblies { get; init; } - /// - /// Interop interfaces in the solution: supplied by user under either - /// or . - /// - public required IReadOnlyCollection Interfaces { get; init; } - /// - /// Interop methods in the solution: either top-level (eg, ) or - /// members of the interop classes generated for . - /// - public required IReadOnlyCollection Methods { get; init; } - /// - /// Types referenced in the interop methods signatures, including - /// types associated with the prior types, crawled recursively. - /// - public required IReadOnlyCollection Crawled { get; init; } -} diff --git a/src/cs/Bootsharp.Common/Meta/ValueMeta.cs b/src/cs/Bootsharp.Common/Meta/ValueMeta.cs deleted file mode 100644 index 8760e46a..00000000 --- a/src/cs/Bootsharp.Common/Meta/ValueMeta.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Bootsharp; - -/// -/// Bootsharp-specific metadata of interop method's argument or returned value. -/// -public sealed record ValueMeta -{ - /// - /// C# type of the value. - /// - public required Type Type { get; init; } - /// - /// C# syntax of the value type, as specified in source code. - /// - public required string TypeSyntax { get; init; } - /// - /// TypeScript syntax of the value type, to be specified in source code. - /// - public required string JSTypeSyntax { get; init; } - /// - /// Whether the value is optional/nullable. - /// - public required bool Nullable { get; init; } - /// - /// Whether the value type is of an async nature (eg, task or promise). - /// - public required bool Async { get; init; } - /// - /// Whether the value is void (when method return value). - /// - public required bool Void { get; init; } - /// - /// Whether the value has to be marshalled to/from JSON for interop. - /// - public required bool Serialized { get; init; } -} diff --git a/src/cs/Bootsharp.Common/Preferences.cs b/src/cs/Bootsharp.Common/Preferences.cs deleted file mode 100644 index 000aa389..00000000 --- a/src/cs/Bootsharp.Common/Preferences.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace Bootsharp; - -/// -/// User preferences for Bootsharp behaviour. Inherit, override methods and -/// supply inherited class type to . -/// -[SuppressMessage("ReSharper", "UnusedParameter.Global", - Justification = "Accessed in Bootsharp.Publish.Test via generated code.")] -public class Preferences -{ - /// - /// Resolves JavaScript namespace (object names chain) for specified C# type. - /// - /// - /// This affect both objects generated to host bindings and type names - /// of the values referenced in the bindings. When building binding host - /// object name, the is declaring type of the - /// associated method. - /// - /// C# type to resolve namespace from. - /// Result when resolved w/o the override. - public virtual string ResolveSpace (Type type, string @default) => @default; - /// - /// Resolves TypeScript type syntax of a function member or object property from associated C# type. - /// - /// C# type to resolve TypeScript syntax from. - /// C# nullability status, ie whether associated member is nullable/optional. - /// Result when resolved w/o the override. - public virtual string ResolveType (Type type, NullabilityInfo nullability, string @default) => @default; - /// - /// Resolves interop-specific metadata for an interop interface specified under - /// or . - /// - /// The interface type. - /// Whether the interface exports to or imports APIs from JavaScript. - /// Result when resolved w/o the override. - public virtual InterfaceMeta ResolveInterface (Type type, InterfaceKind kind, InterfaceMeta @default) => @default; - /// - /// Resolves interop-specific metadata for an interop method attributed with either - /// , or . - /// - /// Info about the interop method. - /// Whether the method is intended to be invoked in JavaScript or vice-versa. - /// Result when resolved w/o the override. - public virtual MethodMeta ResolveMethod (MethodInfo info, MethodKind kind, MethodMeta @default) => @default; -} diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs index c1c69d32..11321042 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs @@ -199,37 +199,31 @@ public class JSImported : global::IImported } [Fact] - public void RespectsResolveInterfacePref () + public void RespectsEventPreference () { AddAssembly(With( """ - [assembly:JSConfiguration] + [assembly:JSPreferences(Event = [@"^Broadcast(\S+)", "On$1"])] [assembly:JSImport(typeof(IImported))] - public class Prefs : Bootsharp.Preferences + public interface IImported { - public override InterfaceMeta ResolveInterface (Type _, InterfaceKind __, InterfaceMeta @default) - { - var method = ((IReadOnlyList)@default.Methods)[0]; - return @default with { - Name = "Foo", - Methods = [method with { Generated = method.Generated with { Space = "Bootsharp.Generated.Imports.Foo" } }] - }; - } + void NotifyFoo (); + void BroadcastBar (); } - - public interface IImported { void NotifyEvt (); } """)); Execute(); Contains( """ namespace Bootsharp.Generated.Imports { - public class Foo : global::IImported + public class JSImported : global::IImported { - [JSEvent] public static void OnEvt () => Proxies.Get("Bootsharp.Generated.Imports.Foo.OnEvt")(); + [JSFunction] public static void NotifyFoo () => Proxies.Get("Bootsharp.Generated.Imports.JSImported.NotifyFoo")(); + [JSEvent] public static void OnBar () => Proxies.Get("Bootsharp.Generated.Imports.JSImported.OnBar")(); - void global::IImported.NotifyEvt () => OnEvt(); + void global::IImported.NotifyFoo () => NotifyFoo(); + void global::IImported.BroadcastBar () => OnBar(); } } """); @@ -242,7 +236,7 @@ internal static class InterfaceRegistrations [System.Runtime.CompilerServices.ModuleInitializer] internal static void RegisterInterfaces () { - Interfaces.Register(typeof(global::IImported), new ImportInterface(new Bootsharp.Generated.Imports.Foo())); + Interfaces.Register(typeof(global::IImported), new ImportInterface(new Bootsharp.Generated.Imports.JSImported())); } } } diff --git a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs index e6fbe815..1730974b 100644 --- a/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Emit/InteropTest.cs @@ -157,21 +157,16 @@ public class Class } [Fact] - public void RespectsResolveSpacePref () + public void RespectsSpacePreference () { AddAssembly(With( """ - [assembly:JSConfiguration] + [assembly:JSPreferences(Space = [@"Space", "Foo"])] [assembly:JSExport(typeof(Space.IExported))] [assembly:JSImport(typeof(Space.IImported))] namespace Space; - public class Prefs : Bootsharp.Preferences - { - public override string ResolveSpace (Type type, string @default) => @default.Replace("Space", "Foo"); - } - public interface IExported { void Inv (); } public interface IImported { void Fun (); void NotifyEvt(); } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 19d49cd4..9f0bdad8 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -377,16 +377,14 @@ public void CustomEnumIndexesArePreservedInJS () } [Fact] - public void RespectsResolveSpacePref () + public void RespectsSpacePreference () { AddAssembly( With( """ - [assembly:JSConfiguration] - public class Prefs : Bootsharp.Preferences - { - public override string ResolveSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); - } + [assembly: Bootsharp.JSPreferences( + Space = [@"^Foo\.Bar\.(\S+)", "$1"] + )] """), WithClass("Foo.Bar.Nya", "[JSInvokable] public static Task GetNya () => Task.CompletedTask;"), WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun () {}")); @@ -409,19 +407,10 @@ public class Prefs : Bootsharp.Preferences } [Fact] - public void RespectsResolveMethodPref () + public void RespectsFunctionPreference () { AddAssembly( - With( - """ - [assembly:JSConfiguration] - - public class Prefs : Bootsharp.Preferences - { - public override MethodMeta ResolveMethod (System.Reflection.MethodInfo _, MethodKind __, MethodMeta @default) => - @default with { JSName = "foo" }; - } - """), + With("""[assembly:JSPreferences(Function = [@".+", "foo"])]"""), WithClass("Space", "[JSInvokable] public static void Inv () {}") ); Execute(); diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 1531e4cf..6b949ed7 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -643,16 +643,14 @@ public void WhenTypeReferencedMultipleTimesItsDeclaredOnlyOnce () } [Fact] - public void RespectsResolveSpacePref () + public void RespectsSpacePreference () { AddAssembly( With( """ - [assembly:JSConfiguration] - public class Prefs : Bootsharp.Preferences - { - public override string ResolveSpace (Type type, string @default) => @default.Replace("Foo.Bar.", ""); - } + [assembly: Bootsharp.JSPreferences( + Space = [@"^Foo\.Bar\.(\S+)", "$1"] + )] """), With("Foo.Bar.Nya", "public class Nya { }"), WithClass("Foo.Bar.Fun", "[JSFunction] public static void OnFun (Nya.Nya nya) { }")); @@ -662,17 +660,14 @@ public class Prefs : Bootsharp.Preferences } [Fact] - public void RespectsResolveTypePref () + public void RespectsTypePreference () { AddAssembly( With( """ - using System.Reflection; - [assembly:JSConfiguration] - public class Prefs : Bootsharp.Preferences - { - public override string ResolveType (Type _, NullabilityInfo __, string @default) => @default.Replace("Record", "Foo"); - } + [assembly: Bootsharp.JSPreferences( + Type = [@"Record", "Foo"] + )] """), With("public record Record;"), WithClass("[JSInvokable] public static void Inv (Record r) {}")); diff --git a/src/cs/Bootsharp.Publish.Test/TypesTest.cs b/src/cs/Bootsharp.Publish.Test/TypesTest.cs new file mode 100644 index 00000000..f13209d8 --- /dev/null +++ b/src/cs/Bootsharp.Publish.Test/TypesTest.cs @@ -0,0 +1,17 @@ +namespace Bootsharp.Publish.Test; + +public class TypesTest +{ + [Fact] + public void Temp () + { + // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 + _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; + _ = new InterfaceMeta { Kind = default, TypeSyntax = "", Name = "", Namespace = "", Methods = [] } with { Name = "foo" }; + _ = new InterfaceMethodMeta { Name = "", Generated = default } with { Name = "foo" }; + _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Kind = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; + _ = new ArgumentMeta { Name = "", JSName = "", Value = default } with { Name = "foo" }; + _ = new ValueMeta { Type = default, Nullable = true, TypeSyntax = "", Void = true, Serialized = true, Async = true, JSTypeSyntax = "" } with { TypeSyntax = "foo" }; + _ = new Preferences { Event = [] } with { Function = [] }; + } +} diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 3c1ed1ae..70bdc418 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -86,7 +86,7 @@ private void AddInterface (Type iType, InterfaceKind kind) methods.Add(method.Generated); } - private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => prefs.ResolveMethod(info, kind, new() { + private MethodMeta CreateMethod (MethodInfo info, MethodKind kind) => new() { Kind = kind, Assembly = info.DeclaringType!.Assembly.GetName().Name!, Space = info.DeclaringType.FullName!, @@ -101,9 +101,9 @@ private void AddInterface (Type iType, InterfaceKind kind) Void = IsVoid(info.ReturnType), Serialized = ShouldSerialize(info.ReturnType) }, - JSSpace = prefs.ResolveSpace(info.DeclaringType, BuildJSSpace(info.DeclaringType)), - JSName = ToFirstLower(info.Name) - }); + JSSpace = WithPrefs(prefs.Space, info.DeclaringType!.FullName!, BuildJSSpace(info.DeclaringType!)), + JSName = WithPrefs(prefs.Function, info.Name, ToFirstLower(info.Name)) + }; private ArgumentMeta CreateArgument (ParameterInfo info) => new() { Name = info.Name!, @@ -125,22 +125,21 @@ private InterfaceMeta CreateInterface (Type iType, InterfaceKind kind) if (iType.Namespace != null) space += $".{iType.Namespace}"; var name = "JS" + iType.Name[1..]; var mSpace = $"{space}.{name}"; - return prefs.ResolveInterface(iType, kind, new InterfaceMeta { + return new InterfaceMeta { Kind = kind, TypeSyntax = BuildSyntax(iType), Namespace = space, Name = name, Methods = iType.GetMethods().Select(m => CreateInterfaceMethod(m, kind, mSpace)).ToArray() - }); + }; } private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKind iKind, string space) { + var name = WithPrefs(prefs.Event, info.Name, info.Name); var mKind = iKind == InterfaceKind.Export ? MethodKind.Invokable - : info.Name.StartsWith("Notify", StringComparison.Ordinal) ? MethodKind.Event - : MethodKind.Function; - var name = mKind == MethodKind.Event ? $"On{info.Name[6..]}" : info.Name; - var jsSpace = prefs.ResolveSpace(info.DeclaringType!, BuildJSSpace(info.DeclaringType!)); + : name != info.Name ? MethodKind.Event : MethodKind.Function; + var jsSpace = WithPrefs(prefs.Space, info.DeclaringType!.FullName!, BuildJSSpace(info.DeclaringType!)); jsSpace = jsSpace[..(jsSpace.LastIndexOf('.') + 1)] + jsSpace[(jsSpace.LastIndexOf('.') + 2)..]; return new() { Name = info.Name, diff --git a/src/cs/Bootsharp.Publish/Common/Meta/ArgumentMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/ArgumentMeta.cs new file mode 100644 index 00000000..48fa07ba --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/ArgumentMeta.cs @@ -0,0 +1,10 @@ +namespace Bootsharp.Publish; + +internal sealed record ArgumentMeta +{ + public required string Name { get; init; } + public required string JSName { get; init; } + public required ValueMeta Value { get; init; } + + public override string ToString () => $"{Name}: {Value.JSTypeSyntax}"; +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs new file mode 100644 index 00000000..c52e2afb --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs @@ -0,0 +1,7 @@ +namespace Bootsharp.Publish; + +internal sealed record AssemblyMeta +{ + public required string Name { get; init; } + public required byte[] Bytes { get; init; } +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/InterfaceKind.cs b/src/cs/Bootsharp.Publish/Common/Meta/InterfaceKind.cs new file mode 100644 index 00000000..d5d90eb6 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/InterfaceKind.cs @@ -0,0 +1,7 @@ +namespace Bootsharp.Publish; + +internal enum InterfaceKind +{ + Export, + Import +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/InterfaceMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/InterfaceMeta.cs new file mode 100644 index 00000000..86744ad4 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/InterfaceMeta.cs @@ -0,0 +1,11 @@ +namespace Bootsharp.Publish; + +internal sealed record InterfaceMeta +{ + public required InterfaceKind Kind { get; init; } + public required string TypeSyntax { get; init; } + public required string Namespace { get; init; } + public required string Name { get; init; } + public string FullName => $"{Namespace}.{Name}"; + public required IReadOnlyCollection Methods { get; init; } +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/InterfaceMethodMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/InterfaceMethodMeta.cs new file mode 100644 index 00000000..0c8ba196 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/InterfaceMethodMeta.cs @@ -0,0 +1,7 @@ +namespace Bootsharp.Publish; + +internal sealed record InterfaceMethodMeta +{ + public required string Name { get; set; } + public required MethodMeta Generated { get; set; } +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/MethodKind.cs b/src/cs/Bootsharp.Publish/Common/Meta/MethodKind.cs new file mode 100644 index 00000000..5801aab0 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/MethodKind.cs @@ -0,0 +1,8 @@ +namespace Bootsharp.Publish; + +internal enum MethodKind +{ + Invokable, + Function, + Event +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/MethodMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/MethodMeta.cs new file mode 100644 index 00000000..e58d5007 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/MethodMeta.cs @@ -0,0 +1,19 @@ +namespace Bootsharp.Publish; + +internal sealed record MethodMeta +{ + public required MethodKind Kind { get; init; } + public required string Assembly { get; init; } + public required string Space { get; init; } + public required string JSSpace { get; init; } + public required string Name { get; init; } + public required string JSName { get; init; } + public required IReadOnlyList Arguments { get; init; } + public required ValueMeta ReturnValue { get; init; } + + public override string ToString () + { + var args = string.Join(", ", Arguments.Select(a => a.ToString())); + return $"[{Kind}] {Assembly}.{Space}.{Name} ({args}) => {ReturnValue}"; + } +} diff --git a/src/cs/Bootsharp.Publish/Common/Meta/ValueMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/ValueMeta.cs new file mode 100644 index 00000000..d1a1ae26 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Meta/ValueMeta.cs @@ -0,0 +1,12 @@ +namespace Bootsharp.Publish; + +internal sealed record ValueMeta +{ + public required Type Type { get; init; } + public required string TypeSyntax { get; init; } + public required string JSTypeSyntax { get; init; } + public required bool Nullable { get; init; } + public required bool Async { get; init; } + public required bool Void { get; init; } + public required bool Serialized { get; init; } +} diff --git a/src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs b/src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs new file mode 100644 index 00000000..55998dde --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs @@ -0,0 +1,3 @@ +namespace Bootsharp.Publish; + +internal sealed record Preference (string Pattern, string Replacement); diff --git a/src/cs/Bootsharp.Publish/Common/Preferences/Preferences.cs b/src/cs/Bootsharp.Publish/Common/Preferences/Preferences.cs new file mode 100644 index 00000000..7a9a5df4 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Preferences/Preferences.cs @@ -0,0 +1,9 @@ +namespace Bootsharp.Publish; + +internal sealed record Preferences +{ + public IReadOnlyList Space { get; init; } = []; + public IReadOnlyList Type { get; init; } = []; + public IReadOnlyList Event { get; init; } = []; + public IReadOnlyList Function { get; init; } = []; +} diff --git a/src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs b/src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs new file mode 100644 index 00000000..eaf8a8f0 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs @@ -0,0 +1,49 @@ +using System.Reflection; + +namespace Bootsharp.Publish; + +internal sealed class PreferencesResolver (string entryAssemblyName) +{ + public Preferences Resolve (string outDir) + { + using var ctx = CreateLoadContext(outDir); + var assemblyPath = Path.Combine(outDir, entryAssemblyName); + var assembly = ctx.LoadFromAssemblyPath(assemblyPath); + var attribute = FindPreferencesAttribute(assembly); + return CreatePreferences(attribute); + } + + private CustomAttributeData? FindPreferencesAttribute (Assembly assembly) + { + foreach (var attr in assembly.CustomAttributes) + if (attr.AttributeType.FullName == typeof(JSPreferencesAttribute).FullName) + return attr; + return null; + } + + private Preferences CreatePreferences (CustomAttributeData? attr) => new() { + Space = CreatePreferences(nameof(JSPreferencesAttribute.Space), attr) ?? [], + Type = CreatePreferences(nameof(JSPreferencesAttribute.Type), attr) ?? [], + Event = CreatePreferences(nameof(JSPreferencesAttribute.Event), attr) ?? [new(@"^Notify(\S+)", "On$1")], + Function = CreatePreferences(nameof(JSPreferencesAttribute.Function), attr) ?? [] + }; + + private Preference[]? CreatePreferences (string name, CustomAttributeData? attr) + { + if (attr is null || !attr.NamedArguments.Any(a => a.MemberName == name)) return null; + var value = CreateValue(attr.NamedArguments.First(a => a.MemberName == name).TypedValue); + var prefs = new Preference[value.Length / 2]; + for (int i = 0; i < prefs.Length; i++) + prefs[i] = new(value[i * 2], value[i * 2 + 1]); + return prefs; + } + + private string[] CreateValue (CustomAttributeTypedArgument arg) + { + var items = ((IEnumerable)arg.Value!).ToArray(); + var value = new string[items.Length]; + for (int i = 0; i < items.Length; i++) + value[i] = (string)items[i].Value!; + return value; + } +} diff --git a/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs deleted file mode 100644 index fe57e5a2..00000000 --- a/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Reflection; -using System.Runtime.Loader; - -namespace Bootsharp.Publish; - -// https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability -internal sealed class PreferencesContext (string outDir) : AssemblyLoadContext -{ - public Assembly LoadAssembly (string assemblyFileName) - { - using var stream = StreamFile(assemblyFileName); - return LoadFromStream(stream); - } - - protected override Assembly Load (AssemblyName name) - { - return LoadAssembly($"{name.Name}.dll"); - } - - private Stream StreamFile (string fileName) - { - var path = Path.GetFullPath(Path.Combine(outDir, fileName)); - return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - } -} diff --git a/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs b/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs deleted file mode 100644 index cf3e383d..00000000 --- a/src/cs/Bootsharp.Publish/Common/PreferencesResolver/PreferencesResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace Bootsharp.Publish; - -internal sealed class PreferencesResolver (string entryAssemblyName) -{ - public Preferences Resolve (string outDir) - { - var ctx = new PreferencesContext(outDir); - var assembly = ctx.LoadAssembly(entryAssemblyName); - if (FindConfigurationAttribute(assembly) is not { } attr) return new(); - return InstantiateCustomPrefs(assembly, attr.AttributeType); - } - - private CustomAttributeData? FindConfigurationAttribute (Assembly assembly) - { - var cfgAttr = typeof(JSConfigurationAttribute<>).FullName!; - foreach (var attr in assembly.CustomAttributes) - if (attr.AttributeType.FullName!.StartsWith(cfgAttr, StringComparison.Ordinal)) - return attr; - return null; - } - - private Preferences InstantiateCustomPrefs (Assembly assembly, Type attributeType) - { - var prefsType = attributeType.GenericTypeArguments[0]; - return Unsafe.As(assembly.CreateInstance(prefsType.FullName!)!); - } -} diff --git a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs index 47c2bbde..48a82014 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs @@ -14,7 +14,7 @@ public string ToTypeScript (Type type, NullabilityInfo nullability) this.nullability = nullability; // nullability of topmost type declarations is evaluated outside (method/property info) if (IsNullable(type)) type = GetNullableUnderlyingType(type); - return prefs.ResolveType(type, nullability, Convert(type)); + return Convert(type); } private string Convert (Type type) @@ -25,7 +25,7 @@ private string Convert (Type type) if (IsDictionary(type)) return ConvertDictionary(type); if (IsTaskLike(type)) return ConvertAwaitable(type); if (type.IsGenericType && CrawledTypes.Contains(type)) return ConvertGeneric(type); - return ConvertFinal(type); + return WithPrefs(prefs.Type, type.FullName!, ConvertFinal(type)); } private string ConvertNullable (Type type) @@ -67,13 +67,14 @@ private string ConvertGeneric (Type type) { EnterNullability(); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); - return $"{prefs.ResolveSpace(type, BuildJSSpace(type))}<{args}>"; + var name = WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); + return $"{name}<{args}>"; } private string ConvertFinal (Type type) { if (type.Name == "Void") return "void"; - if (CrawledTypes.Contains(type)) return prefs.ResolveSpace(type, BuildJSSpace(type)); + if (CrawledTypes.Contains(type)) return WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); return Type.GetTypeCode(type) switch { TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or diff --git a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs index c4a33422..068eda69 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; namespace Bootsharp.Publish; @@ -174,6 +175,14 @@ public static string BuildJSSpace (Type type) return space; } + public static string WithPrefs (IReadOnlyCollection prefs, string input, string @default) + { + foreach (var pref in prefs) + if (Regex.IsMatch(input, pref.Pattern)) + return Regex.Replace(input, pref.Pattern, pref.Replacement); + return @default; + } + public static string BuildSyntax (Type type) => BuildSyntax(type, null, false); public static string BuildSyntax (Type type, ParameterInfo info) => BuildSyntax(type, GetNullability(info)); diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 04f5b7a8..005f6818 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -20,7 +20,7 @@ public string Generate (AssemblyInspection inspection) bindings = inspection.Methods .Select(m => new Binding(m, null, m.JSSpace)) .Concat(inspection.Crawled.Where(t => t.IsEnum) - .Select(t => new Binding(null, t, prefs.ResolveSpace(t, BuildJSSpace(t))))) + .Select(t => new Binding(null, t, WithPrefs(prefs.Space, t.FullName!, BuildJSSpace(t))))) .OrderBy(m => m.Namespace).ToArray(); if (bindings.Length == 0) return ""; EmitImports(); diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index 475f834b..0a528c7b 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -85,7 +85,7 @@ private void DeclareEnum () private string GetNamespace (Type type) { - var space = prefs.ResolveSpace(type, BuildJSSpace(type)); + var space = WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); var lastDotIdx = space.LastIndexOf('.'); return lastDotIdx >= 0 ? space[..lastDotIdx] : space; } @@ -96,7 +96,7 @@ private void AppendExtensions () if (type.BaseType is { } baseType && types.Contains(baseType)) extTypes.Insert(0, baseType); if (extTypes.Count > 0) - builder.Append(" extends ").AppendJoin(", ", extTypes.Select(t => prefs.ResolveSpace(t, BuildJSSpace(t)))); + builder.Append(" extends ").AppendJoin(", ", extTypes.Select(t => WithPrefs(prefs.Space, t.FullName!, BuildJSSpace(t)))); } private void AppendProperties () @@ -136,7 +136,7 @@ private void Append (string content, int level) private string BuildTypeName (Type type) { - var space = prefs.ResolveSpace(type, BuildJSSpace(type)); + var space = WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); var name = space[(space.LastIndexOf('.') + 1)..]; if (!type.IsGenericType) return name; var args = string.Join(", ", type.GetGenericArguments().Select(BuildTypeName)); diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 9f5d51cf..dad57367 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.26 + 0.2.0-alpha.30 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 4d841bd2c7f3b6b99159e837cc5c5d323b9e2ead Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 22 Jan 2024 03:45:39 +0300 Subject: [PATCH 68/75] iteration --- .../Attributes/JSPreferencesAttribute.cs | 12 ++- .../Pack/BindingTest.cs | 12 +-- .../Pack/DeclarationTest.cs | 76 ++++++++++++++++--- .../AssemblyInspector/AssemblyInspector.cs | 17 +++-- .../Common/TypeConverter/TypeConverter.cs | 14 ++-- .../Bootsharp.Publish/Common/TypeUtilities.cs | 25 ++++-- .../Pack/BindingGenerator.cs | 4 +- .../TypeDeclarationGenerator.cs | 19 ++--- src/cs/Directory.Build.props | 2 +- 9 files changed, 125 insertions(+), 56 deletions(-) diff --git a/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs index 02bfab4d..7e9905e9 100644 --- a/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs +++ b/src/cs/Bootsharp.Common/Attributes/JSPreferencesAttribute.cs @@ -9,7 +9,7 @@ /// is tested in order; on first match the result replaces the default. /// /// -/// Make all bindings declared under "Foo.Bar" C# namespace have "Baz" namespace in JavaScript: +/// Make all spaces starting with "Foo.Bar" replaced with "Baz": /// /// [assembly: Bootsharp.JSPreferences( /// Space = ["^Foo\.Bar\.(\S+)", "Baz.$1"] @@ -23,9 +23,13 @@ public sealed class JSPreferencesAttribute : Attribute /// Customize generated JavaScript object names and TypeScript namespaces. /// /// - /// The patterns are matched against full names of the declaring C# types, - /// affecting both objects generated to host bindings and type names - /// of the values referenced in the bindings. + /// The patterns are matched against full type name (namespace.typename) of + /// declaring C# type when generating JavaScript objects for interop methods + /// and against namespace when generating TypeScript syntax for C# types. + /// Matched type names have following modifications:
+ /// - interfaces have first character removed
+ /// - generics have parameter spec removed
+ /// - nested have "+" replaced with "."
///
public string[] Space { get; init; } = []; /// diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 9f0bdad8..2f6c19b4 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -348,9 +348,7 @@ public void ExportedEnumsAreDeclaredInJS () export const n = { Class: { getFoo: () => deserialize(getExports().n_Class_GetFoo()), - Foo: { - "0": "A", "1": "B", "A": 0, "B": 1 - } + Foo: { "0": "A", "1": "B", "A": 0, "B": 1 } } }; """); @@ -360,17 +358,15 @@ public void ExportedEnumsAreDeclaredInJS () public void CustomEnumIndexesArePreservedInJS () { AddAssembly( - WithClass("n", "public enum Foo { A = 1, B = 6 }"), + With("n", "public enum Foo { A = 1, B = 6 }"), WithClass("n", "[JSInvokable] public static Foo GetFoo () => default;")); Execute(); Contains( """ export const n = { + Foo: { "1": "A", "6": "B", "A": 1, "B": 6 }, Class: { - getFoo: () => deserialize(getExports().n_Class_GetFoo()), - Foo: { - "1": "A", "6": "B", "A": 1, "B": 6 - } + getFoo: () => deserialize(getExports().n_Class_GetFoo()) } }; """); diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 6b949ed7..b2a9b4cf 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -47,12 +47,12 @@ public void WhenNoNamespaceDeclaresUnderRoot () Execute(); Contains( """ + export interface Record { + } export enum Enum { A, B } - export interface Record { - } export namespace Class { export function inv(r: Record): Enum; @@ -60,6 +60,26 @@ export namespace Class { """); } + [Fact] + public void NestedTypesAreDeclaredUnderClassSpace () + { + AddAssembly( + With("public class Foo { public record Bar; }"), + WithClass("[JSInvokable] public static void Inv (Foo.Bar r) {}")); + Execute(); + Contains( + """ + export namespace Foo { + export interface Bar { + } + } + + export namespace Class { + export function inv(r: Foo.Bar): void; + } + """); + } + [Fact] public void FunctionDeclarationIsExportedForInvokableMethod () { @@ -392,11 +412,21 @@ export namespace n.Class { public void DefinitionIsGeneratedForGenericInterface () { AddAssembly( - With("n", "public interface GenericInterface { public T Value { get; set; } }"), - WithClass("n", "[JSInvokable] public static GenericInterface Method () => default;")); + With("n", "public interface IGenericInterface { public T Value { get; set; } }"), + WithClass("n", "[JSInvokable] public static IGenericInterface Method () => default;")); Execute(); - Matches(@"export interface GenericInterface {\s*value\?: T;\s*}"); - Contains("method(): n.GenericInterface"); + Contains( + """ + export namespace n { + export interface IGenericInterface { + value?: T; + } + } + + export namespace n.Class { + export function method(): n.IGenericInterface; + } + """); } [Fact] @@ -430,11 +460,22 @@ export namespace n.Class { public void DefinitionIsGeneratedForGenericClassWithMultipleTypeArguments () { AddAssembly( - WithClass("n", "public class GenericClass { public T1 Key { get; set; } public T2 Value { get; set; } }"), + With("n", "public class GenericClass { public T1 Key { get; set; } public T2 Value { get; set; } }"), WithClass("n", "[JSInvokable] public static void Method (GenericClass p) { }")); Execute(); - Matches(@"export interface GenericClass {\s*key\?: T1;\s*value\?: T2;\s*}"); - Contains("method(p: n.Class.GenericClass): void"); + Contains( + """ + export namespace n { + export interface GenericClass { + key?: T1; + value?: T2; + } + } + + export namespace n.Class { + export function method(p: n.GenericClass): void; + } + """); } [Fact] @@ -666,12 +707,23 @@ public void RespectsTypePreference () With( """ [assembly: Bootsharp.JSPreferences( - Type = [@"Record", "Foo"] + Type = [@"Record", "Foo", @".+`.+", "Bar"] )] """), With("public record Record;"), - WithClass("[JSInvokable] public static void Inv (Record r) {}")); + With("public record Generic;"), + WithClass("[JSInvokable] public static void Inv (Record r, Generic g) {}")); Execute(); - Contains("inv(r: Foo): void"); + Contains( + """ + export interface Record { + } + export interface Generic { + } + + export namespace Class { + export function inv(r: Foo, g: Bar): void; + } + """); } } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index 70bdc418..c99d07cc 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -101,7 +101,7 @@ private void AddInterface (Type iType, InterfaceKind kind) Void = IsVoid(info.ReturnType), Serialized = ShouldSerialize(info.ReturnType) }, - JSSpace = WithPrefs(prefs.Space, info.DeclaringType!.FullName!, BuildJSSpace(info.DeclaringType!)), + JSSpace = BuildMethodSpace(info), JSName = WithPrefs(prefs.Function, info.Name, ToFirstLower(info.Name)) }; @@ -124,13 +124,12 @@ private InterfaceMeta CreateInterface (Type iType, InterfaceKind kind) var space = "Bootsharp.Generated." + (kind == InterfaceKind.Export ? "Exports" : "Imports"); if (iType.Namespace != null) space += $".{iType.Namespace}"; var name = "JS" + iType.Name[1..]; - var mSpace = $"{space}.{name}"; return new InterfaceMeta { Kind = kind, TypeSyntax = BuildSyntax(iType), Namespace = space, Name = name, - Methods = iType.GetMethods().Select(m => CreateInterfaceMethod(m, kind, mSpace)).ToArray() + Methods = iType.GetMethods().Select(m => CreateInterfaceMethod(m, kind, $"{space}.{name}")).ToArray() }; } @@ -139,17 +138,23 @@ private InterfaceMethodMeta CreateInterfaceMethod (MethodInfo info, InterfaceKin var name = WithPrefs(prefs.Event, info.Name, info.Name); var mKind = iKind == InterfaceKind.Export ? MethodKind.Invokable : name != info.Name ? MethodKind.Event : MethodKind.Function; - var jsSpace = WithPrefs(prefs.Space, info.DeclaringType!.FullName!, BuildJSSpace(info.DeclaringType!)); - jsSpace = jsSpace[..(jsSpace.LastIndexOf('.') + 1)] + jsSpace[(jsSpace.LastIndexOf('.') + 2)..]; return new() { Name = info.Name, Generated = CreateMethod(info, mKind) with { Assembly = entryAssemblyName, Space = space, Name = name, - JSSpace = jsSpace, JSName = ToFirstLower(name) } }; } + + private string BuildMethodSpace (MethodInfo info) + { + var space = info.DeclaringType!.Namespace ?? ""; + var name = BuildJSSpaceName(info.DeclaringType); + if (info.DeclaringType.IsInterface) name = name[1..]; + var fullname = string.IsNullOrEmpty(space) ? name : $"{space}.{name}"; + return WithPrefs(prefs.Space, fullname, fullname); + } } diff --git a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs index 48a82014..0e6a8bf8 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeConverter/TypeConverter.cs @@ -7,14 +7,14 @@ internal sealed class TypeConverter (Preferences prefs) public IReadOnlyCollection CrawledTypes => crawler.Crawled; private readonly TypeCrawler crawler = new(); - private NullabilityInfo nullability = null!; + private NullabilityInfo? nullability; - public string ToTypeScript (Type type, NullabilityInfo nullability) + public string ToTypeScript (Type type, NullabilityInfo? nullability) { this.nullability = nullability; // nullability of topmost type declarations is evaluated outside (method/property info) if (IsNullable(type)) type = GetNullableUnderlyingType(type); - return Convert(type); + return WithPrefs(prefs.Type, type.FullName!, Convert(type)); } private string Convert (Type type) @@ -25,7 +25,7 @@ private string Convert (Type type) if (IsDictionary(type)) return ConvertDictionary(type); if (IsTaskLike(type)) return ConvertAwaitable(type); if (type.IsGenericType && CrawledTypes.Contains(type)) return ConvertGeneric(type); - return WithPrefs(prefs.Type, type.FullName!, ConvertFinal(type)); + return ConvertFinal(type); } private string ConvertNullable (Type type) @@ -67,14 +67,13 @@ private string ConvertGeneric (Type type) { EnterNullability(); var args = string.Join(", ", type.GenericTypeArguments.Select(Convert)); - var name = WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); - return $"{name}<{args}>"; + return $"{BuildJSSpaceFullName(type, prefs)}<{args}>"; } private string ConvertFinal (Type type) { if (type.Name == "Void") return "void"; - if (CrawledTypes.Contains(type)) return WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); + if (CrawledTypes.Contains(type)) return BuildJSSpaceFullName(type, prefs); return Type.GetTypeCode(type) switch { TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or @@ -89,6 +88,7 @@ TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or private bool EnterNullability () { + if (nullability == null) return false; if (nullability.GenericTypeArguments.Length > 0) nullability = nullability.GenericTypeArguments[0]; else if (nullability.ElementType != null) nullability = nullability.ElementType; else return false; diff --git a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs index 068eda69..a894bfbc 100644 --- a/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs +++ b/src/cs/Bootsharp.Publish/Common/TypeUtilities.cs @@ -167,12 +167,27 @@ public static bool ShouldSerialize (Type type) return !native.Contains(type.FullName!); } - public static string BuildJSSpace (Type type) + public static string BuildJSSpace (Type type, Preferences prefs) { - var space = type.FullName ?? type.Name; - if (type.IsGenericType) space = GetGenericNameWithoutArgs(space); - if (space.Contains('+')) space = space.Replace("+", "."); - return space; + var space = type.Namespace ?? ""; + if (type.IsNested) + { + if (!string.IsNullOrEmpty(space)) space += "."; + space += type.DeclaringType!.Name; + } + return WithPrefs(prefs.Space, space, space); + } + + public static string BuildJSSpaceName (Type type) + { + return type.IsGenericType ? GetGenericNameWithoutArgs(type.Name) : type.Name; + } + + public static string BuildJSSpaceFullName (Type type, Preferences prefs) + { + var space = BuildJSSpace(type, prefs); + var name = BuildJSSpaceName(type); + return string.IsNullOrEmpty(space) ? name : $"{space}.{name}"; } public static string WithPrefs (IReadOnlyCollection prefs, string input, string @default) diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 005f6818..15f3848f 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -20,7 +20,7 @@ public string Generate (AssemblyInspection inspection) bindings = inspection.Methods .Select(m => new Binding(m, null, m.JSSpace)) .Concat(inspection.Crawled.Where(t => t.IsEnum) - .Select(t => new Binding(null, t, WithPrefs(prefs.Space, t.FullName!, BuildJSSpace(t))))) + .Select(t => new Binding(null, t, BuildJSSpace(t, prefs)))) .OrderBy(m => m.Namespace).ToArray(); if (bindings.Length == 0) return ""; EmitImports(); @@ -143,7 +143,7 @@ private void EmitEnum (Type @enum) var fields = string.Join(", ", values .Select(v => $"\"{v}\": \"{Enum.GetName(@enum, v)}\"") .Concat(values.Select(v => $"\"{Enum.GetName(@enum, v)}\": {v}"))); - builder.Append($"{Break()}{fields}"); + builder.Append($"{Break()}{@enum.Name}: {{ {fields} }}"); } private bool ShouldWait (MethodMeta method) => diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs index 0a528c7b..e9f8d803 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/TypeDeclarationGenerator.cs @@ -39,7 +39,7 @@ private void DeclareType () private bool ShouldOpenNamespace () { - if (type.Namespace == null) return false; + if (string.IsNullOrEmpty(GetNamespace(type))) return false; if (prevType == null) return true; return GetNamespace(prevType) != GetNamespace(type); } @@ -52,7 +52,7 @@ private void OpenNamespace () private bool ShouldCloseNamespace () { - if (type.Namespace == null) return false; + if (string.IsNullOrEmpty(GetNamespace(type))) return false; if (nextType is null) return true; return GetNamespace(nextType) != GetNamespace(type); } @@ -64,7 +64,7 @@ private void CloseNamespace () private void DeclareInterface () { - var indent = type.Namespace != null ? 1 : 0; + var indent = !string.IsNullOrEmpty(GetNamespace(type)) ? 1 : 0; AppendLine($"export interface {BuildTypeName(type)}", indent); AppendExtensions(); builder.Append(" {"); @@ -74,7 +74,7 @@ private void DeclareInterface () private void DeclareEnum () { - var indent = type.Namespace != null ? 1 : 0; + var indent = !string.IsNullOrEmpty(GetNamespace(type)) ? 1 : 0; AppendLine($"export enum {type.Name} {{", indent); var names = Enum.GetNames(type); for (int i = 0; i < names.Length; i++) @@ -85,9 +85,7 @@ private void DeclareEnum () private string GetNamespace (Type type) { - var space = WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); - var lastDotIdx = space.LastIndexOf('.'); - return lastDotIdx >= 0 ? space[..lastDotIdx] : space; + return BuildJSSpace(type, prefs); } private void AppendExtensions () @@ -96,7 +94,7 @@ private void AppendExtensions () if (type.BaseType is { } baseType && types.Contains(baseType)) extTypes.Insert(0, baseType); if (extTypes.Count > 0) - builder.Append(" extends ").AppendJoin(", ", extTypes.Select(t => WithPrefs(prefs.Space, t.FullName!, BuildJSSpace(t)))); + builder.Append(" extends ").AppendJoin(", ", extTypes.Select(t => converter.ToTypeScript(t, null))); } private void AppendProperties () @@ -109,7 +107,7 @@ private void AppendProperties () private void AppendProperty (PropertyInfo property) { - var indent = type.Namespace != null ? 1 : 0; + var indent = !string.IsNullOrEmpty(GetNamespace(type)) ? 1 : 0; AppendLine(ToFirstLower(property.Name), indent + 1); if (IsNullable(property)) builder.Append('?'); builder.Append($": {BuildType()};"); @@ -136,8 +134,7 @@ private void Append (string content, int level) private string BuildTypeName (Type type) { - var space = WithPrefs(prefs.Space, type.FullName!, BuildJSSpace(type)); - var name = space[(space.LastIndexOf('.') + 1)..]; + var name = BuildJSSpaceName(type); if (!type.IsGenericType) return name; var args = string.Join(", ", type.GetGenericArguments().Select(BuildTypeName)); return $"{name}<{args}>"; diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index dad57367..b8fa9b67 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.30 + 0.2.0-alpha.31 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 40a48d7a69b7ab5f8b91bbb1be5ead9765b7c07e Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 22 Jan 2024 04:26:43 +0300 Subject: [PATCH 69/75] fix gen --- src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs | 13 +++++++++++++ .../Pack/DeclarationTest.cs | 15 +++++++++++++++ src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs | 1 + .../DeclarationGenerator/DeclarationGenerator.cs | 14 ++++++++++++-- src/cs/Directory.Build.props | 2 +- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 2f6c19b4..0d3bc7d9 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -419,4 +419,17 @@ public void RespectsFunctionPreference () }; """); } + + [Fact] + public void IgnoresBindingsInGeneratedNamespace () + { + AddAssembly(With("Bootsharp.Generated", + """ + public static class Exports { [JSInvokable] public static void Inv () {} } + public static class Imports { [JSFunction] public static void Fun () {} } + """)); + Execute(); + Assert.DoesNotContain("inv: () =>", TestedContent); + Assert.DoesNotContain("get fun()", TestedContent); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index b2a9b4cf..754b897b 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -726,4 +726,19 @@ export namespace Class { } """); } + + [Fact] + public void IgnoresBindingsInGeneratedNamespace () + { + AddAssembly(With("Bootsharp.Generated", + """ + public record Record; + public static class Exports { [JSInvokable] public static void Inv (Record r) {} } + public static class Imports { [JSFunction] public static void Fun () {} } + """)); + Execute(); + Assert.DoesNotContain("Record", TestedContent); + Assert.DoesNotContain("export function inv", TestedContent); + Assert.DoesNotContain("export let fun", TestedContent); + } } diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 15f3848f..6a46b092 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -18,6 +18,7 @@ private record Binding (MethodMeta? Method, Type? Enum, string Namespace); public string Generate (AssemblyInspection inspection) { bindings = inspection.Methods + .Where(m => !m.Space.StartsWith("Bootsharp.Generated", StringComparison.Ordinal)) .Select(m => new Binding(m, null, m.JSSpace)) .Concat(inspection.Crawled.Where(t => t.IsEnum) .Select(t => new Binding(null, t, BuildJSSpace(t, prefs)))) diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs index 0e9b507b..aa19e2a1 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs @@ -7,7 +7,17 @@ internal sealed class DeclarationGenerator (Preferences prefs) public string Generate (AssemblyInspection inspection) => JoinLines(0, """import type { Event } from "./event";""", - typesGenerator.Generate(inspection.Crawled), - methodsGenerator.Generate(inspection.Methods) + typesGenerator.Generate(GetTypesToGenerateDeclarationsFor(inspection)), + methodsGenerator.Generate(GetMethodsToGenerateDeclarationsFor(inspection)) ) + "\n"; + + private IEnumerable GetTypesToGenerateDeclarationsFor (AssemblyInspection inspection) + { + return inspection.Crawled.Where(t => !t.Namespace?.StartsWith("Bootsharp.Generated") ?? true); + } + + private IEnumerable GetMethodsToGenerateDeclarationsFor (AssemblyInspection inspection) + { + return inspection.Methods.Where(m => !m.Space.StartsWith("Bootsharp.Generated")); + } } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index b8fa9b67..f1fbcf3d 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.31 + 0.2.0-alpha.32 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From c0af39cbfcfe9d061939812ddc638f7ca92191a2 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 22 Jan 2024 04:47:13 +0300 Subject: [PATCH 70/75] fix dup --- .../Common/AssemblyInspector/AssemblyInspector.cs | 1 + src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs | 1 - .../DeclarationGenerator/DeclarationGenerator.cs | 14 ++------------ src/cs/Directory.Build.props | 2 +- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index c99d07cc..cf3125b0 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -57,6 +57,7 @@ private void InspectAssembly (Assembly assembly) private void InspectExportedType (Type type) { + if (type.Namespace?.StartsWith("Bootsharp.Generated") ?? false) return; foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) foreach (var attr in method.CustomAttributes) if (attr.AttributeType.FullName == typeof(JSInvokableAttribute).FullName) diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs index 6a46b092..15f3848f 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator.cs @@ -18,7 +18,6 @@ private record Binding (MethodMeta? Method, Type? Enum, string Namespace); public string Generate (AssemblyInspection inspection) { bindings = inspection.Methods - .Where(m => !m.Space.StartsWith("Bootsharp.Generated", StringComparison.Ordinal)) .Select(m => new Binding(m, null, m.JSSpace)) .Concat(inspection.Crawled.Where(t => t.IsEnum) .Select(t => new Binding(null, t, BuildJSSpace(t, prefs)))) diff --git a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs index aa19e2a1..0e9b507b 100644 --- a/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/DeclarationGenerator/DeclarationGenerator.cs @@ -7,17 +7,7 @@ internal sealed class DeclarationGenerator (Preferences prefs) public string Generate (AssemblyInspection inspection) => JoinLines(0, """import type { Event } from "./event";""", - typesGenerator.Generate(GetTypesToGenerateDeclarationsFor(inspection)), - methodsGenerator.Generate(GetMethodsToGenerateDeclarationsFor(inspection)) + typesGenerator.Generate(inspection.Crawled), + methodsGenerator.Generate(inspection.Methods) ) + "\n"; - - private IEnumerable GetTypesToGenerateDeclarationsFor (AssemblyInspection inspection) - { - return inspection.Crawled.Where(t => !t.Namespace?.StartsWith("Bootsharp.Generated") ?? true); - } - - private IEnumerable GetMethodsToGenerateDeclarationsFor (AssemblyInspection inspection) - { - return inspection.Methods.Where(m => !m.Space.StartsWith("Bootsharp.Generated")); - } } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index f1fbcf3d..4afd09ae 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.32 + 0.2.0-alpha.33 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 1c741760f27f1cb3aba4d38c0a33f484e639ef8d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:45:08 +0300 Subject: [PATCH 71/75] etc --- src/cs/Bootsharp.Common/Attributes/JSEventAttribute.cs | 2 +- src/cs/Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp.Common/Attributes/JSEventAttribute.cs b/src/cs/Bootsharp.Common/Attributes/JSEventAttribute.cs index 88cdca43..c9f913f6 100644 --- a/src/cs/Bootsharp.Common/Attributes/JSEventAttribute.cs +++ b/src/cs/Bootsharp.Common/Attributes/JSEventAttribute.cs @@ -7,7 +7,7 @@ /// /// /// [JSEvent] -/// public static partial string OnSomethingHappened (string payload); +/// public static partial void OnSomethingHappened (string payload); /// Namespace.onSomethingHappened.subscribe(payload => ...); /// /// diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 4afd09ae..60ad3616 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.33 + 0.2.0-alpha.47 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 98ec42aac2aa01268f6446b90a4d0d9afcfe4778 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 23 Jan 2024 05:00:19 +0300 Subject: [PATCH 72/75] testing --- .../Pack/BindingTest.cs | 65 +++++++++++++++++ .../Pack/DeclarationTest.cs | 69 +++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index 0d3bc7d9..b3761b9f 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -432,4 +432,69 @@ public static class Imports { [JSFunction] public static void Fun () {} } Assert.DoesNotContain("inv: () =>", TestedContent); Assert.DoesNotContain("get fun()", TestedContent); } + + [Fact] + public void GeneratesForExportImportInterfaces () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(Space.IImported))] + + namespace Space; + + public enum Enum { A, B } + + public interface IExported { void Inv (string s, Enum e); } + public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string s, Enum e); } + """)); + Execute(); + Contains( + """ + export const Space = { + Enum: { "0": "A", "1": "B", "A": 0, "B": 1 }, + Exported: { + inv: (s, e) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e)) + }, + Imported: { + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = (s, e) => this.funHandler(s, deserialize(e)); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Space.Imported.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; }, + onEvt: new Event(), + onEvtSerialized: (s, e) => Space.Imported.onEvt.broadcast(s, deserialize(e)) + } + }; + """); + } + + [Fact] + public void GeneratesForExportImportInterfacesWithSpacePref () + { + AddAssembly(With( + """ + [assembly:JSPreferences(Space = [@".+", "Foo"])] + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(Space.IImported))] + + namespace Space; + + public enum Enum { A, B } + + public interface IExported { void Inv (string s, Enum e); } + public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string s, Enum e); } + """)); + Execute(); + Contains( + """ + export const Foo = { + inv: (s, e) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e)), + get fun() { return this.funHandler; }, + set fun(handler) { this.funHandler = handler; this.funSerializedHandler = (s, e) => this.funHandler(s, deserialize(e)); }, + get funSerialized() { if (typeof this.funHandler !== "function") throw Error("Failed to invoke 'Foo.fun' from C#. Make sure to assign function in JavaScript."); return this.funSerializedHandler; }, + onEvt: new Event(), + onEvtSerialized: (s, e) => Foo.onEvt.broadcast(s, deserialize(e)), + Enum: { "0": "A", "1": "B", "A": 0, "B": 1 } + }; + """); + } } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs index 754b897b..e38d1686 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/DeclarationTest.cs @@ -741,4 +741,73 @@ public static class Imports { [JSFunction] public static void Fun () {} } Assert.DoesNotContain("export function inv", TestedContent); Assert.DoesNotContain("export let fun", TestedContent); } + + [Fact] + public void GeneratesForExportImportInterfaces () + { + AddAssembly(With( + """ + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(Space.IImported))] + + namespace Space; + + public enum Enum { A, B } + + public interface IExported { void Inv (string s, Enum e); } + public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string s, Enum e); } + """)); + Execute(); + Contains( + """ + export namespace Space { + export enum Enum { + A, + B + } + } + + export namespace Space.Exported { + export function inv(s: string, e: Space.Enum): void; + } + export namespace Space.Imported { + export let fun: (s: string, e: Space.Enum) => void; + export const onEvt: Event<[s: string, e: Space.Enum]>; + } + """); + } + + [Fact] + public void GeneratesForExportImportInterfacesWithSpacePref () + { + AddAssembly(With( + """ + [assembly:JSPreferences(Space = [@".+", "Foo"])] + [assembly:JSExport(typeof(Space.IExported))] + [assembly:JSImport(typeof(Space.IImported))] + + namespace Space; + + public enum Enum { A, B } + + public interface IExported { void Inv (string s, Enum e); } + public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string s, Enum e); } + """)); + Execute(); + Contains( + """ + export namespace Foo { + export enum Enum { + A, + B + } + } + + export namespace Foo { + export function inv(s: string, e: Foo.Enum): void; + export let fun: (s: string, e: Foo.Enum) => void; + export const onEvt: Event<[s: string, e: Foo.Enum]>; + } + """); + } } From 77b692d8ae74e659448c518e6b5b2a5877482c8d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:05:46 +0300 Subject: [PATCH 73/75] fix inspect --- src/cs/Bootsharp/Build/Bootsharp.targets | 2 +- src/cs/Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index f4bc7f8c..b2561cb8 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -110,7 +110,7 @@ - 0.2.0-alpha.47 + 0.2.0-alpha.58 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From ad0c4b41ff99e87ef8bd6a827d310541263faaa9 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:00:05 +0300 Subject: [PATCH 74/75] fix inspection --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 2 -- .../Pack/AssemblyInspectionTest.cs | 15 ++++++--- .../Bootsharp.Publish.Test/Pack/PackTest.cs | 6 ++++ .../Pack/ResourceTest.cs | 6 ---- src/cs/Bootsharp.Publish.Test/TaskTest.cs | 2 +- src/cs/Bootsharp.Publish.Test/TypesTest.cs | 1 - .../AssemblyInspector/AssemblyInspection.cs | 1 - .../AssemblyInspector/AssemblyInspector.cs | 12 ++----- .../AssemblyInspector/InspectionReporter.cs | 6 ++-- .../Common/Meta/AssemblyMeta.cs | 7 ----- .../Bootsharp.Publish/Emit/BootsharpEmit.cs | 3 +- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 10 ++++-- .../Pack/ResourceGenerator.cs | 31 +++++++------------ src/cs/Directory.Build.props | 2 +- 14 files changed, 43 insertions(+), 61 deletions(-) delete mode 100644 src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index 7d0a13b8..25f5fae2 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -43,8 +43,6 @@ public void ImportParametersEqualArguments () (import.ConstructorArguments[0].Value as IReadOnlyCollection).Select(a => a.Value)); } - private static object GetNamedValue (IList args, string key) => - args.First(a => a.MemberName == key).TypedValue.Value; private static CustomAttributeData GetMockExportAttribute () => typeof(TypesTest).Assembly.CustomAttributes .First(a => a.AttributeType == typeof(JSExportAttribute)); diff --git a/src/cs/Bootsharp.Publish.Test/Pack/AssemblyInspectionTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/AssemblyInspectionTest.cs index 2ee0fd50..6d96f311 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/AssemblyInspectionTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/AssemblyInspectionTest.cs @@ -5,17 +5,22 @@ public class AssemblyInspectionTest : PackTest [Fact] public void AllAssembliesAreInspected () { - AddAssembly("Foo.dll"); + AddAssembly("foo.dll", + WithClass("[JSInvokable] public static void Inv () {}") + ); Execute(); - Assert.Contains(Engine.Messages, w => w.Contains("Foo")); - Assert.Contains(Engine.Messages, w => w.Contains("Bootsharp.Common")); - Assert.Contains(Engine.Messages, w => w.Contains("System.Runtime")); - Assert.Contains(Engine.Messages, w => w.Contains("System.Private.CoreLib")); + Assert.Contains(Engine.Messages, w => w.Contains("foo")); } [Fact] public void WhenAssemblyInspectionFailsWarningIsLogged () { + AddAssembly("foo.dll", + WithClass("[JSInvokable] public static void InvFoo () {}") + ); + AddAssembly("bar.dll", + WithClass("[JSInvokable] public static void InvBar () {}") + ); File.WriteAllText(Path.Combine(Project.Root, "foo.dll"), "corrupted"); Execute(); Assert.Contains(Engine.Warnings, w => w.Contains("Failed to inspect 'foo.dll' assembly")); diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs index 59a3b7a4..1188419b 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs @@ -32,6 +32,12 @@ public override void Execute () Task.Execute(); } + protected override void AddAssembly (string assemblyName, params MockSource[] sources) + { + base.AddAssembly(assemblyName, sources); + Project.WriteFile(assemblyName[..^3] + "wasm", ""); + } + private BootsharpPack CreateTask () => new() { BuildDirectory = Project.Root, InspectedDirectory = Project.Root, diff --git a/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs index d1b17c29..af2edb48 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs @@ -20,9 +20,6 @@ public void BinariesEmbeddedWhenEnabled () Execute(); Contains($$"""wasm: { name: "dotnet.native.wasm", content: "{{Convert.ToBase64String(MockWasmBinary)}}" },"""); Contains("{ name: \"Foo.wasm\", content: \""); - Contains("{ name: \"Bootsharp.Common.wasm\", content: \""); - Contains("{ name: \"System.Runtime.wasm\", content: \""); - Contains("{ name: \"System.Private.CoreLib.wasm\", content: \""); } [Fact] @@ -33,8 +30,5 @@ public void BinariesNotEmbeddedWhenDisabled () Execute(); Contains("""wasm: { name: "dotnet.native.wasm", content: undefined },"""); Contains("""{ name: "Foo.wasm", content: undefined"""); - Contains("""{ name: "Bootsharp.Common.wasm", content: undefined"""); - Contains("""{ name: "System.Runtime.wasm", content: undefined"""); - Contains("""{ name: "System.Private.CoreLib.wasm", content: undefined"""); } } diff --git a/src/cs/Bootsharp.Publish.Test/TaskTest.cs b/src/cs/Bootsharp.Publish.Test/TaskTest.cs index 8c06b17a..02298a89 100644 --- a/src/cs/Bootsharp.Publish.Test/TaskTest.cs +++ b/src/cs/Bootsharp.Publish.Test/TaskTest.cs @@ -18,7 +18,7 @@ public void Dispose () GC.SuppressFinalize(this); } - protected void AddAssembly (string assemblyName, params MockSource[] sources) + protected virtual void AddAssembly (string assemblyName, params MockSource[] sources) { LastAddedAssemblyName = assemblyName; Project.AddAssembly(new(assemblyName, sources)); diff --git a/src/cs/Bootsharp.Publish.Test/TypesTest.cs b/src/cs/Bootsharp.Publish.Test/TypesTest.cs index f13209d8..8321e165 100644 --- a/src/cs/Bootsharp.Publish.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Publish.Test/TypesTest.cs @@ -6,7 +6,6 @@ public class TypesTest public void Temp () { // TODO: Remove when coverlet bug is resolved: https://github.com/coverlet-coverage/coverlet/issues/1561 - _ = new AssemblyMeta { Name = "", Bytes = [] } with { Name = "foo" }; _ = new InterfaceMeta { Kind = default, TypeSyntax = "", Name = "", Namespace = "", Methods = [] } with { Name = "foo" }; _ = new InterfaceMethodMeta { Name = "", Generated = default } with { Name = "foo" }; _ = new MethodMeta { Name = "", JSName = "", Arguments = default, Assembly = "", Kind = default, Space = "", JSSpace = "", ReturnValue = default } with { Assembly = "foo" }; diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs index 0d3768d0..949ed269 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspection.cs @@ -4,7 +4,6 @@ namespace Bootsharp.Publish; internal class AssemblyInspection (MetadataLoadContext ctx) : IDisposable { - public required IReadOnlyCollection Assemblies { get; init; } public required IReadOnlyCollection Interfaces { get; init; } public required IReadOnlyCollection Methods { get; init; } public required IReadOnlyCollection Crawled { get; init; } diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs index cf3125b0..2491fb69 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/AssemblyInspector.cs @@ -4,16 +4,15 @@ namespace Bootsharp.Publish; internal sealed class AssemblyInspector (Preferences prefs, string entryAssemblyName) { - private readonly List assemblies = []; private readonly List interfaces = []; private readonly List methods = []; private readonly List warnings = []; private readonly TypeConverter converter = new(prefs); - public AssemblyInspection InspectInDirectory (string directory) + public AssemblyInspection InspectInDirectory (string directory, IEnumerable paths) { var ctx = CreateLoadContext(directory); - foreach (var assemblyPath in Directory.GetFiles(directory, "*.dll")) + foreach (var assemblyPath in paths) try { InspectAssemblyFile(assemblyPath, ctx); } catch (Exception e) { AddSkippedAssemblyWarning(assemblyPath, e); } return CreateInspection(ctx); @@ -21,7 +20,6 @@ public AssemblyInspection InspectInDirectory (string directory) private void InspectAssemblyFile (string assemblyPath, MetadataLoadContext ctx) { - assemblies.Add(CreateAssembly(assemblyPath)); if (!ShouldIgnoreAssembly(assemblyPath)) InspectAssembly(ctx.LoadFromAssemblyPath(assemblyPath)); } @@ -35,18 +33,12 @@ private void AddSkippedAssemblyWarning (string assemblyPath, Exception exception } private AssemblyInspection CreateInspection (MetadataLoadContext ctx) => new(ctx) { - Assemblies = [..assemblies], Interfaces = [..interfaces], Methods = [..methods], Crawled = [..converter.CrawledTypes], Warnings = [..warnings] }; - private AssemblyMeta CreateAssembly (string assemblyPath) => new() { - Name = Path.GetFileNameWithoutExtension(assemblyPath), - Bytes = File.ReadAllBytes(assemblyPath) - }; - private void InspectAssembly (Assembly assembly) { foreach (var exported in assembly.GetExportedTypes()) diff --git a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs index 7a167eed..e3b41ca3 100644 --- a/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs +++ b/src/cs/Bootsharp.Publish/Common/AssemblyInspector/InspectionReporter.cs @@ -8,9 +8,9 @@ internal sealed class InspectionReporter (TaskLoggingHelper logger) public void Report (AssemblyInspection inspection) { logger.LogMessage(MessageImportance.Normal, "Bootsharp assembly inspection result:"); - logger.LogMessage(MessageImportance.Normal, JoinLines($"Discovered {inspection.Assemblies.Count} assemblies:", - JoinLines(inspection.Assemblies.Select(a => a.Name)))); - logger.LogMessage(MessageImportance.Normal, JoinLines($"Discovered {inspection.Methods.Count} JS methods:", + logger.LogMessage(MessageImportance.Normal, JoinLines("Discovered assemblies:", + JoinLines(inspection.Methods.GroupBy(m => m.Assembly).Select(g => g.Key)))); + logger.LogMessage(MessageImportance.Normal, JoinLines("Discovered interop methods:", JoinLines(inspection.Methods.Select(m => m.ToString())))); foreach (var warning in inspection.Warnings) diff --git a/src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs b/src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs deleted file mode 100644 index c52e2afb..00000000 --- a/src/cs/Bootsharp.Publish/Common/Meta/AssemblyMeta.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Bootsharp.Publish; - -internal sealed record AssemblyMeta -{ - public required string Name { get; init; } - public required byte[] Bytes { get; init; } -} diff --git a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs index 37da354f..47c29204 100644 --- a/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs +++ b/src/cs/Bootsharp.Publish/Emit/BootsharpEmit.cs @@ -32,7 +32,8 @@ private Preferences ResolvePreferences () private AssemblyInspection InspectAssemblies (Preferences prefs) { var inspector = new AssemblyInspector(prefs, EntryAssemblyName); - var inspection = inspector.InspectInDirectory(InspectedDirectory); + var inspected = Directory.GetFiles(InspectedDirectory, "*.dll"); + var inspection = inspector.InspectInDirectory(InspectedDirectory, inspected); new InspectionReporter(Log).Report(inspection); return inspection; } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index dda7baaa..2c06a94f 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -32,7 +32,11 @@ private Preferences ResolvePreferences () private AssemblyInspection InspectAssemblies (Preferences prefs) { var inspector = new AssemblyInspector(prefs, EntryAssemblyName); - var inspection = inspector.InspectInDirectory(InspectedDirectory); + // Assemblies in publish dir are trimmed and don't contain some data (eg, method arg names). + // While the inspected dir contains extra assemblies we don't need in build. Hence the filtering. + var included = Directory.GetFiles(BuildDirectory, "*.wasm").Select(Path.GetFileNameWithoutExtension).ToHashSet(); + var inspected = Directory.GetFiles(InspectedDirectory, "*.dll").Where(p => included.Contains(Path.GetFileNameWithoutExtension(p))); + var inspection = inspector.InspectInDirectory(InspectedDirectory, inspected); new InspectionReporter(Log).Report(inspection); return inspection; } @@ -53,8 +57,8 @@ private void GenerateDeclarations (Preferences prefs, AssemblyInspection inspect private void GenerateResources (AssemblyInspection inspection) { - var generator = new ResourceGenerator(EntryAssemblyName, BuildDirectory, EmbedBinaries); - var content = generator.Generate(inspection); + var generator = new ResourceGenerator(EntryAssemblyName, EmbedBinaries); + var content = generator.Generate(BuildDirectory); File.WriteAllText(Path.Combine(BuildDirectory, "resources.g.js"), content); } diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs index 7f7d0c40..16147523 100644 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs @@ -1,11 +1,15 @@ namespace Bootsharp.Publish; -internal sealed class ResourceGenerator (string entryAssemblyName, string buildDir, bool embed) +internal sealed class ResourceGenerator (string entryAssemblyName, bool embed) { - public string Generate (AssemblyInspection inspection) + private readonly List assemblies = []; + private string wasm = null!; + + public string Generate (string buildDir) { - var wasm = BuildBin("dotnet.native.wasm", GenerateWasm()); - var assemblies = inspection.Assemblies.Select(BuildAssembly); + foreach (var path in Directory.GetFiles(buildDir, "*.wasm")) + if (path.EndsWith("dotnet.native.wasm")) wasm = BuildBin(path); + else assemblies.Add(BuildBin(path)); return $$""" export default { @@ -18,23 +22,10 @@ public string Generate (AssemblyInspection inspection) """; } - private string GenerateWasm () - { - if (!embed) return "undefined"; - var path = Path.Combine(buildDir, "dotnet.native.wasm"); - var bytes = File.ReadAllBytes(path); - return ToBase64(bytes); - } - - private string BuildAssembly (AssemblyMeta assembly) - { - var name = assembly.Name + ".wasm"; - var content = embed ? ToBase64(assembly.Bytes) : "undefined"; - return BuildBin(name, content); - } - - private string BuildBin (string name, string content) + private string BuildBin (string path) { + var name = Path.GetFileName(path); + var content = embed ? ToBase64(File.ReadAllBytes(path)) : "undefined"; return $$"""{ name: "{{name}}", content: {{content}} }"""; } diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index d2580ec8..0ee441f9 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.58 + 0.2.0-alpha.63 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 0e15293a9361014c6bb981bc95b19824fe389eda Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:20:46 +0300 Subject: [PATCH 75/75] bump ver --- src/cs/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 0ee441f9..1167aee4 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,6 @@ - 0.2.0-alpha.63 + 0.2.0 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com