Skip to content

Commit 50d4991

Browse files
authored
Allow app to exclude types from metadata parsing with a NodeApiExcludeFromMetadata property (#462)
Co-authored-by: JesseCol <[email protected]>
1 parent 11c5a48 commit 50d4991

File tree

6 files changed

+138
-10
lines changed

6 files changed

+138
-10
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
5+
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
6+
<OutDir>bin</OutDir>
7+
<Platforms>x86;x64;ARM64</Platforms>
8+
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
9+
<NodeApiJSModuleType>CommonJs</NodeApiJSModuleType>
10+
11+
<!-- Exclude types from metadata parsing that cause the metadata parser to fail -->
12+
<NodeApiExcludeFromMetadata>ABI.*;WinRT.IInspectable+Vftbl*;WinRT.Interop.IUnknownVftbl*</NodeApiExcludeFromMetadata>
13+
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<!-- We need to add references to these assemblies so we can complete the type closure so that
18+
the generator can find all the types it needs. -->
19+
<UserRuntimeAssembly Include="bin\Microsoft.Windows.SDK.NET.dll" />
20+
<UserRuntimeAssembly Include="bin\WinRT.Runtime.dll" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<Compile Remove="node_modules\**\*.cs" />
25+
<EmbeddedResource Remove="node_modules\**\*.cs" />
26+
<Compile Include="src\*.cs" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="$(NodeApiDotnetPackageVersion)" PrivateAssets="all" />
31+
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="$(NodeApiDotnetPackageVersion)" />
32+
33+
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
34+
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.250916003" />
35+
<PackageReference Include="System.Numerics.Tensors" Version="9.0.10" />
36+
</ItemGroup>
37+
</Project>

examples/winappsdk/example.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
3+
const dotnet = require('node-api-dotnet');
4+
5+
require('./bin/Microsoft.Windows.SDK.NET.js');
6+
require('./bin/Microsoft.WindowsAppRuntime.Bootstrap.Net.js');
7+
require('./bin/Microsoft.InteractiveExperiences.Projection.js');
8+
require('./bin/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.js');
9+
require('./bin/Microsoft.Windows.AI.Text.Projection.js');
10+
require('./bin/Microsoft.Windows.AI.ContentSafety.Projection.js');
11+
require('./bin/Microsoft.Windows.AppNotifications.Projection.js');
12+
require('./bin/Microsoft.Windows.AppNotifications.Builder.Projection.js');
13+
14+
const majorVersion = 1;
15+
const minorVersion = 8;
16+
17+
console.log("Attempt to initialize the WindowsAppRuntime Bootstrapper. (This requires the WindowsAppRuntime " + majorVersion + "." + minorVersion + " to be installed on the system.)");
18+
const fullVersion = (majorVersion << 16) | minorVersion;
19+
dotnet.Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Initialize(fullVersion);
20+
console.log("Initialized Bootstraper. WindowsAppRuntime is now available.");

examples/winappsdk/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "node-api-dotnet-examples-winappsdk",
3+
"dependencies": {
4+
"node-api-dotnet": "file:../../out/pkg/node-api-dotnet"
5+
}
6+
}

src/NodeApi.Generator/NodeApi.Generator.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--packs &quot;$(_NodeApiGeneratorTargetingPacks)&quot;" />
7272
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--reference &quot;$(_NodeApiGeneratorAssemblyReferences)&quot;" />
7373
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--typedefs &quot;$(TargetDir)$(NodeApiTypeDefinitionsFileName)&quot;" />
74+
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--exclude &quot;$(NodeApiExcludeFromMetadata.Replace(';', '%3B'))&quot;" Condition=" '$(NodeApiExcludeFromMetadata)' != '' " />
7475
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="$(NodeApiTypeDefinitionsGeneratorOptions)" />
7576

7677
<!-- Run the generator using args from the response file. Note the '@' indicates the response file NOT an MSBuild item-list. -->
@@ -171,6 +172,7 @@
171172
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--packs &quot;$(_NodeApiGeneratorTargetingPacks)&quot;" />
172173
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--typedefs &quot;$(_NodeApiGeneratorTypeDefs)&quot;" />
173174
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--nowarn" Condition=" '$(NodeApiTypeDefinitionsEnableWarnings)' != 'true' " />
175+
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--exclude &quot;$(NodeApiExcludeFromMetadata.Replace(';', '%3B'))&quot;" Condition=" '$(NodeApiExcludeFromMetadata)' != '' " />
174176
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="$(NodeApiTypeDefinitionsGeneratorOptions)" />
175177

176178
<!-- Run the generator using args from the response file. Note the '@' indicates the response file NOT an MSBuild item-list. -->

src/NodeApi.Generator/Program.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public static class Program
3939
private static readonly List<string> s_typeDefinitionsPaths = new();
4040
private static readonly HashSet<int> s_systemAssemblyIndexes = new();
4141
private static readonly List<TypeDefinitionsGenerator.ModuleType> s_moduleTypes = new();
42+
private static readonly List<string> s_excludePatterns = new();
4243
private static bool s_suppressWarnings;
4344

4445
public static int Main(string[] args)
@@ -56,6 +57,7 @@ public static int Main(string[] args)
5657
-t --typedefs Path to output type definitions file (required)
5758
-m --module Generate JS module(s) alongside typedefs (optional, multiple)
5859
Value: 'commonjs', 'esm', or path to package.json with "type"
60+
-e --exclude Exclude types matching wildcard patterns from metadata parsing (optional)
5961
--nowarn Suppress warnings
6062
-? -h --help Show this help message
6163
@<file> Read response file for more options
@@ -108,7 +110,8 @@ public static int Main(string[] args)
108110
modulePaths,
109111
s_targetFramework,
110112
isSystemAssembly: s_systemAssemblyIndexes.Contains(i),
111-
s_suppressWarnings);
113+
s_suppressWarnings,
114+
s_excludePatterns);
112115
}
113116

114117
return 0;
@@ -206,6 +209,11 @@ void AddItems(List<string> list, string items)
206209
}
207210
break;
208211

212+
case "-e":
213+
case "--exclude":
214+
AddItems(s_excludePatterns, args[++i]);
215+
break;
216+
209217
case "--nowarn":
210218
s_suppressWarnings = true;
211219
break;

src/NodeApi.Generator/TypeDefinitionsGenerator.cs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ public static void GenerateTypeDefinitions(
218218
IDictionary<ModuleType, string> modulePaths,
219219
string? targetFramework = null,
220220
bool isSystemAssembly = false,
221-
bool suppressWarnings = false)
221+
bool suppressWarnings = false,
222+
IEnumerable<string>? excludePatterns = null)
222223
{
223224
if (string.IsNullOrEmpty(assemblyPath))
224225
{
@@ -282,6 +283,7 @@ public static void GenerateTypeDefinitions(
282283
SuppressWarnings = suppressWarnings,
283284
ExportAll = assemblyExportAttribute != null &&
284285
GetExportAttributeValue(assemblyExportAttribute),
286+
ExcludePatterns = excludePatterns?.ToList() ?? new List<string>(),
285287
};
286288

287289
generator.LoadAssemblyDocs();
@@ -380,6 +382,8 @@ public TypeDefinitionsGenerator(
380382

381383
public bool SuppressWarnings { get; set; }
382384

385+
public List<string> ExcludePatterns { get; set; } = new();
386+
383387
public override void ReportDiagnostic(Diagnostic diagnostic)
384388
{
385389
if (SuppressWarnings && diagnostic.Severity == DiagnosticSeverity.Warning)
@@ -1260,8 +1264,23 @@ private void EndNamespace(ref SourceBuilder s, Type type)
12601264
}
12611265
}
12621266

1263-
private static bool IsExcluded(MemberInfo member)
1267+
private bool IsExcluded(MemberInfo member)
12641268
{
1269+
Type type = member as Type ?? member.DeclaringType!;
1270+
1271+
// Check user-provided exclude patterns first
1272+
if (ExcludePatterns.Count > 0)
1273+
{
1274+
string typeFullName = type.FullName ?? type.Name;
1275+
foreach (string pattern in ExcludePatterns)
1276+
{
1277+
if (IsWildcardMatch(typeFullName, pattern))
1278+
{
1279+
return true;
1280+
}
1281+
}
1282+
}
1283+
12651284
if (member is PropertyInfo property)
12661285
{
12671286
return IsExcluded(property);
@@ -1271,8 +1290,6 @@ private static bool IsExcluded(MemberInfo member)
12711290
return IsExcluded(method);
12721291
}
12731292

1274-
Type type = member as Type ?? member.DeclaringType!;
1275-
12761293
if (type.BaseType != null && IsExcluded(type.BaseType))
12771294
{
12781295
return true;
@@ -1301,14 +1318,41 @@ private static bool IsExcluded(MemberInfo member)
13011318
};
13021319
}
13031320

1304-
private static bool IsExcluded(PropertyInfo property)
1321+
/// <summary>
1322+
/// Checks if a string matches a wildcard pattern. Supports * (any characters) and ? (single character).
1323+
/// </summary>
1324+
private static bool IsWildcardMatch(string input, string pattern)
13051325
{
1306-
if (property.PropertyType.IsPointer)
1326+
if (string.IsNullOrEmpty(pattern)) return false;
1327+
if (string.IsNullOrEmpty(input)) return false;
1328+
1329+
// Convert wildcard pattern to regex pattern
1330+
string regexPattern = "^" + Regex.Escape(pattern)
1331+
.Replace(@"\*", ".*")
1332+
.Replace(@"\?", ".") + "$";
1333+
1334+
return Regex.IsMatch(input, regexPattern, RegexOptions.IgnoreCase);
1335+
}
1336+
1337+
private bool IsExcluded(PropertyInfo property)
1338+
{
1339+
Type propertyType;
1340+
try
1341+
{
1342+
propertyType = property.PropertyType;
1343+
}
1344+
catch (System.NotSupportedException e)
1345+
{
1346+
Console.WriteLine($"Error: Property {property.DeclaringType!.FullName}.{property.Name} could not be analyzed. ({e.GetType().Name})");
1347+
throw;
1348+
}
1349+
1350+
if (propertyType.IsPointer)
13071351
{
13081352
return true;
13091353
}
13101354

1311-
if (IsExcluded(property.PropertyType))
1355+
if (IsExcluded(propertyType))
13121356
{
13131357
return true;
13141358
}
@@ -1324,11 +1368,22 @@ private bool IsExcluded(MethodBase method)
13241368
return true;
13251369
}
13261370

1371+
System.Reflection.ParameterInfo[] methodParams;
1372+
try
1373+
{
1374+
methodParams = method.GetParameters();
1375+
}
1376+
catch (System.NotSupportedException e)
1377+
{
1378+
Console.WriteLine($"Error: Method {method.DeclaringType!.FullName}.{method.Name}() could not be analyzed. ({e.GetType().Name})");
1379+
throw;
1380+
}
1381+
13271382
// Exclude old style Begin/End async methods, as they always have Task-based alternatives.
13281383
if ((method.Name.StartsWith("Begin") &&
13291384
(method as MethodInfo)?.ReturnType.FullName == typeof(IAsyncResult).FullName) ||
1330-
(method.Name.StartsWith("End") && method.GetParameters().Length == 1 &&
1331-
method.GetParameters()[0].ParameterType.FullName == typeof(IAsyncResult).FullName))
1385+
(method.Name.StartsWith("End") && methodParams.Length == 1 &&
1386+
methodParams[0].ParameterType.FullName == typeof(IAsyncResult).FullName))
13321387
{
13331388
return true;
13341389
}

0 commit comments

Comments
 (0)