-
Notifications
You must be signed in to change notification settings - Fork 0
JSExport TypeScript codegen support #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
f4dbbda
ignore pack dir
ArcadeMode e1896c4
keep jsexports as codegen input
ArcadeMode e4ffe91
update packages
ArcadeMode 72f7b9e
add tests for generator expectations
ArcadeMode 29eb974
infobuilding for jsexport such that ts code rendering works and cs is…
ArcadeMode 31804e7
throw on mixed exports
ArcadeMode 969704c
implement error diagnostic for mixed exports
ArcadeMode 3ccd85a
update analyzer releases
ArcadeMode 2abd000
guard invalid codegen
ArcadeMode 46c0e37
e2e tests for jsexport codegen
ArcadeMode 39643f4
relax jsexport signature validation to cover complex types containing…
ArcadeMode 51eb79b
fix diagnostics warning
ArcadeMode 374d575
Merge branch 'master' into jsexport-support
ArcadeMode 4664cf6
fix: consistent analyzer rule ID/category, FQN attribute matching via…
Copilot a825017
colocate duplicated logic in shared symbolfacts
ArcadeMode 9bb62bb
ammend
ArcadeMode File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
|
|
||
| # production | ||
| /build | ||
| **/pack | ||
|
|
||
| # misc | ||
| .DS_Store | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| using System.Linq; | ||
| using System.Runtime.InteropServices.JavaScript; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace TypeShim.E2E.Wasm; | ||
|
|
||
| public static partial class JSExportClass | ||
| { | ||
| private static object? _identityObject; | ||
|
|
||
| [JSExport] | ||
| public static int Add(int a, int b) => a + b; | ||
|
|
||
| [JSExport] | ||
| public static int GetSum(int[] ints) => ints.Sum(); | ||
|
|
||
| [JSExport] | ||
| public static string GetGreeting() => "Hello, from JSExport"; | ||
|
|
||
| [JSExport] | ||
| public static bool IsEven(int value) => value % 2 == 0; | ||
|
|
||
| [JSExport] | ||
| public static double MultiplyDouble(double left, double right) => left * right; | ||
|
|
||
| [JSExport] | ||
| public static string Describe(int x, bool y, string z) => $"{x}:{y}:{z}"; | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Promise<JSType.Boolean>>] | ||
| public static Task<bool> IsPositiveAsync([JSMarshalAs<JSType.Number>] int value) => Task.FromResult(value > 0); | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Promise<JSType.Number>>] | ||
| public static Task<int> AddAsync([JSMarshalAs<JSType.Number>] int a, [JSMarshalAs<JSType.Number>] int b) => Task.FromResult(a + b); | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Promise<JSType.Void>>] | ||
| public static Task CompleteAsync() => Task.CompletedTask; | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Any>] | ||
| public static object CreateIdentityObject([JSMarshalAs<JSType.Number>] int id) => new IdentityPayload(id); | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Void>] | ||
| public static void RememberObject([JSMarshalAs<JSType.Any>] object obj) => _identityObject = obj; | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Number>] | ||
| public static int ReadIdentityId([JSMarshalAs<JSType.Any>] object obj) => ((IdentityPayload)obj).Id; | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Promise<JSType.Number>>] | ||
| public static Task<int> ReadIdentityIdAsync([JSMarshalAs<JSType.Any>] object obj) => Task.FromResult(((IdentityPayload)obj).Id); | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Array<JSType.Number>>] | ||
| public static int[] ReadIdentityIds([JSMarshalAs<JSType.Array<JSType.Any>>] object[] objs) => objs.Select(obj => ((IdentityPayload)obj).Id).ToArray(); | ||
|
|
||
| [JSExport] | ||
| [return: JSMarshalAs<JSType.Boolean>] | ||
| public static bool IsRememberedObject([JSMarshalAs<JSType.Any>] object obj) => ReferenceEquals(_identityObject, obj); | ||
|
|
||
| [JSExport] | ||
| public static string[] PrefixAll(string[] values, string prefix) => values.Select(value => $"{prefix}{value}").ToArray(); | ||
|
|
||
| private sealed record IdentityPayload(int Id); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { describe, expect, test } from 'vitest'; | ||
| import { JSExportClass } from '@typeshim/e2e-wasm-lib'; | ||
|
|
||
| describe('JSExportClass primitives and tasks', () => { | ||
| test('Add returns number', () => { | ||
| expect(JSExportClass.Add(2, 3)).toBe(5); | ||
| }); | ||
|
|
||
| test('GetSum handles number arrays', () => { | ||
| expect(JSExportClass.GetSum([1, 2, 3, 4])).toBe(10); | ||
| }); | ||
|
|
||
| test('GetGreeting returns string', () => { | ||
| expect(JSExportClass.GetGreeting()).toBe('Hello, from JSExport'); | ||
| }); | ||
|
|
||
| test('IsEven returns true for even value', () => { | ||
| expect(JSExportClass.IsEven(4)).toBe(true); | ||
| }); | ||
|
|
||
| test('IsEven returns false for odd value', () => { | ||
| expect(JSExportClass.IsEven(5)).toBe(false); | ||
| }); | ||
|
|
||
| test('MultiplyDouble returns expected product', () => { | ||
| expect(JSExportClass.MultiplyDouble(2.5, 4)).toBeCloseTo(10); | ||
| }); | ||
|
|
||
| test('Describe handles multiple primitive parameters', () => { | ||
| expect(JSExportClass.Describe(7, true, 'x')).toBe('7:True:x'); | ||
| }); | ||
|
|
||
| test('IsPositiveAsync resolves true/false correctly', async () => { | ||
| await expect(JSExportClass.IsPositiveAsync(3)).resolves.toBe(true); | ||
| await expect(JSExportClass.IsPositiveAsync(-1)).resolves.toBe(false); | ||
| }); | ||
|
|
||
| test('AddAsync resolves sum', async () => { | ||
| await expect(JSExportClass.AddAsync(10, 20)).resolves.toBe(30); | ||
| }); | ||
|
|
||
| test('CompleteAsync resolves void', async () => { | ||
| await expect(JSExportClass.CompleteAsync()).resolves.toBeUndefined(); | ||
| }); | ||
|
|
||
| test('Object identity is preserved on C# side with sync method', () => { | ||
| const identityObject = JSExportClass.CreateIdentityObject(321); | ||
| JSExportClass.RememberObject(identityObject); | ||
|
|
||
| expect(JSExportClass.ReadIdentityId(identityObject)).toBe(321); | ||
| expect(JSExportClass.IsRememberedObject(identityObject)).toBe(true); | ||
| }); | ||
|
|
||
| test('Object identity is preserved on C# side with async method', async () => { | ||
| const identityObject = JSExportClass.CreateIdentityObject(654); | ||
| JSExportClass.RememberObject(identityObject); | ||
|
|
||
| await expect(JSExportClass.ReadIdentityIdAsync(identityObject)).resolves.toBe(654); | ||
| expect(JSExportClass.IsRememberedObject(identityObject)).toBe(true); | ||
| }); | ||
|
|
||
| test('ReadIdentityIds returns ids for all objects in array', async () => { | ||
| const identityObject1 = JSExportClass.CreateIdentityObject(654); | ||
| const identityObject2 = JSExportClass.CreateIdentityObject(321); | ||
| const identityObjects = [identityObject1, identityObject2]; | ||
| expect(JSExportClass.ReadIdentityIds(identityObjects)).toEqual(new Int32Array([654, 321])); | ||
| }); | ||
|
|
||
| test('Can pass string array around the boundary', () => { | ||
| expect(JSExportClass.PrefixAll(['a', 'b', 'c'], 'pre-')).toEqual(['pre-a', 'pre-b', 'pre-c']); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_JSExport.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using TypeShim.Generator.CSharp; | ||
| using TypeShim.Generator.Parsing; | ||
| using TypeShim.Shared; | ||
|
|
||
| namespace TypeShim.Generator.Tests.CSharp; | ||
|
|
||
| internal class CSharpInteropClassRendererTests_JSExport | ||
| { | ||
| [Test] | ||
| public void CSharpInteropClass_JSExportOnlyClass_GeneratesNothing() | ||
| { | ||
| SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(""" | ||
| using System; | ||
| using System.Runtime.InteropServices.JavaScript; | ||
| namespace N1; | ||
| public partial class C1 | ||
| { | ||
| [JSExport] | ||
| public static int M1() => 1; | ||
| } | ||
| """); | ||
|
|
||
| SymbolExtractor symbolExtractor = new([CSharpFileInfo.Create(syntaxTree)], TestFixture.TargetingPackRefDir); | ||
| List<INamedTypeSymbol> exportedClasses = [.. symbolExtractor.ExtractAllExportedSymbols()]; | ||
| Assert.That(exportedClasses, Has.Count.EqualTo(1)); | ||
| INamedTypeSymbol classSymbol = exportedClasses[0]; | ||
|
|
||
| InteropTypeInfoCache typeInfoCache = new(); | ||
| ClassInfo classInfo = new ClassInfoBuilder(classSymbol, typeInfoCache).Build(); | ||
| RenderContext renderContext = new(classInfo, [classInfo], RenderOptions.CSharp); | ||
| string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); | ||
|
|
||
| // No C# interop wrapper should be generated for a JSExport-only class - | ||
| // the methods are already WASM-exported by Roslyn's source generator. | ||
| Assert.That(interopClass.Trim(), Is.Empty); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.