From cad7694f571a7f7e5a695a8bb865c114fa241800 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 31 May 2026 16:43:28 +0300 Subject: [PATCH 1/3] update docs --- docs/.vitepress/config.ts | 2 +- docs/guide/declarations.md | 4 +- docs/guide/preferences.md | 72 ------------------- docs/guide/renaming.md | 68 ++++++++++++++++++ samples/react/backend/Backend.WASM/Program.cs | 8 ++- 5 files changed, 77 insertions(+), 77 deletions(-) delete mode 100644 docs/guide/preferences.md create mode 100644 docs/guide/renaming.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c10d806a..6c83229e 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -55,7 +55,7 @@ export default defineConfig({ { text: "Serialization", link: "/guide/serialization" }, { text: "Interop Modules", link: "/guide/interop-modules" }, { text: "Interop Instances", link: "/guide/interop-instances" }, - { text: "Preferences", link: "/guide/preferences" }, + { text: "Renaming", link: "/guide/renaming" }, { text: "Specialization", link: "/guide/specialization" }, { text: "Build Configuration", link: "/guide/build-config" }, { text: "Sideloading", link: "/guide/sideloading" }, diff --git a/docs/guide/declarations.md b/docs/guide/declarations.md index 4fe6821c..543bc1fd 100644 --- a/docs/guide/declarations.md +++ b/docs/guide/declarations.md @@ -351,8 +351,8 @@ Class.baz(); ::: -You can control how the C#-side namespace path resolves to the generated module path with the `Space` and `Name` options in [preferences](/guide/preferences). +You can control how the C#-side namespace and type names resolve to the generated module and node names with the [rename attributes](/guide/renaming). ## Configuring Type Mappings -You can override which type declaration is generated for associated C# types via `Type` patterns of [emit preferences](/guide/preferences). +You can rename or omit the JavaScript node generated for an associated C# type via the [rename attributes](/guide/renaming). diff --git a/docs/guide/preferences.md b/docs/guide/preferences.md deleted file mode 100644 index 3d570aef..00000000 --- a/docs/guide/preferences.md +++ /dev/null @@ -1,72 +0,0 @@ -# Preferences - -Use the `[Preferences]` assembly attribute to customize Bootsharp behaviour at build time when the interop code is emitted. Each property accepts an array of `(pattern, replacement)` strings that are fed to [Regex.Replace](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.replace?view=net-6.0#system-text-regularexpressions-regex-replace(system-string-system-string-system-string)) when generating the associated code. Pairs are tested in order; on first match the result replaces the default. - -## Space - -Customizes how C# type namespaces map to JavaScript module names. The patterns are matched against the type's C# namespace, or an empty string when the type is global; the result is slugified into the module path. Types whose resolved space ends up empty land in the default `index` module. - -By default, all generated bindings and TypeScript declarations are grouped under their C# namespace. To rewrite every type declared under `Foo.Bar` so its binding ends up in the `baz` module: - -```cs -[assembly: Preferences( - Space = ["^Foo\.Bar$", "Baz"] -)] -``` - -## Name - -Customizes how C# type names map to the emitted JavaScript object names. The patterns are matched against the C# type name as reflected. To rename every exported type whose name ends with `Service` so the suffix is dropped on the JavaScript side: - -```cs -[assembly: Preferences( - Name = ["^(\S+)Service$", "$1"] -)] -``` - -## Method - -Customizes how C# method names map to JavaScript function names. The patterns are matched against the reflected method name. By default, Bootsharp camel-cases the C# name; this pref overrides the result. To force every method starting with `Get` to keep its original casing: - -```cs -[assembly: Preferences( - Method = ["^Get(\S+)$", "Get$1"] -)] -``` - -## Property - -Customizes how C# property names map to JavaScript property names. The patterns are matched against the reflected property name and behave the same way as `Method`, but apply to both exported and imported properties. - -```cs -[assembly: Preferences( - Property = ["^Is(\S+)$", "has$1"] -)] -``` - -## Event - -Customizes how C# event names map to JavaScript event names. The patterns are matched against the reflected event name. Applies to both exported and imported events. -```cs -[assembly: Preferences( - Event = ["^On(\S+)$", "$1Changed"] -)] -``` - -## Combining Preferences - -Multiple properties can be set on the same attribute, and each property accepts more than one pair — earlier pairs take precedence over later ones on the same input: - -```cs -[assembly: Preferences( - Space = ["^Foo\.Bar$", "Baz"], - Name = [ - "^I([A-Z].*)UI$", "$1", - "^I([A-Z].*)$", "$1" - ], - Method = ["^Get(\S+)$", "fetch$1"] -)] -``` - -The example preferences above will strip the leading `I` from interface names (so `IService` is emitted as `Service`), additionally drop a trailing `UI` suffix when present (so `IMainUI` becomes `Main`) — the more specific pair is listed first so it wins over the generic `I` strip on the same input — move every type declared in the C# `Foo.Bar` namespace into the `baz` JavaScript module, and rename every C# method starting with `Get` so the JavaScript binding uses a `fetch` prefix instead (so `GetUser` is exposed as `fetchUser`). - diff --git a/docs/guide/renaming.md b/docs/guide/renaming.md new file mode 100644 index 00000000..1352ee8e --- /dev/null +++ b/docs/guide/renaming.md @@ -0,0 +1,68 @@ +# Renaming + +By default, Bootsharp derives JavaScript names from your C# types: a type's namespace becomes the module path, the type name becomes the node (object) under that module, and members are `camelCased`. + +It's possible to customize the behaviour by specifying static methods annotated with `[RenameModule]`, `[RenameNode]` and `[RenameMember]` attributes. The methods receive a CLR type associated with the renamed artifact plus the default name generated by Bootsharp and expected to return the custom name you want to use. + +## Module + +`[RenameModule]` customizes the module path that groups the generated bindings and declarations. The default is the slugified C# namespace, or `index` for global types. Returning an empty, null or whitespace string falls back to the default `index` module. + +```cs +[RenameModule] +public static string RenameModule (Type type, string @default) => + @default.Replace("/foo/bar", "/foo"); +``` + +In the example above we fold `/foo/bar` modules into the `/foo` module. + +## Node + +`[RenameNode]` customizes the node — the object representing a C# type under its module. The default is the reflected type name. Returning an empty, null or whitespace string **erases** the type, omitting it from the generated JavaScript. + +```cs +[RenameNode] +public static string RenameNode (Type type, string @default) +{ + if (type.Name == "Foo") return null; + if (type.IsInterface && @default.EndsWith("UI")) return @default[1..^2]; + if (type.IsInterface && @default.StartsWith('I')) return @default[1..]; + return @default; +} +``` + +The example removes `Foo` types from the interop surface, strips the leading `I` from interface names and drops a trailing `UI` suffix when present. + +## Member + +`[RenameMember]` customizes member names — the methods, properties and events projected on an interop surface. The default is the `camelCased` (and disambiguated) member name. Returning an empty, null or whitespace string **erases** the member, omitting it from the generated JavaScript. + +```cs +[RenameMember] +public static string RenameMember (MemberInfo info, string @default) +{ + if (info.DeclaringType.Name == "Foo") + if (info is EventInfo) return null; + else return char.ToUpperInvariant(@default[0]) + @default[0..]; + return @default; +} +``` + +Here we drop all events and rename other members declared under `Foo` to `PascalCase`. + +## Combining Renamers + +Define any combination of the three renamers — each is optional and resolved independently. The example below groups everything under an `api` module, removes the interface prefixes and drops properties from all surfaces: + +```cs +[RenameModule] +public static string Module (Type type, string @default) => "api"; + +[RenameNode] +public static string Node (Type type, string @default) => + type.IsInterface ? @default[1..] : @default; + +[RenameMember] +public static string Member (MemberInfo info, string @default) => + info is PropertyInfo ? null : @default; +``` diff --git a/samples/react/backend/Backend.WASM/Program.cs b/samples/react/backend/Backend.WASM/Program.cs index bcd431b5..ef1b52fe 100644 --- a/samples/react/backend/Backend.WASM/Program.cs +++ b/samples/react/backend/Backend.WASM/Program.cs @@ -11,8 +11,6 @@ [assembly: Export(typeof(Backend.IComputer))] // Generate JavaScript -> C# interop handlers for specified contracts. [assembly: Import(typeof(Backend.Prime.IPrimeUI))] -// Group all generated JavaScript APIs under "Computer" namespace. -[assembly: Preferences(Space = [".+", "Computer"])] // Perform dependency injection. new ServiceCollection() @@ -20,3 +18,9 @@ .AddBootsharp() // inject generated interop handlers .BuildServiceProvider() .RunBootsharp(); // initialize interop services + +public static class Prefs +{ + [RenameModule] // Group generated JavaScript APIs under the "computer" module. + public static string RenameModule (Type type, string @default) => "computer"; +} From cf0260fa2c4042f781616f9725be53e9c1542c5d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 31 May 2026 17:06:33 +0300 Subject: [PATCH 2/3] implement --- src/cs/Bootsharp.Common.Test/TypesTest.cs | 1 - .../Attributes/PreferencesAttribute.cs | 57 ---------- .../Attributes/RenameMemberAttribute.cs | 15 +++ .../Attributes/RenameModuleAttribute.cs | 15 +++ .../Attributes/RenameNodeAttribute.cs | 15 +++ .../GenerateCS/CSInteropTest.cs | 9 +- .../GenerateCS/GenerateCSTest.cs | 3 - .../GenerateJS/DeclarationTest.cs | 106 ++++++++++++++---- .../GenerateJS/JSModuleTest.cs | 65 +++++++---- .../Mock/MockCompiler.cs | 1 + .../Bootsharp.Publish.csproj | 1 - .../Common/Global/GlobalInspection.cs | 25 +---- .../Common/Inspection/InspectedAssembly.cs | 5 + .../Common/Inspection/InspectionContext.cs | 15 +++ .../Common/Inspection/Meta/TypeMeta.cs | 15 +-- .../Common/Inspection/SolutionInspection.cs | 12 +- .../Common/Inspection/SolutionInspector.cs | 30 +++-- .../Common/Inspection/TypeInspector.cs | 12 +- .../Common/Preferences/Preference.cs | 3 - .../Common/Preferences/Preferences.cs | 100 +++++++++++++++-- .../Common/Preferences/PreferencesResolver.cs | 52 --------- .../Preferences/SpecializationResolver.cs | 34 ------ .../GenerateCS/GenerateCS.cs | 5 +- .../GenerateJS/GenerateJS.cs | 4 +- src/cs/Bootsharp/Build/Bootsharp.targets | 1 - src/cs/Directory.Build.props | 2 +- 26 files changed, 319 insertions(+), 284 deletions(-) delete mode 100644 src/cs/Bootsharp.Common/Attributes/PreferencesAttribute.cs create mode 100644 src/cs/Bootsharp.Common/Attributes/RenameMemberAttribute.cs create mode 100644 src/cs/Bootsharp.Common/Attributes/RenameModuleAttribute.cs create mode 100644 src/cs/Bootsharp.Common/Attributes/RenameNodeAttribute.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Inspection/InspectedAssembly.cs create mode 100644 src/cs/Bootsharp.Publish/Common/Inspection/InspectionContext.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs delete mode 100644 src/cs/Bootsharp.Publish/Common/Preferences/SpecializationResolver.cs diff --git a/src/cs/Bootsharp.Common.Test/TypesTest.cs b/src/cs/Bootsharp.Common.Test/TypesTest.cs index 36950743..55b12190 100644 --- a/src/cs/Bootsharp.Common.Test/TypesTest.cs +++ b/src/cs/Bootsharp.Common.Test/TypesTest.cs @@ -21,7 +21,6 @@ public void TypesAreAssigned () Assert.Equal(typeof(IFrontend), new SpecializeImportAttribute(typeof(IFrontend)).Clr); Assert.Equal("JS", new SpecializeImportAttribute(typeof(IFrontend), JS: "JS").JS); Assert.Equal("Decl", new SpecializeImportAttribute(typeof(IFrontend), Decl: "Decl").Decl); - Assert.Equal("Space", new PreferencesAttribute { Space = ["Space"] }.Space[0]); } [Fact] diff --git a/src/cs/Bootsharp.Common/Attributes/PreferencesAttribute.cs b/src/cs/Bootsharp.Common/Attributes/PreferencesAttribute.cs deleted file mode 100644 index a422874d..00000000 --- a/src/cs/Bootsharp.Common/Attributes/PreferencesAttribute.cs +++ /dev/null @@ -1,57 +0,0 @@ -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 spaces starting with "Foo.Bar" replaced with "Baz": -/// -/// [assembly: Bootsharp.Preferences( -/// Space = ["^Foo\.Bar\.(\S+)", "Baz.$1"] -/// )] -/// -/// -[AttributeUsage(AttributeTargets.Assembly)] -public sealed class PreferencesAttribute : Attribute -{ - /// - /// Customize how C# type namespaces transform into JavaScript module names. - /// - /// - /// The patterns are matched against the C# type namespace, or 'index' when the type is global. - /// - public string[] Space { get; init; } = []; - /// - /// Customize how C# type names transform into JavaScript object names. - /// - /// - /// The patterns are matched against the C# type names. - /// - public string[] Name { get; init; } = []; - /// - /// Customize how C# method names transform into JavaScript function names. - /// - /// - /// The patterns are matched against the C# reflected method names. - /// - public string[] Method { get; init; } = []; - /// - /// Customize how C# property names transform into JavaScript property names. - /// - /// - /// The patterns are matched against the C# reflected property names. - /// - public string[] Property { get; init; } = []; - /// - /// Customize how C# event names transform into JavaScript event names. - /// - /// - /// The patterns are matched against the C# reflected event names. - /// - public string[] Event { get; init; } = []; -} diff --git a/src/cs/Bootsharp.Common/Attributes/RenameMemberAttribute.cs b/src/cs/Bootsharp.Common/Attributes/RenameMemberAttribute.cs new file mode 100644 index 00000000..80f72fd0 --- /dev/null +++ b/src/cs/Bootsharp.Common/Attributes/RenameMemberAttribute.cs @@ -0,0 +1,15 @@ +namespace Bootsharp; + +/// +/// When applied to a static method, designates it as the customizer of JavaScript member names — +/// the methods, properties and events projected on an interop surface. +/// +/// +/// The annotated method has to be static and accept the reflected +/// together with the default member name (camel-cased and disambiguated), returning the desired member name. +/// It is invoked for every inspected member after the metadata is collected. Return the supplied default to +/// keep the member unchanged; returning an empty, null or whitespace string erases the member, omitting it +/// from the generated JavaScript. +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class RenameMemberAttribute : Attribute; diff --git a/src/cs/Bootsharp.Common/Attributes/RenameModuleAttribute.cs b/src/cs/Bootsharp.Common/Attributes/RenameModuleAttribute.cs new file mode 100644 index 00000000..5fb13949 --- /dev/null +++ b/src/cs/Bootsharp.Common/Attributes/RenameModuleAttribute.cs @@ -0,0 +1,15 @@ +namespace Bootsharp; + +/// +/// When applied to a static method, designates it as the customizer of JavaScript module names — +/// the namespace-derived path that groups generated bindings and declarations. +/// +/// +/// The annotated method has to be static and accept the inspected CLR together +/// with the default module name (the slugified C# namespace, or 'index' for global types), returning the +/// desired module name. It is invoked for every inspected type after the metadata is collected. Return the +/// supplied default to keep the module unchanged; returning an empty, null or whitespace string falls back +/// to the default 'index' module. +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class RenameModuleAttribute : Attribute; diff --git a/src/cs/Bootsharp.Common/Attributes/RenameNodeAttribute.cs b/src/cs/Bootsharp.Common/Attributes/RenameNodeAttribute.cs new file mode 100644 index 00000000..7af3ce1c --- /dev/null +++ b/src/cs/Bootsharp.Common/Attributes/RenameNodeAttribute.cs @@ -0,0 +1,15 @@ +namespace Bootsharp; + +/// +/// When applied to a static method, designates it as the customizer of JavaScript node names — +/// the object that represents a CLR type under its module. +/// +/// +/// The annotated method has to be static and accept the inspected CLR together +/// with the default node name (the reflected C# type name), returning the desired node name. It is invoked +/// for every inspected type after the metadata is collected. Return the supplied default to keep the node +/// unchanged; returning an empty, null or whitespace string erases the type, omitting it from the generated +/// JavaScript. +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class RenameNodeAttribute : Attribute; diff --git a/src/cs/Bootsharp.Publish.Test/GenerateCS/CSInteropTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateCS/CSInteropTest.cs index a65b49ed..ef5ab048 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateCS/CSInteropTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateCS/CSInteropTest.cs @@ -452,11 +452,10 @@ public void EscapesReservedArgumentNames () } [Fact] - public void RespectsSpacePref () + public void RespectsPref () { AddAssembly(With( """ - [assembly:Preferences(Space = [@"Space", "Foo"])] [assembly:Export(typeof(Space.IExported))] [assembly:Import(typeof(Space.IImported))] @@ -471,6 +470,12 @@ public class Class [Export] public static void Inv () {} [Import] public static void Fun () => Proxies.Get("Class.Fun")(); } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => @default.Replace("space", "foo"); + } """)); Execute(); Contains("[JSExport] internal static void JS_Export_Space_IExported_Inv () => global::Bootsharp.Generated.JS_Export_Space_IExported.Inv();"); diff --git a/src/cs/Bootsharp.Publish.Test/GenerateCS/GenerateCSTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateCS/GenerateCSTest.cs index 898b58f3..3db9d2c4 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateCS/GenerateCSTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateCS/GenerateCSTest.cs @@ -20,14 +20,11 @@ public GenerateCSTest () public override void Execute () { - if (LastAddedAssemblyName is not null) - Task.EntryAssemblyName = LastAddedAssemblyName; Task.Execute(); } private GenerateCS CreateTask () => new() { InspectedDirectory = Project.Root, - EntryAssemblyName = "System.Runtime.dll", SerializerFilePath = serializerPath, InstancesFilePath = instancesPath, ModulesFilePath = modulesPath, diff --git a/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs index 84770ac6..043f4806 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs @@ -1295,14 +1295,6 @@ public void RespectsPrefsInStatics () { AddAssembly(With( """ - [assembly:Preferences( - Space = [@".+", "index"], - Name = [@"^Class$", "Foo"], - Method = [@"^Method$", "bar"], - Property = [@"^Property$", "baz"], - Event = [@"^Event$", "qux"] - )] - namespace Space; public enum Enum { A, B } @@ -1313,6 +1305,20 @@ public class Class [Export] public static Enum Property { get; set; } [Export] public static event Action? Event; } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => "index"; + + [RenameNode] + public static string Node (Type type, string @default) => type.Name == "Class" ? "Foo" : @default; + + [RenameMember] + public static string Member (MemberInfo info, string @default) => info.Name switch { + "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default + }; + } """)); Execute(); Contains( @@ -1334,13 +1340,6 @@ public void RespectsPrefsInModules () { AddAssembly(With( """ - [assembly:Preferences( - Space = [@".+", "index"], - Name = [@"^I.+$", "Foo"], - Method = [@"^Inv$", "bar", @"^Fun$", "baz"], - Property = [@"^State$", "qux"], - Event = [@"^Changed$", "quz"] - )] [assembly:Export(typeof(Space.IExported))] [assembly:Import(typeof(Space.IImported))] @@ -1358,6 +1357,20 @@ public interface IImported { void Fun (Enum e); } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => "index"; + + [RenameNode] + public static string Node (Type type, string @default) => type.IsInterface ? "Foo" : @default; + + [RenameMember] + public static string Member (MemberInfo info, string @default) => info.Name switch { + "Inv" => "bar", "Fun" => "baz", "State" => "qux", "Changed" => "quz", _ => @default + }; + } """)); Execute(); Contains( @@ -1380,14 +1393,6 @@ public void RespectsPrefsInInstanced () { AddAssembly(With( """ - [assembly:Preferences( - Space = [@".+", "index"], - Name = [@"^IInst$", "Foo"], - Method = [@"^Method$", "bar"], - Property = [@"^Property$", "baz"], - Event = [@"^Event$", "qux"] - )] - namespace Space; public enum Enum { A, B } @@ -1403,6 +1408,20 @@ public class Class { [Export] public static IInst Get () => default; } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => null; + + [RenameNode] + public static string Node (Type type, string @default) => type.Name == "IInst" ? "Foo" : @default; + + [RenameMember] + public static string Member (MemberInfo info, string @default) => info.Name switch { + "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default + }; + } """)); Execute(); Contains( @@ -1422,6 +1441,47 @@ export enum Enum { """); } + [Fact] + public void ErasesNodeRenamedToNull () + { + AddAssembly(With( + """ + [assembly:Export(typeof(IKept), typeof(IGone))] + + public interface IKept { void Foo (); } + public interface IGone { void Bar (); } + + public static class Prefs + { + [RenameNode] + public static string Node (Type type, string @default) => type.Name == "IGone" ? null : @default; + } + """)); + Execute(); + Contains("export namespace IKept"); + DoesNotContain("IGone"); + } + + [Fact] + public void ErasesMemberWhenRenamedToNull () + { + AddAssembly(With( + """ + [assembly:Export(typeof(IFoo))] + + public interface IFoo { void Foo (); void Bar(); } + + public static class Prefs + { + [RenameMember] + public static string Member (MemberInfo info, string @default) => @default == "bar" ? null : @default; + } + """)); + Execute(); + Contains("function foo"); + DoesNotContain("function bar"); + } + [Fact] public void GeneratesJsDocsOverCsDocs () { diff --git a/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs index a313d5be..853fb796 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs @@ -735,14 +735,6 @@ public void RespectsPrefsInStatics () { AddAssembly(With( """ - [assembly:Preferences( - Space = [@".+", "index"], - Name = [@"^Class$", "Foo"], - Method = [@"^Method$", "bar"], - Property = [@"^Property$", "baz"], - Event = [@"^Event$", "qux"] - )] - namespace Space; public enum Enum { A, B } @@ -753,6 +745,20 @@ public class Class [Export] public static Enum Property { get; set; } [Export] public static event Action? Event; } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => "index"; + + [RenameNode] + public static string Node (Type type, string @default) => type.Name == "Class" ? "Foo" : @default; + + [RenameMember] + public static string Member (MemberInfo info, string @default) => info.Name switch { + "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default + }; + } """)); Execute(); Contains( @@ -778,13 +784,6 @@ public void RespectsPrefsInModules () { AddAssembly(With( """ - [assembly:Preferences( - Space = [@".+", "index"], - Name = [@"^I.+$", "Foo"], - Method = [@"^Inv$", "bar", @"^Fun$", "baz"], - Property = [@"^State$", "qux"], - Event = [@"^Changed$", "quz"] - )] [assembly:Export(typeof(Space.IExported))] [assembly:Import(typeof(Space.IImported))] @@ -802,6 +801,20 @@ public interface IImported { void Fun (Enum e); } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => "index"; + + [RenameNode] + public static string Node (Type type, string @default) => type.IsInterface ? "Foo" : @default; + + [RenameMember] + public static string Member (MemberInfo info, string @default) => info.Name switch { + "Inv" => "bar", "Fun" => "baz", "State" => "qux", "Changed" => "quz", _ => @default + }; + } """)); Execute(); Contains( @@ -830,14 +843,6 @@ public void RespectsPrefsInInstanced () { AddAssembly(With( """ - [assembly:Preferences( - Space = [@".+", "index"], - Name = [@"^IInst$", "Foo"], - Method = [@"^Method$", "bar"], - Property = [@"^Property$", "baz"], - Event = [@"^Event$", "qux"] - )] - namespace Space; public enum Enum { A, B } @@ -853,6 +858,20 @@ public class Class { [Export] public static IInst Get () => default; } + + public static class Prefs + { + [RenameModule] + public static string Module (Type type, string @default) => "index"; + + [RenameNode] + public static string Node (Type type, string @default) => type.Name == "IInst" ? "Foo" : @default; + + [RenameMember] + public static string Member (MemberInfo info, string @default) => info.Name switch { + "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default + }; + } """)); Execute(); Contains( diff --git a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs index 8d83f50e..4bc4b25b 100644 --- a/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs +++ b/src/cs/Bootsharp.Publish.Test/Mock/MockCompiler.cs @@ -10,6 +10,7 @@ public class MockCompiler "System.Collections.Generic", "System.Threading", "System.Threading.Tasks", + "System.Reflection", "Bootsharp" ]; diff --git a/src/cs/Bootsharp.Publish/Bootsharp.Publish.csproj b/src/cs/Bootsharp.Publish/Bootsharp.Publish.csproj index ac917327..fe59760b 100644 --- a/src/cs/Bootsharp.Publish/Bootsharp.Publish.csproj +++ b/src/cs/Bootsharp.Publish/Bootsharp.Publish.csproj @@ -11,7 +11,6 @@ - diff --git a/src/cs/Bootsharp.Publish/Common/Global/GlobalInspection.cs b/src/cs/Bootsharp.Publish/Common/Global/GlobalInspection.cs index c58e8835..1b66c79e 100644 --- a/src/cs/Bootsharp.Publish/Common/Global/GlobalInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/Global/GlobalInspection.cs @@ -1,15 +1,11 @@ global using static Bootsharp.Publish.GlobalInspection; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; namespace Bootsharp.Publish; internal static class GlobalInspection { - public static Preferences Pref => PreferencesResolver.Resolved.Value ?? new(); - private static readonly HashSet csKeywords = [ "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", @@ -32,17 +28,6 @@ internal static class GlobalInspection "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield" ]; - public static MetadataLoadContext CreateLoadContext (string directory) - { - var runtimeDir = RuntimeEnvironment.GetRuntimeDirectory(); - var assemblyPaths = Directory.GetFiles(runtimeDir, "*.dll").Order().ToList(); - foreach (var path in Directory.GetFiles(directory, "*.dll").Order()) - if (assemblyPaths.All(p => Path.GetFileName(p) != Path.GetFileName(path))) - assemblyPaths.Add(path); - var resolver = new PathAssemblyResolver(assemblyPaths); - return new MetadataLoadContext(resolver); - } - public static bool IsUserAssembly (string assemblyName) => !assemblyName.StartsWith("System.", StringComparison.OrdinalIgnoreCase) && !assemblyName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase) && @@ -51,7 +36,7 @@ public static bool IsUserAssembly (string assemblyName) => public static bool IsUserType (Type type) { - if (SpecializationResolver.IsSpecialized(type)) return true; + if (Preferences.IsSpecialized(type)) return true; if (IsDelegate(type)) return true; if (type.IsArray) return false; return IsUserAssembly(type.Assembly.FullName!); @@ -86,14 +71,6 @@ public static string BuildJSName (string name) return jsKeywords.Contains(name) ? $"${name}" : name; } - public static string WithPref (IReadOnlyCollection prefs, string input, string? @default = null) - { - foreach (var pref in prefs) - if (Regex.IsMatch(input, pref.Pattern)) - return Regex.Replace(input, pref.Pattern, pref.Replacement); - return @default ?? input; - } - extension (IReadOnlyCollection types) { public bool HasBase (Type clr, [NotNullWhen(true)] out TypeMeta? bs) diff --git a/src/cs/Bootsharp.Publish/Common/Inspection/InspectedAssembly.cs b/src/cs/Bootsharp.Publish/Common/Inspection/InspectedAssembly.cs new file mode 100644 index 00000000..9d3c294a --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Inspection/InspectedAssembly.cs @@ -0,0 +1,5 @@ +using System.Reflection; + +namespace Bootsharp.Publish; + +public record InspectedAssembly (Assembly Assembly, string Path, string Name); diff --git a/src/cs/Bootsharp.Publish/Common/Inspection/InspectionContext.cs b/src/cs/Bootsharp.Publish/Common/Inspection/InspectionContext.cs new file mode 100644 index 00000000..5bc1ca05 --- /dev/null +++ b/src/cs/Bootsharp.Publish/Common/Inspection/InspectionContext.cs @@ -0,0 +1,15 @@ +using System.Runtime.Loader; + +namespace Bootsharp.Publish; + +internal sealed class InspectionContext () : AssemblyLoadContext("Bootsharp", isCollectible: true) +{ + public InspectedAssembly Load (string path) + { + var bytes = File.ReadAllBytes(path); + using var stream = new MemoryStream(bytes); + var ass = LoadFromStream(stream); + var name = Path.GetFileName(path); + return new(ass, path, name); + } +} diff --git a/src/cs/Bootsharp.Publish/Common/Inspection/Meta/TypeMeta.cs b/src/cs/Bootsharp.Publish/Common/Inspection/Meta/TypeMeta.cs index cf23369e..85cf555f 100644 --- a/src/cs/Bootsharp.Publish/Common/Inspection/Meta/TypeMeta.cs +++ b/src/cs/Bootsharp.Publish/Common/Inspection/Meta/TypeMeta.cs @@ -21,20 +21,9 @@ internal record TypeMeta (Type Clr) /// /// The path to the module containing the node that represents the type in JavaScript. /// - public string JSModule { get; } = BuildModule(Clr); + public string JSModule { get; } = Clr.Namespace is { } ns ? Slugify(ns) : "index"; /// /// The path to the node inside the module that represents the type in JavaScript. /// - public string JSNode { get; } = BuildNode(Clr); - - private static string BuildModule (Type clr) - { - var slug = Slugify(WithPref(Pref.Space, clr.Namespace ?? "")); - return string.IsNullOrWhiteSpace(slug) ? "index" : slug; - } - - private static string BuildNode (Type clr) - { - return WithPref(Pref.Name, clr.Name, BuildId(clr, full: false, separator: '.')); - } + public string JSNode { get; } = BuildId(Clr, full: false, separator: '.'); } diff --git a/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspection.cs b/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspection.cs index 8fd605e8..264a109e 100644 --- a/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspection.cs +++ b/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspection.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Runtime.Loader; namespace Bootsharp.Publish; @@ -7,11 +7,11 @@ namespace Bootsharp.Publish; /// code and other Bootsharp-specific resources. /// /// -/// Context in which the solution's assemblies were loaded and inspected. -/// Shouldn't be disposed to keep C# reflection APIs usable on the inspected types. -/// Dispose to remove file lock on the inspected assemblies. +/// Collectible context in which the solution's assemblies were loaded and inspected. +/// Shouldn't be unloaded to keep C# reflection APIs usable on the inspected types. +/// Dispose to unload the context and reclaim the associated memory. /// -internal sealed class SolutionInspection (MetadataLoadContext ctx) : IDisposable +internal sealed class SolutionInspection (AssemblyLoadContext ctx) : IDisposable { /// /// The discovered interop artifacts. @@ -22,5 +22,5 @@ internal sealed class SolutionInspection (MetadataLoadContext ctx) : IDisposable /// public required IReadOnlyCollection Docs { get; init; } - public void Dispose () => ctx.Dispose(); + public void Dispose () => ctx.Unload(); } diff --git a/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspector.cs b/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspector.cs index 04721df0..6f879c6f 100644 --- a/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/Inspection/SolutionInspector.cs @@ -1,49 +1,45 @@ -using System.Reflection; using System.Xml.Linq; using Microsoft.Build.Utilities; namespace Bootsharp.Publish; -internal sealed class SolutionInspector (string entryAssemblyName, TaskLoggingHelper logger) +internal sealed class SolutionInspector (TaskLoggingHelper logger) { - private readonly List asses = []; + private readonly List asses = []; private readonly TypeInspector types = new(); private readonly List docs = []; /// /// Inspects specified solution assembly paths in the output directory. /// - /// Absolute path to directory containing compiled assemblies. /// Absolute paths of the assemblies to inspect. - public SolutionInspection Inspect (string directory, IEnumerable paths) + public SolutionInspection Inspect (IEnumerable paths) { - var ctx = CreateLoadContext(directory); + var ctx = new InspectionContext(); foreach (var pth in paths) LoadAssembly(pth, ctx); foreach (var ass in asses) ResolvePreferences(ass); - foreach (var ass in asses) types.Inspect(ass); + foreach (var ass in asses) types.Inspect(ass.Assembly); foreach (var ass in asses) InspectDocs(ass); return new(ctx) { Types = types.Collect(), Docs = docs.ToArray() }; } - private void LoadAssembly (string path, MetadataLoadContext ctx) + private void LoadAssembly (string path, InspectionContext ctx) { if (!IsUserAssembly(Path.GetFileNameWithoutExtension(path))) return; - try { asses.Add(ctx.LoadFromAssemblyPath(path)); } + try { asses.Add(ctx.Load(path)); } catch (Exception e) { Warn(path, e); } } - private void ResolvePreferences (Assembly ass) + private void ResolvePreferences (InspectedAssembly ass) { - if (ass.GetName().Name == Path.GetFileNameWithoutExtension(entryAssemblyName)) - PreferencesResolver.Resolve(ass); - try { SpecializationResolver.Resolve(ass); } - catch (Exception e) { Warn(ass.Location, e); } + try { Preferences.Resolve(ass.Assembly); } + catch (Exception e) { Warn(ass.Path, e); } } - private void InspectDocs (Assembly ass) + private void InspectDocs (InspectedAssembly ass) { - var xmlPath = Path.ChangeExtension(ass.Location, ".xml"); - var name = Path.GetFileNameWithoutExtension(ass.Location); + var xmlPath = Path.ChangeExtension(ass.Path, ".xml"); + var name = Path.GetFileNameWithoutExtension(ass.Name); if (File.Exists(xmlPath)) docs.Add(new(name, XDocument.Load(xmlPath))); } diff --git a/src/cs/Bootsharp.Publish/Common/Inspection/TypeInspector.cs b/src/cs/Bootsharp.Publish/Common/Inspection/TypeInspector.cs index 180be470..df3ece7a 100644 --- a/src/cs/Bootsharp.Publish/Common/Inspection/TypeInspector.cs +++ b/src/cs/Bootsharp.Publish/Common/Inspection/TypeInspector.cs @@ -34,7 +34,7 @@ public IReadOnlyCollection Collect () OverloadDisambiguator.Disambiguate([..surfaces, ..its.Values]); TypeMeta[] specialized = [..surfaces, ..its.Values, ..srd.Collect()]; var clrs = specialized.Select(t => t.Clr).ToHashSet(); - return [..specialized, ..crawled.Values.Where(c => !clrs.Contains(c.Clr))]; + return Preferences.Rename([..specialized, ..crawled.Values.Where(c => !clrs.Contains(c.Clr))]); } private StaticMeta? InspectStatic (Type type) @@ -72,7 +72,7 @@ public IReadOnlyCollection Collect () if (IsTaskWithResult(type, out var result)) return InspectInstance(result, ik, nul!.GenericTypeArguments[0]); if (!IsInstanced(type)) return null; if (IsDelegate(type)) return its[key] = InspectDelegate(type, ik); - if (!SpecializationResolver.IsSpecialized(type, out var sp) && ik == InteropKind.Import && !type.IsInterface) + if (!Preferences.IsSpecialized(type, out var sp) && ik == InteropKind.Import && !type.IsInterface) return InspectInstance(type, InteropKind.Export, nul)!; // likely passing back an exported instance return InspectMembers(its[key] = new(type) { IK = ik, @@ -87,7 +87,7 @@ static bool IsInstanced (Type type) // Instanced types are mutable user types that are passed by reference when crossing the // interop boundary (as opposed to serialized immutable types, which are copied by value). if (!IsUserType(type)) return false; - if (type.IsInterface || SpecializationResolver.IsSpecialized(type)) return true; + if (type.IsInterface || Preferences.IsSpecialized(type)) return true; return type.IsClass && !IsStatic(type) && !IsRecord(type); // records are immutable by convention } @@ -152,7 +152,7 @@ bool ShouldInspectMethod (MethodInfo method) IK = ik, Surf = srf, Name = BuildCSName(evt.Name), - JSName = WithPref(Pref.Event, evt.Name, BuildJSName(evt.Name)), + JSName = BuildJSName(evt.Name), TypeSyntax = BuildSyntax(evt.EventHandlerType!, GetNullity(evt)), Args = evt.EventHandlerType!.GetMethod("Invoke")!.GetParameters() .Select(p => InspectArg(p, GetNullity(evt, p), ik)).ToArray() @@ -162,7 +162,7 @@ bool ShouldInspectMethod (MethodInfo method) IK = ik, Surf = srf, Name = BuildCSName(prop.Name), - JSName = WithPref(Pref.Property, prop.Name, BuildJSName(prop.Name)), + JSName = BuildJSName(prop.Name), TypeSyntax = BuildSyntax(prop.PropertyType, GetNullity(prop)), Get = prop.GetMethod != null ? InspectValue(prop.PropertyType, GetNullity(prop), ik) : null, Set = prop.SetMethod != null ? InspectValue(prop.PropertyType, GetNullity(prop), ik.Invert) : null @@ -172,7 +172,7 @@ bool ShouldInspectMethod (MethodInfo method) IK = ik, Surf = srf, Name = BuildCSName(method.Name), - JSName = WithPref(Pref.Method, method.Name, BuildJSName(method.Name)), + JSName = BuildJSName(method.Name), Args = method.GetParameters().Select(p => InspectArg(p, GetNullity(p), ik.Invert)).ToArray(), Return = InspectValue(method.ReturnParameter.ParameterType, GetNullity(method.ReturnParameter), ik), Void = IsVoid(method.ReturnParameter.ParameterType), diff --git a/src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs b/src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs deleted file mode 100644 index b027bae5..00000000 --- a/src/cs/Bootsharp.Publish/Common/Preferences/Preference.cs +++ /dev/null @@ -1,3 +0,0 @@ -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 index 27eb6100..64b55397 100644 --- a/src/cs/Bootsharp.Publish/Common/Preferences/Preferences.cs +++ b/src/cs/Bootsharp.Publish/Common/Preferences/Preferences.cs @@ -1,16 +1,92 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + namespace Bootsharp.Publish; -/// -internal sealed record Preferences +internal static class Preferences { - /// - public IReadOnlyList Space { get; init; } = []; - /// - public IReadOnlyList Name { get; init; } = []; - /// - public IReadOnlyList Method { get; init; } = []; - /// - public IReadOnlyList Property { get; init; } = []; - /// - public IReadOnlyList Event { get; init; } = []; + private sealed class Prefs + { + public Dictionary Specs { get; } = []; + public Func? Module, Node; + public Func? Member; + } + + private static AsyncLocal async { get; } = new(); + private static Prefs prefs => async.Value ??= new(); + + private static readonly FieldInfo moduleField = GetMetaField(nameof(TypeMeta.JSModule)); + private static readonly FieldInfo nodeField = GetMetaField(nameof(TypeMeta.JSNode)); + + public static void Resolve (Assembly ass) + { + ResolveRenames(ass); + ResolveSpecs(ass); + } + + public static IReadOnlyCollection Rename (IReadOnlyCollection types) + { + if (prefs is { Module: null, Node: null, Member: null }) return types; + var renamed = new List(types.Count); + foreach (var type in types) + { + if (prefs.Node is { } renameNode) + if (renameNode(type.Clr, type.JSNode) is { Length: > 0 } node) nodeField.SetValue(type, node); + else continue; + if (prefs.Module is { } renameModule) + if (renameModule(type.Clr, type.JSModule) is { Length: > 0 } md) moduleField.SetValue(type, md); + else moduleField.SetValue(type, "index"); + if (prefs.Member is { } renameMember && type is SurfaceMeta { MemberList: { Count: > 0 } mm }) + for (var i = mm.Count - 1; i >= 0; i--) + if (renameMember(mm[i].Info, mm[i].JSName) is not { Length: > 0 } name) mm.RemoveAt(i); + else mm[i] = mm[i] with { JSName = name }; + renamed.Add(type); + } + return renamed; + } + + public static bool IsSpecialized (Type type) => IsSpecialized(type, out _); + public static bool IsSpecialized (Type type, [NotNullWhen(true)] out Specialization? sp) + { + var key = type.IsGenericType ? type.GetGenericTypeDefinition() : type; + return (sp = prefs.Specs.GetValueOrDefault(key)) != null; + } + + private static void ResolveRenames (Assembly ass) + { + foreach (var type in ass.GetExportedTypes()) + foreach (var meth in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + foreach (var attr in meth.CustomAttributes) + if (IsAttribute(attr)) + prefs.Module = meth.CreateDelegate>(); + else if (IsAttribute(attr)) + prefs.Node = meth.CreateDelegate>(); + else if (IsAttribute(attr)) + prefs.Member = meth.CreateDelegate>(); + } + + private static void ResolveSpecs (Assembly ass) + { + var imports = new Dictionary(); + var exports = new List<(Type Clr, Type Type)>(); + foreach (var type in ass.GetExportedTypes()) + foreach (var attr in type.CustomAttributes) + if (IsAttribute(attr)) + imports[GetAttributeArg(attr)!] = + (type, GetAttributeArg(attr, 1), GetAttributeArg(attr, 2)); + else if (IsAttribute(attr)) + exports.Add((GetAttributeArg(attr)!, type)); + foreach (var (clr, export) in exports) + prefs.Specs[clr] = imports.TryGetValue(clr, out var import) + ? new() { Import = import.Type, Export = export, JS = import.JS, Decl = import.Decl } + : throw new Error($"Specialized export '{export.FullName}' is missing the paired import."); + } + + private static FieldInfo GetMetaField (string prop) => + typeof(TypeMeta).GetField($"<{prop}>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance)!; + + extension (SurfaceMeta srf) + { + private IList MemberList => (IList)srf.Members; + } } diff --git a/src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs b/src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs deleted file mode 100644 index a6f0f8fc..00000000 --- a/src/cs/Bootsharp.Publish/Common/Preferences/PreferencesResolver.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Reflection; - -namespace Bootsharp.Publish; - -internal static class PreferencesResolver -{ - /// - /// Resolved preferences of the current build task. - /// - internal static AsyncLocal Resolved { get; } = new(); - - public static void Resolve (Assembly assembly) - { - var attribute = FindPreferencesAttribute(assembly); - Resolved.Value = CreatePreferences(attribute); - } - - private static CustomAttributeData? FindPreferencesAttribute (Assembly assembly) - { - foreach (var attr in assembly.CustomAttributes) - if (IsAttribute(attr)) - return attr; - return null; - } - - private static Preferences CreatePreferences (CustomAttributeData? attr) => new() { - Space = CreatePreferences(nameof(PreferencesAttribute.Space), attr) ?? [], - Name = CreatePreferences(nameof(PreferencesAttribute.Name), attr) ?? [], - Method = CreatePreferences(nameof(PreferencesAttribute.Method), attr) ?? [], - Property = CreatePreferences(nameof(PreferencesAttribute.Property), attr) ?? [], - Event = CreatePreferences(nameof(PreferencesAttribute.Event), attr) ?? [] - }; - - private static 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 static 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/Preferences/SpecializationResolver.cs b/src/cs/Bootsharp.Publish/Common/Preferences/SpecializationResolver.cs deleted file mode 100644 index 271f744c..00000000 --- a/src/cs/Bootsharp.Publish/Common/Preferences/SpecializationResolver.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace Bootsharp.Publish; - -internal static class SpecializationResolver -{ - private static AsyncLocal> async { get; } = new(); - private static Dictionary map => async.Value ??= []; - - public static void Resolve (Assembly ass) - { - var imports = new Dictionary(); - var exports = new List<(Type Clr, Type Type)>(); - foreach (var type in ass.GetExportedTypes()) - foreach (var attr in type.CustomAttributes) - if (IsAttribute(attr)) - imports[GetAttributeArg(attr)!] = - (type, GetAttributeArg(attr, 1), GetAttributeArg(attr, 2)); - else if (IsAttribute(attr)) - exports.Add((GetAttributeArg(attr)!, type)); - foreach (var (clr, export) in exports) - map[clr] = imports.TryGetValue(clr, out var import) - ? new() { Import = import.Type, Export = export, JS = import.JS, Decl = import.Decl } - : throw new Error($"Specialized export '{export.FullName}' is missing the paired import."); - } - - public static bool IsSpecialized (Type type) => IsSpecialized(type, out _); - public static bool IsSpecialized (Type type, [NotNullWhen(true)] out Specialization? sp) - { - var key = type.IsGenericType ? type.GetGenericTypeDefinition() : type; - return (sp = map.GetValueOrDefault(key)) != null; - } -} diff --git a/src/cs/Bootsharp.Publish/GenerateCS/GenerateCS.cs b/src/cs/Bootsharp.Publish/GenerateCS/GenerateCS.cs index 362791bd..403520f7 100644 --- a/src/cs/Bootsharp.Publish/GenerateCS/GenerateCS.cs +++ b/src/cs/Bootsharp.Publish/GenerateCS/GenerateCS.cs @@ -6,7 +6,6 @@ namespace Bootsharp.Publish; public sealed class GenerateCS : Microsoft.Build.Utilities.Task { public required string InspectedDirectory { get; set; } - public required string EntryAssemblyName { get; set; } public required string SerializerFilePath { get; set; } public required string InstancesFilePath { get; set; } public required string ModulesFilePath { get; set; } @@ -24,9 +23,9 @@ public override bool Execute () private SolutionInspection InspectSolution () { - var inspector = new SolutionInspector(EntryAssemblyName, Log); + var inspector = new SolutionInspector(Log); var inspected = Directory.GetFiles(InspectedDirectory, "*.dll").Order(); - return inspector.Inspect(InspectedDirectory, inspected); + return inspector.Inspect(inspected); } private void GenerateSerializer (SolutionInspection spec) diff --git a/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs b/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs index 1fd6d8f1..606bbada 100644 --- a/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs +++ b/src/cs/Bootsharp.Publish/GenerateJS/GenerateJS.cs @@ -31,9 +31,9 @@ public override bool Execute () private SolutionInspection InspectSolution () { - var inspector = new SolutionInspector(EntryAssemblyName, Log); + var inspector = new SolutionInspector(Log); var inspected = ResolveInspectedFiles(); - return inspector.Inspect(InspectedDirectory, inspected); + return inspector.Inspect(inspected); IEnumerable ResolveInspectedFiles () { diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 0d970fd1..9c68ad4f 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -61,7 +61,6 @@ - 0.8.0-alpha.430 + 0.8.0-alpha.435 Elringus javascript typescript ts js wasm node deno bun interop codegen https://bootsharp.com From 24ac8d6ddc595565e8cfeac9e05a75f457f01d8d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 31 May 2026 18:18:04 +0300 Subject: [PATCH 3/3] fmt --- .../GenerateJS/DeclarationTest.cs | 24 +++++++++---------- .../GenerateJS/JSModuleTest.cs | 14 +++++------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs index 043f4806..b03c3aa3 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateJS/DeclarationTest.cs @@ -1308,13 +1308,13 @@ public class Class public static class Prefs { - [RenameModule] + [RenameModule] public static string Module (Type type, string @default) => "index"; - - [RenameNode] + + [RenameNode] public static string Node (Type type, string @default) => type.Name == "Class" ? "Foo" : @default; - - [RenameMember] + + [RenameMember] public static string Member (MemberInfo info, string @default) => info.Name switch { "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default }; @@ -1360,12 +1360,12 @@ public interface IImported public static class Prefs { - [RenameModule] + [RenameModule] public static string Module (Type type, string @default) => "index"; - + [RenameNode] public static string Node (Type type, string @default) => type.IsInterface ? "Foo" : @default; - + [RenameMember] public static string Member (MemberInfo info, string @default) => info.Name switch { "Inv" => "bar", "Fun" => "baz", "State" => "qux", "Changed" => "quz", _ => @default @@ -1413,10 +1413,10 @@ public static class Prefs { [RenameModule] public static string Module (Type type, string @default) => null; - + [RenameNode] public static string Node (Type type, string @default) => type.Name == "IInst" ? "Foo" : @default; - + [RenameMember] public static string Member (MemberInfo info, string @default) => info.Name switch { "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default @@ -1453,7 +1453,7 @@ public interface IGone { void Bar (); } public static class Prefs { - [RenameNode] + [RenameNode] public static string Node (Type type, string @default) => type.Name == "IGone" ? null : @default; } """)); @@ -1473,7 +1473,7 @@ public interface IFoo { void Foo (); void Bar(); } public static class Prefs { - [RenameMember] + [RenameMember] public static string Member (MemberInfo info, string @default) => @default == "bar" ? null : @default; } """)); diff --git a/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs b/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs index 853fb796..a7072f6c 100644 --- a/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs +++ b/src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs @@ -748,12 +748,12 @@ public class Class public static class Prefs { - [RenameModule] + [RenameModule] public static string Module (Type type, string @default) => "index"; - + [RenameNode] public static string Node (Type type, string @default) => type.Name == "Class" ? "Foo" : @default; - + [RenameMember] public static string Member (MemberInfo info, string @default) => info.Name switch { "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default @@ -806,10 +806,10 @@ public static class Prefs { [RenameModule] public static string Module (Type type, string @default) => "index"; - + [RenameNode] public static string Node (Type type, string @default) => type.IsInterface ? "Foo" : @default; - + [RenameMember] public static string Member (MemberInfo info, string @default) => info.Name switch { "Inv" => "bar", "Fun" => "baz", "State" => "qux", "Changed" => "quz", _ => @default @@ -863,10 +863,10 @@ public static class Prefs { [RenameModule] public static string Module (Type type, string @default) => "index"; - + [RenameNode] public static string Node (Type type, string @default) => type.Name == "IInst" ? "Foo" : @default; - + [RenameMember] public static string Member (MemberInfo info, string @default) => info.Name switch { "Method" => "bar", "Property" => "baz", "Event" => "qux", _ => @default