From 54c2e8faa2ff32173785d20b017a50eed4e34720 Mon Sep 17 00:00:00 2001 From: JesseCol Date: Thu, 16 Oct 2025 12:33:43 -0700 Subject: [PATCH 1/2] Allow app to exclude types from metadata parsing with a NodeApiExcludeFromMetadata property --- examples/winappsdk/WinAppSdkExample.csproj | 37 ++++++++++ examples/winappsdk/example.js | 20 +++++ examples/winappsdk/package.json | 6 ++ .../NodeApi.Generator.targets | 2 + src/NodeApi.Generator/Program.cs | 10 ++- .../TypeDefinitionsGenerator.cs | 73 ++++++++++++++++--- 6 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 examples/winappsdk/WinAppSdkExample.csproj create mode 100644 examples/winappsdk/example.js create mode 100644 examples/winappsdk/package.json diff --git a/examples/winappsdk/WinAppSdkExample.csproj b/examples/winappsdk/WinAppSdkExample.csproj new file mode 100644 index 00000000..92c01a5f --- /dev/null +++ b/examples/winappsdk/WinAppSdkExample.csproj @@ -0,0 +1,37 @@ + + + + net8.0-windows10.0.19041.0 + 10.0.17763.0 + bin + x86;x64;ARM64 + win-x86;win-x64;win-arm64 + CommonJs + + + ABI.*;WinRT.IInspectable+Vftbl*;WinRT.Interop.IUnknownVftbl* + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/winappsdk/example.js b/examples/winappsdk/example.js new file mode 100644 index 00000000..9b6bd40f --- /dev/null +++ b/examples/winappsdk/example.js @@ -0,0 +1,20 @@ + + +const dotnet = require('node-api-dotnet'); + +require('./bin/Microsoft.Windows.SDK.NET.js'); +require('./bin/Microsoft.WindowsAppRuntime.Bootstrap.Net.js'); +require('./bin/Microsoft.InteractiveExperiences.Projection.js'); +require('./bin/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.js'); +require('./bin/Microsoft.Windows.AI.Text.Projection.js'); +require('./bin/Microsoft.Windows.AI.ContentSafety.Projection.js'); +require('./bin/Microsoft.Windows.AppNotifications.Projection.js'); +require('./bin/Microsoft.Windows.AppNotifications.Builder.Projection.js'); + +const majorVersion = 1; +const minorVersion = 8; + +console.log("Attempt to initialize the WindowsAppRuntime Bootstrapper. (This requires the WindowsAppRuntime " + majorVersion + "." + minorVersion + " to be installed on the system.)"); +const fullVersion = (majorVersion << 16) | minorVersion; +dotnet.Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Initialize(fullVersion); +console.log("Initialized Bootstraper. WindowsAppRuntime is now available."); diff --git a/examples/winappsdk/package.json b/examples/winappsdk/package.json new file mode 100644 index 00000000..e628bc64 --- /dev/null +++ b/examples/winappsdk/package.json @@ -0,0 +1,6 @@ +{ + "name": "node-api-dotnet-examples-winappsdk", + "dependencies": { + "node-api-dotnet": "file:../../out/pkg/node-api-dotnet" + } +} diff --git a/src/NodeApi.Generator/NodeApi.Generator.targets b/src/NodeApi.Generator/NodeApi.Generator.targets index 66e9ce62..3c572c62 100644 --- a/src/NodeApi.Generator/NodeApi.Generator.targets +++ b/src/NodeApi.Generator/NodeApi.Generator.targets @@ -71,6 +71,7 @@ + @@ -171,6 +172,7 @@ + diff --git a/src/NodeApi.Generator/Program.cs b/src/NodeApi.Generator/Program.cs index bc4b7a7b..c41f8039 100644 --- a/src/NodeApi.Generator/Program.cs +++ b/src/NodeApi.Generator/Program.cs @@ -39,6 +39,7 @@ public static class Program private static readonly List s_typeDefinitionsPaths = new(); private static readonly HashSet s_systemAssemblyIndexes = new(); private static readonly List s_moduleTypes = new(); + private static readonly List s_excludePatterns = new(); private static bool s_suppressWarnings; public static int Main(string[] args) @@ -56,6 +57,7 @@ public static int Main(string[] args) -t --typedefs Path to output type definitions file (required) -m --module Generate JS module(s) alongside typedefs (optional, multiple) Value: 'commonjs', 'esm', or path to package.json with "type" + -e --exclude Exclude types matching wildcard patterns from metadata parsing (optional) --nowarn Suppress warnings -? -h --help Show this help message @ Read response file for more options @@ -108,7 +110,8 @@ public static int Main(string[] args) modulePaths, s_targetFramework, isSystemAssembly: s_systemAssemblyIndexes.Contains(i), - s_suppressWarnings); + s_suppressWarnings, + s_excludePatterns); } return 0; @@ -206,6 +209,11 @@ void AddItems(List list, string items) } break; + case "-e": + case "--exclude": + AddItems(s_excludePatterns, args[++i]); + break; + case "--nowarn": s_suppressWarnings = true; break; diff --git a/src/NodeApi.Generator/TypeDefinitionsGenerator.cs b/src/NodeApi.Generator/TypeDefinitionsGenerator.cs index 80e4405d..ff136af2 100644 --- a/src/NodeApi.Generator/TypeDefinitionsGenerator.cs +++ b/src/NodeApi.Generator/TypeDefinitionsGenerator.cs @@ -218,7 +218,8 @@ public static void GenerateTypeDefinitions( IDictionary modulePaths, string? targetFramework = null, bool isSystemAssembly = false, - bool suppressWarnings = false) + bool suppressWarnings = false, + IEnumerable? excludePatterns = null) { if (string.IsNullOrEmpty(assemblyPath)) { @@ -282,6 +283,7 @@ public static void GenerateTypeDefinitions( SuppressWarnings = suppressWarnings, ExportAll = assemblyExportAttribute != null && GetExportAttributeValue(assemblyExportAttribute), + ExcludePatterns = excludePatterns?.ToList() ?? new List(), }; generator.LoadAssemblyDocs(); @@ -380,6 +382,8 @@ public TypeDefinitionsGenerator( public bool SuppressWarnings { get; set; } + public List ExcludePatterns { get; set; } = new(); + public override void ReportDiagnostic(Diagnostic diagnostic) { if (SuppressWarnings && diagnostic.Severity == DiagnosticSeverity.Warning) @@ -1260,8 +1264,23 @@ private void EndNamespace(ref SourceBuilder s, Type type) } } - private static bool IsExcluded(MemberInfo member) + private bool IsExcluded(MemberInfo member) { + Type type = member as Type ?? member.DeclaringType!; + + // Check user-provided exclude patterns first + if (ExcludePatterns.Count > 0) + { + string typeFullName = type.FullName ?? type.Name; + foreach (string pattern in ExcludePatterns) + { + if (IsWildcardMatch(typeFullName, pattern)) + { + return true; + } + } + } + if (member is PropertyInfo property) { return IsExcluded(property); @@ -1271,8 +1290,6 @@ private static bool IsExcluded(MemberInfo member) return IsExcluded(method); } - Type type = member as Type ?? member.DeclaringType!; - if (type.BaseType != null && IsExcluded(type.BaseType)) { return true; @@ -1301,14 +1318,41 @@ private static bool IsExcluded(MemberInfo member) }; } - private static bool IsExcluded(PropertyInfo property) + /// + /// Checks if a string matches a wildcard pattern. Supports * (any characters) and ? (single character). + /// + private static bool IsWildcardMatch(string input, string pattern) + { + if (string.IsNullOrEmpty(pattern)) return false; + if (string.IsNullOrEmpty(input)) return false; + + // Convert wildcard pattern to regex pattern + string regexPattern = "^" + Regex.Escape(pattern) + .Replace(@"\*", ".*") + .Replace(@"\?", ".") + "$"; + + return Regex.IsMatch(input, regexPattern, RegexOptions.IgnoreCase); + } + + private bool IsExcluded(PropertyInfo property) { - if (property.PropertyType.IsPointer) + Type propertyType; + try + { + propertyType = property.PropertyType; + } + catch (System.NotSupportedException e) + { + Console.WriteLine($"Error: Property {property.DeclaringType!.FullName}.{property.Name} could not be analyzed. ({e.GetType().Name})"); + throw e; + } + + if (propertyType.IsPointer) { return true; } - if (IsExcluded(property.PropertyType)) + if (IsExcluded(propertyType)) { return true; } @@ -1323,12 +1367,23 @@ private bool IsExcluded(MethodBase method) { return true; } + + System.Reflection.ParameterInfo[] methodParams; + try + { + methodParams = method.GetParameters(); + } + catch (System.NotSupportedException e) + { + Console.WriteLine($"Error: Method {method.DeclaringType!.FullName}.{method.Name}() could not be analyzed. ({e.GetType().Name})"); + throw e; + } // Exclude old style Begin/End async methods, as they always have Task-based alternatives. if ((method.Name.StartsWith("Begin") && (method as MethodInfo)?.ReturnType.FullName == typeof(IAsyncResult).FullName) || - (method.Name.StartsWith("End") && method.GetParameters().Length == 1 && - method.GetParameters()[0].ParameterType.FullName == typeof(IAsyncResult).FullName)) + (method.Name.StartsWith("End") && methodParams.Length == 1 && + methodParams[0].ParameterType.FullName == typeof(IAsyncResult).FullName)) { return true; } From 89ab871d94fda17686e6466ac211c3a485a992dd Mon Sep 17 00:00:00 2001 From: JesseCol Date: Thu, 16 Oct 2025 13:28:46 -0700 Subject: [PATCH 2/2] Use raw throw instead of re-throwing the exception. Also, whitespace fix. --- src/NodeApi.Generator/TypeDefinitionsGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NodeApi.Generator/TypeDefinitionsGenerator.cs b/src/NodeApi.Generator/TypeDefinitionsGenerator.cs index ff136af2..f4498064 100644 --- a/src/NodeApi.Generator/TypeDefinitionsGenerator.cs +++ b/src/NodeApi.Generator/TypeDefinitionsGenerator.cs @@ -1344,7 +1344,7 @@ private bool IsExcluded(PropertyInfo property) catch (System.NotSupportedException e) { Console.WriteLine($"Error: Property {property.DeclaringType!.FullName}.{property.Name} could not be analyzed. ({e.GetType().Name})"); - throw e; + throw; } if (propertyType.IsPointer) @@ -1367,7 +1367,7 @@ private bool IsExcluded(MethodBase method) { return true; } - + System.Reflection.ParameterInfo[] methodParams; try { @@ -1376,7 +1376,7 @@ private bool IsExcluded(MethodBase method) catch (System.NotSupportedException e) { Console.WriteLine($"Error: Method {method.DeclaringType!.FullName}.{method.Name}() could not be analyzed. ({e.GetType().Name})"); - throw e; + throw; } // Exclude old style Begin/End async methods, as they always have Task-based alternatives.