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