Skip to content

Commit

Permalink
Escape reserved words in generated typedefs (#357)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongin authored Aug 14, 2024
1 parent 02ca89d commit 6ffa6a2
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 21 deletions.
1 change: 1 addition & 0 deletions docs/reference/msbuild-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The following properties can be used to customize the build processes for genera
| `GenerateNodeApiTypeDefinitions` | Set to `true` to generate TypeScript type definitions for .NET APIs in the current project. (This is enabled by default when referencing the `Microsoft.JavaScript.NodeApi.Generator` package.) See [Develop a Node.js addon module](../scenarios/js-dotnet-module). |
| `GenerateNodeApiTypeDefinitionsForReferences` | Set to `true` to generate TypeScript type definitions for .NET APIs in assemblies referenced by the current project. (This is enabled by default when **_an empty project_** references the `Microsoft.JavaScript.NodeApi.Generator` package.) See [Dynamically invoke .NET APIs from JavaScript](../scenarios/js-dotnet-dynamic). |
| `NodeApiTypeDefinitionsFileName` | Name of the type-definitions file generated for a project. Defaults to `$(TargetName).d.ts`. |
| `NodeApiTypeDefinitionsEnableWarnings` | Set to `true` to enable warnings when [generating type definitions](../features/type-definitions). The warnings are suppressed by default because they can be verbose when referencing system or external assemblies.
| `NodeApiJSModuleType` | Set to either `commonjs` or `esm` to specify the module system used by the generated type definitions. If unspecified, the module type is detected automatically from `package.json`, which is usually correct. |
| `NodeApiSystemReferenceAssembly` | Item-list of assembly names (not file paths) to be included in typedefs generator. The `System`, `System.Runtime`, and `System.Console` assemblies are included by default. Add system assembly names to the item-list to generate type definitions for them. System assemblies are provided by the installed .NET SDK. |
| `PublishNodeModule` | Set to `true` to produce a Native AOT `.node` binary and `.js` module-loader script when building the `Publish` target. The files will be placed in the directory indicated by the `PublishDir` variable. See [Develop a Node.js addon module with .NET Native AOT](../scenarios/js-aot-module). |
Expand Down
5 changes: 1 addition & 4 deletions src/NodeApi.Generator/NodeApi.Generator.targets
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,6 @@
Outputs="@(NodeApiReferenceAssemblies->'$(TargetDir)%(Filename).d.ts')"
Condition=" '$(GenerateNodeApiTypeDefinitions)' == 'true' AND '$(Compile)' == '' "
>
<PropertyGroup Condition=" '$(EnableTSGenerationWarnings)' != 'true' ">
<_SuppressTSGenerationWarnings>--nowarn</_SuppressTSGenerationWarnings>
</PropertyGroup>

<ItemGroup Condition="$(TargetFramework.StartsWith('net4'))">
<NodeApiSystemReferenceAssembly Include="mscorlib" />
<NodeApiSystemReferenceAssembly Include="System" />
Expand Down Expand Up @@ -172,6 +168,7 @@
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--assemblies &quot;$(_NodeApiGeneratorAssemblyReferences)&quot;" Overwrite="true" />
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--packs &quot;$(_NodeApiGeneratorTargetingPacks)&quot;" />
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--typedefs &quot;$(_NodeApiGeneratorTypeDefs)&quot;" />
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="--nowarn" Condition=" '$(NodeApiTypeDefinitionsEnableWarnings)' != 'true' " />
<WriteLinesToFile File="$(NodeApiGeneratorResponseFile)" Lines="$(NodeApiTypeDefinitionsGeneratorOptions)" />

<!-- Run the generator using args from the response file. Note the '@' indicates the response file NOT an MSBuild item-list. -->
Expand Down
68 changes: 61 additions & 7 deletions src/NodeApi.Generator/TypeDefinitionsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2332,12 +2332,66 @@ private static string FormatDocMemberParameterType(

private static string TSIdentifier(string? identifier)
{
return identifier switch
{
// A method parameter named "function" is valid in C# but invalid in TS.
"function" => "_" + identifier,
null => "_",
_ => identifier,
};
return string.IsNullOrEmpty(identifier) ? "_" :
s_tsReservedWords.Contains(identifier) ? "_" + identifier : identifier;
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
private static readonly HashSet<string> s_tsReservedWords = new()
{
"arguments",
"as",
"async",
"await",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"eval",
"export",
"extends",
"false",
"finally",
"for",
"from",
"function",
"get",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"new",
"null",
"of",
"package",
"private",
"protected",
"public",
"return",
"set",
"static",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
};
}
20 changes: 10 additions & 10 deletions test/TypeDefsGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ export interface SimpleInterface {
TestProperty: string;

/** method */
TestMethod(): string;
TestMethod(_default: string | undefined): string;
}
""".ReplaceLineEndings(),
GenerateTypeDefinition(typeof(SimpleInterface), new Dictionary<string, string>
{
["T:SimpleInterface"] = "interface",
["P:SimpleInterface.TestProperty"] = "property",
["M:SimpleInterface.TestMethod"] = "method",
["M:SimpleInterface.TestMethod(System.String)"] = "method",
}));
}

Expand All @@ -96,15 +96,15 @@ export class SimpleClass implements SimpleInterface {
TestProperty: string;

/** method */
TestMethod(): string;
TestMethod(_default: string | undefined): string;
}
""".ReplaceLineEndings(),
GenerateTypeDefinition(typeof(SimpleClass), new Dictionary<string, string>
{
["T:SimpleClass"] = "class",
["M:SimpleClass.#ctor"] = "constructor",
["P:SimpleClass.TestProperty"] = "property",
["M:SimpleClass.TestMethod"] = "method",
["M:SimpleClass.TestMethod(System.String)"] = "method",
}));
}

Expand All @@ -120,7 +120,7 @@ public void GenerateSimpleProperty()
[Fact]
public void GenerateSimpleMethod()
{
Assert.Equal(@"TestMethod(): string;",
Assert.Equal(@"TestMethod(_default: string | undefined): string;",
GenerateMemberDefinition(
typeof(SimpleClass).GetMethod(nameof(SimpleClass.TestMethod))!,
new Dictionary<string, string>()));
Expand Down Expand Up @@ -250,7 +250,7 @@ public void GenerateJSDocLink()
export interface SimpleInterface {
TestProperty: string;

TestMethod(): string;
TestMethod(_default: string | undefined): string;
}
""".ReplaceLineEndings(),
GenerateTypeDefinition(typeof(SimpleInterface), new Dictionary<string, XElement>
Expand Down Expand Up @@ -296,13 +296,13 @@ export interface SimpleClass {
public interface SimpleInterface
{
string TestProperty { get; set; }
string TestMethod();
string TestMethod(string? @default); // 'default' is a reserved word in JS
}

public class SimpleClass : SimpleInterface
{
public string TestProperty { get; set; } = null!;
public string TestMethod() { return string.Empty; }
public string TestMethod(string? @default) { return @default ?? string.Empty; }
}

public delegate void SimpleDelegate(string arg);
Expand Down Expand Up @@ -334,9 +334,9 @@ public class GenericClass<T> : GenericInterface<T>
public static class SimpleClassExtensions
{
public static void TestExtensionA(this SimpleClass value)
=> value.TestMethod();
=> value.TestMethod(null);
public static void TestExtensionB(this SimpleClass value)
=> value.TestMethod();
=> value.TestMethod(null);
}

#endif

0 comments on commit 6ffa6a2

Please sign in to comment.