From c6e263e2699e767435bbb5cca08f5dc988cd3719 Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Sun, 21 Feb 2021 00:58:34 +0000 Subject: [PATCH] Add work-in-progress TypeScript interface bindings generator. --- src/CLI/CLI.cs | 4 + src/Generator/Driver.cs | 9 +- src/Generator/Generator.cs | 3 +- src/Generator/Generators/TS/TSGenerator.cs | 41 ++ src/Generator/Generators/TS/TSSources.cs | 505 ++++++++++++++++++ src/Generator/Generators/TS/TSTypePrinter.cs | 166 ++++++ src/Generator/Generators/TypePrinter.cs | 6 +- .../Passes/CheckDuplicatedNamesPass.cs | 1 + tests2/ts/.gitignore | 3 + tests2/ts/test.sh | 32 ++ tests2/ts/test.ts | 73 +++ tests2/ts/tsconfig.json | 13 + 12 files changed, 853 insertions(+), 3 deletions(-) create mode 100644 src/Generator/Generators/TS/TSGenerator.cs create mode 100644 src/Generator/Generators/TS/TSSources.cs create mode 100644 src/Generator/Generators/TS/TSTypePrinter.cs create mode 100644 tests2/ts/.gitignore create mode 100755 tests2/ts/test.sh create mode 100644 tests2/ts/test.ts create mode 100644 tests2/ts/tsconfig.json diff --git a/src/CLI/CLI.cs b/src/CLI/CLI.cs index 7715195b..4306ca7d 100644 --- a/src/CLI/CLI.cs +++ b/src/CLI/CLI.cs @@ -225,6 +225,10 @@ namespace CppSharp case "qjs": options.Kind = CppSharp.Generators.GeneratorKind.QuickJS; return; + case "ts": + case "typescript": + options.Kind = CppSharp.Generators.GeneratorKind.TypeScript; + return; } errorMessages.Add($"Unknown generator kind: {generator}."); diff --git a/src/Generator/Driver.cs b/src/Generator/Driver.cs index f4b04b21..57cc5e57 100644 --- a/src/Generator/Driver.cs +++ b/src/Generator/Driver.cs @@ -8,6 +8,7 @@ using CppSharp.Generators.C; using CppSharp.Generators.CLI; using CppSharp.Generators.Cpp; using CppSharp.Generators.CSharp; +using CppSharp.Generators.TS; using CppSharp.Parser; using CppSharp.Passes; using CppSharp.Utils; @@ -46,6 +47,8 @@ namespace CppSharp return new QuickJSGenerator(Context); case GeneratorKind.NAPI: return new NAPIGenerator(Context); + case GeneratorKind.TypeScript: + return new TSGenerator(Context); } throw new NotImplementedException(); @@ -238,7 +241,11 @@ namespace CppSharp TranslationUnitPasses.AddPass(new CheckMacroPass()); TranslationUnitPasses.AddPass(new CheckStaticClass()); - TranslationUnitPasses.AddPass(new CheckAmbiguousFunctions()); + if (Options.IsCLIGenerator || Options.IsCSharpGenerator || Options.IsCppGenerator) + { + TranslationUnitPasses.AddPass(new CheckAmbiguousFunctions()); + } + TranslationUnitPasses.AddPass(new ConstructorToConversionOperatorPass()); TranslationUnitPasses.AddPass(new MarshalPrimitivePointersAsRefTypePass()); diff --git a/src/Generator/Generator.cs b/src/Generator/Generator.cs index f017a468..4b135948 100644 --- a/src/Generator/Generator.cs +++ b/src/Generator/Generator.cs @@ -18,7 +18,8 @@ namespace CppSharp.Generators Java, Swift, QuickJS, - NAPI + NAPI, + TypeScript } /// diff --git a/src/Generator/Generators/TS/TSGenerator.cs b/src/Generator/Generators/TS/TSGenerator.cs new file mode 100644 index 00000000..fa452671 --- /dev/null +++ b/src/Generator/Generators/TS/TSGenerator.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using CppSharp.AST; +using CppSharp.Generators.C; +using CppSharp.Passes; + +namespace CppSharp.Generators.TS +{ + /// + /// C++ generator responsible for driving the generation of source and + /// header files. + /// + public class TSGenerator : CGenerator + { + private readonly TSTypePrinter typePrinter; + + public TSGenerator(BindingContext context) : base(context) + { + typePrinter = new TSTypePrinter(Context); + } + + public override List Generate(IEnumerable units) + { + var outputs = new List(); + + var header = new TSSources(Context, units); + outputs.Add(header); + + return outputs; + } + + public override bool SetupPasses() + { + return true; + } + + protected override string TypePrinterDelegate(Type type) + { + return type.Visit(typePrinter).ToString(); + } + } +} diff --git a/src/Generator/Generators/TS/TSSources.cs b/src/Generator/Generators/TS/TSSources.cs new file mode 100644 index 00000000..64a5b892 --- /dev/null +++ b/src/Generator/Generators/TS/TSSources.cs @@ -0,0 +1,505 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CppSharp.AST; +using CppSharp.AST.Extensions; +using CppSharp.Generators.C; +using CppSharp.Generators.CLI; + +namespace CppSharp.Generators.TS +{ + /// + /// Generates TypeScript interface files. + /// + public class TSSources : CCodeGenerator + { + public TSSources(BindingContext context, IEnumerable units) + : base(context, units) + { + typePrinter = new TSTypePrinter(Context); + } + + public override string FileExtension => "d.ts"; + + public override bool GenerateSemicolonAsDeclarationTerminator => false; + + public virtual bool GenerateNamespaces => true; + + public virtual bool GenerateSelectiveImports => false; + + public override void Process() + { + GenerateFilePreamble(CommentKind.BCPL); + + if (GenerateSelectiveImports) + GenerateImports(); + else + GenerateWildcardImports(); + + GenerateMain(); + } + + public virtual void GenerateMain() + { + VisitTranslationUnit(TranslationUnit); + } + + public virtual Dictionary> ComputeExternalReferences() + { + var typeReferenceCollector = new CLITypeReferenceCollector(Context.TypeMaps, + Context.Options); + typeReferenceCollector.Process(TranslationUnit); + + var typeReferences = typeReferenceCollector.TypeReferences; + var imports = new Dictionary>(); + + foreach (var typeRef in typeReferences) + { + if (typeRef.Include.TranslationUnit == TranslationUnit) + continue; + + if (typeRef.Include.File == TranslationUnit.FileName) + continue; + + var include = typeRef.Include; + var typeRefUnit = include.TranslationUnit; + + if (typeRefUnit != null && !typeRefUnit.IsDeclared) + continue; + + if (!imports.ContainsKey(typeRefUnit)) + imports[typeRefUnit] = new List(); + + imports[typeRefUnit].Add(typeRef.Declaration); + } + + return imports; + } + + public virtual bool NeedsExternalImports() + { + var imports = ComputeExternalReferences(); + return imports.Keys.Count > 0; + } + + public virtual void GenerateWildcardImports() + { + if (!NeedsExternalImports()) + return; + + foreach (var module in Options.Modules) + { + if (module == Options.SystemModule) + continue; + + WriteLine($"import * as {module.LibraryName} from \"{module.LibraryName}\";"); + } + } + + public virtual void GenerateImports() + { + PushBlock(BlockKind.ForwardReferences); + + var imports = ComputeExternalReferences(); + + foreach (var unit in imports) + { + string unitName = unit.Key.FileNameWithoutExtension; + if (Options.GenerateName != null) + unitName = Options.GenerateName(unit.Key); + + var names = string.Join(", ", unit.Value.Select(d => d.Name)); + WriteLine($"import {{{names}}} from \"{unitName}\";"); + } + + PopBlock(NewLineKind.BeforeNextBlock); + } + + public override bool VisitNamespace(Namespace @namespace) + { + var isTopLevel = @namespace is TranslationUnit; + var generateNamespace = GenerateNamespaces; + + if (generateNamespace) + { + PushBlock(BlockKind.Namespace, @namespace); + WriteLine(isTopLevel + ? $"declare module \"{@namespace.TranslationUnit.Module.LibraryName}\"" + : $"namespace {@namespace.Name}"); + WriteOpenBraceAndIndent(); + } + + VisitDeclContext(@namespace); + + if (generateNamespace) + { + UnindentAndWriteCloseBrace(); + PopBlock(NewLineKind.BeforeNextBlock); + } + + return true; + } + + public override bool VisitClassDecl(Class @class) + { + if (!@class.IsGenerated || @class.IsIncomplete) + return false; + + //if (@class.IsOpaque) + // return false; + + PushBlock(BlockKind.Class, @class); + + GenerateDeclarationCommon(@class); + + GenerateClassSpecifier(@class); + + if (@class.IsOpaque) + { + WriteLine(";"); + return false; + } + + NewLine(); + WriteLine("{"); + NewLine(); + + // Process the nested types. + Indent(); + VisitDeclContext(@class); + Unindent(); + + GenerateClassConstructors(@class); + GenerateClassProperties(@class); + GenerateClassEvents(@class); + GenerateClassMethods(@class.Methods); + + GenerateClassVariables(@class); + + PushBlock(BlockKind.Fields); + GenerateClassFields(@class); + PopBlock(); + + WriteLine("}"); + + PopBlock(NewLineKind.BeforeNextBlock); + + return true; + } + + public void GenerateClassConstructors(Class @class) + { + if (@class.IsStatic) + return; + + Indent(); + + var classNativeName = @class.Visit(CTypePrinter); + + foreach (var ctor in @class.Constructors) + { + if (ASTUtils.CheckIgnoreMethod(ctor) || FunctionIgnored(ctor)) + continue; + + ctor.Visit(this); + } + + Unindent(); + } + + public void GenerateClassFields(Class @class) + { + // Handle the case of struct (value-type) inheritance by adding the base + // properties to the managed value subtypes. + if (@class.IsValueType) + { + foreach (var @base in @class.Bases.Where(b => b.IsClass && b.Class.IsDeclared)) + { + GenerateClassFields(@base.Class); + } + } + + Indent(); + // check for value types because some of the ignored fields may back properties; + // not the case for ref types because the NativePtr pattern is used there + foreach (var field in @class.Fields.Where(f => !ASTUtils.CheckIgnoreField(f))) + { + var property = @class.Properties.FirstOrDefault(p => p.Field == field); + if (property != null && !property.IsInRefTypeAndBackedByValueClassField()) + { + field.Visit(this); + } + } + Unindent(); + } + + public void GenerateClassMethods(List methods) + { + if (methods.Count == 0) + return; + + Indent(); + + var @class = (Class) methods[0].Namespace; + + if (@class.IsValueType) + foreach (var @base in @class.Bases.Where(b => b.IsClass && !b.Class.Ignore)) + GenerateClassMethods(@base.Class.Methods.Where(m => !m.IsOperator).ToList()); + + var staticMethods = new List(); + foreach (var method in methods) + { + if (ASTUtils.CheckIgnoreMethod(method) || FunctionIgnored(method)) + continue; + + if (method.IsConstructor) + continue; + + if (method.IsOperator) + continue; + + if (method.IsStatic) + { + staticMethods.Add(method); + continue; + } + + method.Visit(this); + } + + foreach(var method in staticMethods) + method.Visit(this); + + Unindent(); + } + + public void GenerateClassVariables(Class @class) + { + foreach (var variable in @class.Variables) + { + if (!variable.IsGenerated) continue; + variable.Visit(this); + } + } + + public static string GetClassTemplateParameters(Class @class) + { + var @params = @class.TemplateParameters.OfType().Select( + p => !string.IsNullOrEmpty(p.Constraint) ? $"{p.Name} extends {p.Constraint}" : p.Name); + + return $"<{string.Join(", ", @params)}>"; + } + + public string GetBaseClassTemplateParameters(BaseClassSpecifier baseClassSpec) + { + if (!(baseClassSpec.Type is TemplateSpecializationType templateSpecType)) + throw new NotSupportedException(); + + var args = templateSpecType.Arguments.Select(arg => + { + arg.Type.Type.TryGetClass(out var @class); + return @class; + }); + + return $"<{string.Join(", ", args.Select(c => c.Name))}>"; + } + + public override void GenerateClassSpecifier(Class @class) + { + if (@class.IsAbstract) + Write("abstract "); + + Write(@class.IsInterface ? "interface" : "class"); + Write($" {@class.Name}"); + if (@class.IsDependent) + Write(GetClassTemplateParameters(@class)); + + if (!@class.IsStatic && @class.HasNonIgnoredBase) + { + var baseClassSpec = @class.Bases.First(bs => bs.Class == @class.BaseClass); + var baseClass = baseClassSpec.Class; + var needsQualifiedName = baseClass.TranslationUnit != @class.TranslationUnit; + var baseClassName = needsQualifiedName ? QualifiedIdentifier(baseClass) : baseClass.Name; + + Write($" extends {baseClassName}"); + if (baseClass.IsDependent) + Write($"{GetBaseClassTemplateParameters(baseClassSpec)}"); + } + } + + public void GenerateClassProperties(Class @class) + { + // Handle the case of struct (value-type) inheritance by adding the base + // properties to the managed value subtypes. + if (@class.IsValueType) + { + foreach (var @base in @class.Bases.Where(b => b.IsClass && b.Class.IsDeclared)) + { + GenerateClassProperties(@base.Class); + } + } + + Indent(); + foreach (var prop in @class.Properties.Where( + prop => !ASTUtils.CheckIgnoreProperty(prop) && !TypeIgnored(prop.Type))) + { + if (prop.IsInRefTypeAndBackedByValueClassField()) + { + prop.Field.Visit(this); + continue; + } + + prop.Visit(this); + } + Unindent(); + } + + public virtual void GenerateIndexer(Property property) + { + throw new System.NotImplementedException(); + } + + public override string GenerateEnumSpecifier(Enumeration @enum) + { + Write($"enum {@enum.Name}"); + return @enum.Name; + } + + public override bool VisitFieldDecl(Field field) + { + PushBlock(BlockKind.Field, field); + + GenerateDeclarationCommon(field); + + var fieldType = field.Type.Visit(CTypePrinter); + WriteLine($"{fieldType} {field.Name};"); + + PopBlock(); + + return true; + } + + public override bool VisitEvent(Event @event) + { + PushBlock(BlockKind.Event, @event); + + GenerateDeclarationCommon(@event); + + //WriteLine($"{@event.Name}: CppSharp.Signal;"); + + PopBlock(NewLineKind.BeforeNextBlock); + + return true; + } + + public override bool VisitProperty(Property property) + { + GenerateDeclarationCommon(property); + + return base.VisitProperty(property); + } + + public override bool VisitMethodDecl(Method method) + { + if (ASTUtils.CheckIgnoreMethod(method) || FunctionIgnored(method)) + return false; + + PushBlock(BlockKind.Method, method); + GenerateDeclarationCommon(method); + + GenerateMethodSpecifier(method); + WriteLine(";"); + + PopBlock(NewLineKind.BeforeNextBlock); + + return true; + } + + public override void GenerateMethodSpecifier(Method method, MethodSpecifierKind? kind = null) + { + if (method.IsConstructor || method.IsDestructor || + method.OperatorKind == CXXOperatorKind.Conversion || + method.OperatorKind == CXXOperatorKind.ExplicitConversion) + { + Write($"{GetMethodIdentifier(method)}("); + GenerateMethodParameters(method); + Write($")"); + return; + } + + Write($"{GetMethodIdentifier(method)}("); + GenerateMethodParameters(method); + + var returnType = method.ReturnType.Visit(CTypePrinter); + Write($"): {returnType}"); + } + + public override string GetMethodIdentifier(Function function, + TypePrinterContextKind context = TypePrinterContextKind.Managed) + { + if (function is Method method) + { + if (method.IsConstructor) + return "constructor"; + } + + return base.GetMethodIdentifier(function, context); + } + + public override bool VisitTypedefNameDecl(TypedefNameDecl typedef) + { + if (!typedef.IsGenerated) + return false; + + var functionType = typedef.Type as FunctionType; + if (functionType != null || typedef.Type.IsPointerTo(out functionType)) + { + PushBlock(BlockKind.Typedef, typedef); + GenerateDeclarationCommon(typedef); + + var @delegate = string.Format(CTypePrinter.VisitDelegate(functionType), typedef.Name); + WriteLine($"{@delegate};"); + + PopBlock(NewLineKind.BeforeNextBlock); + + return true; + } + + return false; + } + + public override bool VisitFunctionDecl(Function function) + { + if (!function.IsGenerated || FunctionIgnored(function)) + return false; + + PushBlock(BlockKind.Function, function); + + GenerateDeclarationCommon(function); + + var retType = function.ReturnType.Visit(CTypePrinter); + Write($"export function {function.Name}("); + + GenerateMethodParameters(function); + WriteLine($"): {retType};"); + + PopBlock(); + + return true; + } + + public static bool FunctionIgnored(Function function) + { + return TypeIgnored(function.ReturnType.Type) || + function.Parameters.Any(param => TypeIgnored(param.Type)); + } + + public static bool TypeIgnored(CppSharp.AST.Type type) + { + var desugared = type.Desugar(); + var finalType = (desugared.GetFinalPointee() ?? desugared).Desugar(); + Class @class; + return finalType.TryGetClass(out @class) && (@class.CompleteDeclaration == null && @class.IsIncomplete); + } + } +} diff --git a/src/Generator/Generators/TS/TSTypePrinter.cs b/src/Generator/Generators/TS/TSTypePrinter.cs new file mode 100644 index 00000000..d53107df --- /dev/null +++ b/src/Generator/Generators/TS/TSTypePrinter.cs @@ -0,0 +1,166 @@ +using System; +using System.Linq; +using CppSharp.AST; +using CppSharp.AST.Extensions; +using CppSharp.Generators.C; + +namespace CppSharp.Generators.TS +{ + public class TSTypePrinter : CppTypePrinter + { + public override string NamespaceSeparator => "."; + + public override bool HasGlobalNamespacePrefix => false; + + public override bool PrefixSpecialFunctions => true; + + public TSTypePrinter(BindingContext context) : base(context) + { + PrintTypeModifiers = false; + } + + public override string GlobalNamespace(Declaration declaration) + { + return declaration.TranslationUnit?.Module?.LibraryName; + } + + public override TypePrinterResult GetDeclName(Declaration declaration, TypePrintScopeKind scope) + { + var result = base.GetDeclName(declaration, scope); + result.Type = result.Type.Replace("::", NamespaceSeparator); + return result; + } + + public override TypePrinterResult VisitArrayType(ArrayType array, TypeQualifiers quals) + { + return $"{array.Type.Visit(this)}[]"; + } + + public override TypePrinterResult VisitBuiltinType(BuiltinType builtin, TypeQualifiers quals) + { + return VisitPrimitiveType(builtin.Type); + } + + public override TypePrinterResult VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers quals) + { + return VisitPrimitiveType(primitive); + } + + public override TypePrinterResult VisitTypedefType(TypedefType typedef, TypeQualifiers quals) + { + if (typedef.Declaration.QualifiedOriginalName == "std::nullptr_t") + return VisitPrimitiveType(PrimitiveType.Null); + + if (typedef.Declaration.Type.IsPrimitiveType()) + return typedef.Declaration.Type.Visit(this); + + return base.VisitTypedefType(typedef, quals); + } + + public override TypePrinterResult VisitPrimitiveType(PrimitiveType primitive) + { + switch (primitive) + { + case PrimitiveType.Bool: + return "boolean"; + case PrimitiveType.Void: + return "void"; + case PrimitiveType.Char16: + case PrimitiveType.Char32: + case PrimitiveType.WideChar: + case PrimitiveType.Char: + case PrimitiveType.SChar: + case PrimitiveType.UChar: + case PrimitiveType.Short: + case PrimitiveType.UShort: + case PrimitiveType.Int: + case PrimitiveType.UInt: + case PrimitiveType.Long: + case PrimitiveType.ULong: + case PrimitiveType.LongLong: + return "number"; + case PrimitiveType.ULongLong: + return "bigint"; + case PrimitiveType.Half: + case PrimitiveType.Float: + case PrimitiveType.Double: + return "number"; + case PrimitiveType.LongDouble: + case PrimitiveType.Float128: + case PrimitiveType.Int128: + case PrimitiveType.UInt128: + case PrimitiveType.IntPtr: + case PrimitiveType.UIntPtr: + case PrimitiveType.Null: + return "null"; + case PrimitiveType.String: + return "string"; + case PrimitiveType.Decimal: + return "number"; + } + + throw new NotSupportedException(); + } + + public override TypePrinterResult VisitPointerType(PointerType pointer, TypeQualifiers quals) + { + if (pointer.IsConstCharString()) + return VisitPrimitiveType(PrimitiveType.String); + + return base.VisitPointerType(pointer, quals); + } + + public override TypePrinterResult VisitTagType(TagType tag, TypeQualifiers quals) + { + if (FindTypeMap(tag, out var result)) + return result; + + return tag.Declaration.Visit(this); + } + + public override TypePrinterResult VisitTemplateSpecializationType(TemplateSpecializationType template, + TypeQualifiers quals) + { + if (!template.Desugared.Type.TryGetClass(out var @class)) + return string.Empty; + + var args = template.Arguments.Select(a => a.Type.Visit(this)); + return $"{@class.Visit(this)}<{string.Join(", ", args)}>"; + } + + public override TypePrinterResult VisitParameter(Parameter param, bool hasName = true) + { + var oldParam = Parameter; + Parameter = param; + var result = param.QualifiedType.Visit(this); + result.Kind = GeneratorKind.TypeScript; + Parameter = oldParam; + + var printName = hasName && !string.IsNullOrEmpty(param.Name); + if (!printName) + return result; + + result.Name = param.Name; + +/* + if (param.DefaultArgument != null && Options.GenerateDefaultValuesForArguments) + { + try + { + var expressionPrinter = new CSharpExpressionPrinter(this); + var defaultValue = expressionPrinter.VisitParameter(param); + return $"{result} = {defaultValue}"; + } + catch (Exception) + { + var function = param.Namespace as Function; + Diagnostics.Warning($"Error printing default argument expression: " + + $"{function.QualifiedOriginalName}({param.OriginalName})"); + } + } +*/ + + return $"{result}"; + } + } +} diff --git a/src/Generator/Generators/TypePrinter.cs b/src/Generator/Generators/TypePrinter.cs index b31efab6..838baca9 100644 --- a/src/Generator/Generators/TypePrinter.cs +++ b/src/Generator/Generators/TypePrinter.cs @@ -13,6 +13,7 @@ namespace CppSharp.Generators public StringBuilder NamePrefix { get; set; } = new StringBuilder(); public StringBuilder NameSuffix { get; set; } = new StringBuilder(); public TypeMap TypeMap { get; set; } + public GeneratorKind Kind { get; set; } public TypePrinterResult(string type = "", string nameSuffix = "") { @@ -35,7 +36,10 @@ namespace CppSharp.Generators public override string ToString() { - bool hasPlaceholder = Type.Contains("{0}"); + if (Kind == GeneratorKind.TypeScript) + return $"{Name}{NameSuffix}: {Type}"; + + var hasPlaceholder = Type.Contains("{0}"); if (hasPlaceholder) return string.Format(Type, $"{NamePrefix}{Name}{NameSuffix}"); diff --git a/src/Generator/Passes/CheckDuplicatedNamesPass.cs b/src/Generator/Passes/CheckDuplicatedNamesPass.cs index a80a7e85..1ccefa42 100644 --- a/src/Generator/Passes/CheckDuplicatedNamesPass.cs +++ b/src/Generator/Passes/CheckDuplicatedNamesPass.cs @@ -207,6 +207,7 @@ namespace CppSharp.Passes case GeneratorKind.CPlusPlus: case GeneratorKind.QuickJS: case GeneratorKind.NAPI: + case GeneratorKind.TypeScript: typePrinter = new CppTypePrinter(Context); break; case GeneratorKind.CLI: diff --git a/tests2/ts/.gitignore b/tests2/ts/.gitignore new file mode 100644 index 00000000..e65691b1 --- /dev/null +++ b/tests2/ts/.gitignore @@ -0,0 +1,3 @@ +gen +*.js +*.map diff --git a/tests2/ts/test.sh b/tests2/ts/test.sh new file mode 100755 index 00000000..a468220a --- /dev/null +++ b/tests2/ts/test.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e +dir=$(cd "$(dirname "$0")"; pwd) +rootdir="$dir/../.." +dotnet_configuration=Release +configuration=debug +platform=x64 +jsinterp="$rootdir/deps/quickjs/qjs-debug" + +red=`tput setaf 1` +green=`tput setaf 2` +reset=`tput sgr0` + +generate=true + +if [ $generate = true ]; then + echo "${green}Generating bindings${reset}" + dotnet $rootdir/bin/${dotnet_configuration}_${platform}/CppSharp.CLI.dll \ + --gen=ts -I$dir/.. -I$rootdir/include -o $dir/gen -m tests $dir/../*.h +fi + +echo "${green}Building generated binding files${reset}" +#make -C $dir/gen +echo + +echo "${green}Typechecking generated binding files with tsc${reset}" +#tsc --noEmit --strict --noImplicitAny --strictNullChecks --strictFunctionTypes --noImplicitThis gen/*.d.ts + +# echo "${green}Executing JS tests with QuickJS${reset}" +# cp $dir/gen/bin/$configuration/libtest.so $dir +# #cp $dir/gen/bin/$configuration/libtest.dylib $dir +# $jsinterp --std $dir/test.js \ No newline at end of file diff --git a/tests2/ts/test.ts b/tests2/ts/test.ts new file mode 100644 index 00000000..6d877ad7 --- /dev/null +++ b/tests2/ts/test.ts @@ -0,0 +1,73 @@ +import * as test from "tests"; + + +function builtins() +{ + eq(test.ReturnsVoid(), undefined) + + eq(test.ReturnsBool(), true) + eq(test.PassAndReturnsBool(false), false) + + eq(test.ReturnsNullptr(), null) + //eq(test.PassAndReturnsNullptr(null), null) + eq(test.ReturnsNullptr(), null) + + eq(test.ReturnsChar (), ascii('a')); + eq(test.ReturnsSChar(), ascii('a')); + eq(test.ReturnsUChar(), ascii('a')); + + eq(test.PassAndReturnsChar (ascii('a')), ascii('a')); + eq(test.PassAndReturnsSChar(ascii('b')), ascii('b')); + eq(test.PassAndReturnsUChar(ascii('c')), ascii('c')); + + // TODO: add wchar_t tests + + eq(test.ReturnsFloat (), 5.0); + eq(test.ReturnsDouble(), -5.0); + //eq(test.ReturnsLongDouble(), -5.0); + + //floateq(test.PassAndReturnsFloat (1.32), 1.32); + floateq(test.PassAndReturnsDouble(1.32), 1.32); + //float(test.PassAndReturnsLongDouble(1.32), 1.32); + + eq(test.ReturnsInt8 (), -5); + eq(test.ReturnsUInt8 (), 5); + eq(test.ReturnsInt16 (), -5); + eq(test.ReturnsUInt16(), 5); + eq(test.ReturnsInt32 (), -5); + eq(test.ReturnsUInt32(), 5); + eq(test.ReturnsInt64 (), -5n); + eq(test.ReturnsUInt64(), 5n); + + const int8 = { min: -(2**7), max: (2**7) - 1 }; + eq(test.PassAndReturnsInt8(int8.min), int8.min); + eq(test.PassAndReturnsInt8(int8.max), int8.max); + + const uint8 = { min: 0, max: (2**8) - 1 }; + eq(test.PassAndReturnsUInt8(uint8.min), uint8.min); + eq(test.PassAndReturnsUInt8(uint8.max), uint8.max); + + const int16 = { min: -(2**15), max: (2**15) - 1 }; + eq(test.PassAndReturnsInt16(int16.min), int16.min); + eq(test.PassAndReturnsInt16(int16.max), int16.max); + + const uint16 = { min: 0, max: (2**16) - 1 }; + eq(test.PassAndReturnsUInt16(uint16.min), uint16.min); + eq(test.PassAndReturnsUInt16(uint16.max), uint16.max); + + const int32 = { min: -(2**31), max: (2**31) - 1 }; + eq(test.PassAndReturnsInt32(int32.min), int32.min); + eq(test.PassAndReturnsInt32(int32.max), int32.max); + + const uint32 = { min: 0, max: (2**32) - 1 }; + eq(test.PassAndReturnsUInt32(uint32.min), uint32.min); + eq(test.PassAndReturnsUInt32(uint32.max), uint32.max); + + const int64 = { min: BigInt(2**63) * -1n, max: BigInt(2**63) - 1n }; + eq(test.PassAndReturnsInt64(int64.min), int64.min); + eq(test.PassAndReturnsInt64(int64.max), int64.max); + + const uint64 = { min: BigInt(0), max: BigInt(2**64) - 1n }; + eq(test.PassAndReturnsUInt64(uint64.min), uint64.min); + eq(test.PassAndReturnsUInt64(uint64.max), uint64.max); +} \ No newline at end of file diff --git a/tests2/ts/tsconfig.json b/tests2/ts/tsconfig.json new file mode 100644 index 00000000..dcc8e2ef --- /dev/null +++ b/tests2/ts/tsconfig.json @@ -0,0 +1,13 @@ +{ + "include": ["*.ts", "./gen/*.ts"], + "compilerOptions": { + "alwaysStrict": true, // Parse in strict mode and emit "use strict" for each source file. + "noImplicitAny": true, + "strictNullChecks": true, + "sourceMap": true, + "typeRoots": ["./gen"] + }, + "typeAcquisition": { + "include": ["gen"] + } + } \ No newline at end of file