public string ToString(Predicate<Type> includeNamespace, bool includeGenericArguments, bool strictNullCheck) { var options = new ToTypeScriptOptions(includeNamespace, includeGenericArguments, strictNullCheck); string strictNullCheckString = " | null"; string simpleTypeScript; string result; if (TryGetTypeScriptForSimpleType(this.Type, options, out simpleTypeScript)) { result = simpleTypeScript; } else if (this.Type.IsGenericParameter) { result = this.Type.Name; } else if (!this.Type.IsGenericType) { if (this.Type.IsSystem()) { result = "{}"; } else { string ns = includeNamespace(this.Type) ? $"{this.Type.Namespace.Replace('.', '_')}." : ""; result = $"{ns}{this.Type.Name}"; } } else { // nullable is a very specific case // is is a value type serialize has a null, this is the only value type that can became null var genericArguments = this.Type.GetGenericArguments(); if (this.Type.FullName != null && this.Type.FullName.Contains("System.Nullable") && genericArguments.Length == 1) { result = $"{genericArguments[0].ToTypeScript().ToString(includeNamespace)}{strictNullCheckString}"; } else { result = $"{ToTypeScriptForGenericClass(this.Type, options)}"; if (this.Type.IsArray) { result = $"{result}{(strictNullCheck ? strictNullCheckString : "")}"; } } } if (strictNullCheck) { // value type cannot be null // except arrays and nullable, which was handle previously if (this.Type.IsValueType) { return result; } else { if (result.HasNonEmbededWhiteSpace()) { return $"({result}){strictNullCheckString}"; } else { return $"{result}{strictNullCheckString}"; } } } else { return result; } }
private string ArrayToTypeScript(ToTypeScriptOptions options) { var tsSubType = $"{this.Type.GetElementType().ToTypeScript().ToString(options)}"; return tsSubType.HasNonEmbededWhiteSpace() ? $"({tsSubType})[]" : $"{tsSubType}[]"; }
public string ToString(ToTypeScriptOptions options) => this.ToString(options.IncludeNamespace, options.IncludeGenericArguments, options.Nullable);
/// <summary> /// Generic type, emit GenericType`2<A, B> as GenericType<A, B> /// </summary> private static string GenericTypeToTypeScript(Type type, ToTypeScriptOptions options) { var result = new StringBuilder(); if (options.IncludeNamespace(type)) { result.Append($"{type.GetGenericTypeDefinition().Namespace.Replace('.', '_')}."); } var genericTypeDefinitionName = type.GetGenericTypeDefinition().Name; // generics type got a name like Foo`2, we parse only before the ` var withoutAfterBacktick = genericTypeDefinitionName.Remove(genericTypeDefinitionName.IndexOf('`')); result.Append(withoutAfterBacktick); if (options.IncludeGenericArguments) { var args = type.GetGenericArguments() .Select(t => t.ToTypeScript().ToString(options.IncludeNamespace, true, false)) .Join(", "); result.Append($"<{args}>"); } return result.ToString(); }
/// <summary> /// Complicated stuff for returning the name of a generic class /// </summary> private static string ToTypeScriptForGenericClass(Type type, ToTypeScriptOptions options) { var genericArguments = type.GetGenericArguments(); if (type.IsGeneric1DArray(genericArguments)) { string baseType = genericArguments[0].ToTypeScript().ToString(options); if (baseType.HasNonEmbededWhiteSpace()) { return $"({baseType})[]"; } else { return $"{baseType}[]"; } } else if (type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { var keyType = genericArguments[0]; string keyTypescript; // Today we allow only string or number a for key in a TypeScript Dictionary // we plan to support enum indexer type when typescript will allow // please consider upvote the proposal : https://github.com/Microsoft/TypeScript/issues/2491 if (keyType == typeof(string) || keyType.IsEnum) { keyTypescript = "string"; } else { keyTypescript = "number"; } var valueTypescript = genericArguments[1].ToTypeScript().ToString(options); return $"{{ [id: {keyTypescript}]: {valueTypescript}; }}"; } else { return GenericTypeToTypeScript(type, options); } }
/// <summary> /// Return TypeScript type for simple type like numbers, datetime, string, etc.. /// </summary> /// <param name="type"></param> /// <param name="includeNamespace"></param> /// <param name="includeGenericArguments"></param> /// <param name="value"></param> /// <returns></returns> private static bool TryGetTypeScriptForSimpleType(Type type, ToTypeScriptOptions options, out string value) { value = PredicateToTypescriptStringMap .Where(kvp => kvp.Key(type)) .Select(kvp => kvp.Value(type, options)) .FirstOrDefault(); return value != null; }
private static string TupleToTypeScript(Type type, ToTypeScriptOptions options) { // search the generic type definition so the method work for both Tuple<int> and Tuple<T> var genericTypeDefinition = type.IsGenericType ? type.GetGenericTypeDefinition() : type; if (!genericTypeDefinition.IsTuple()) { throw new ArgumentException(nameof(type), "Type should be a Tuple"); } var content = type.GetGenericArguments() .Select(a => a.ToTypeScript().ToString(options)) .Select((s, i) => $"Item{i + 1}: {s}") .Join(", "); return $"{{ {content} }}"; }