From a7bbccece4db88921aaa502f865d8032ed7ddff5 Mon Sep 17 00:00:00 2001
From: Pavel Vostretsov
Date: Tue, 19 Feb 2019 18:02:31 +0500
Subject: [PATCH] Global nullable option
---
.../CustomGenerator/array-types.expected.js | 2 +-
.../CustomGenerator/array-types.expected.ts | 2 +-
.../global-nullable-disabled.js | 15 ++++
.../global-nullable-disabled.ts | 15 ++++
.../global-nullable-enabled.js | 11 +++
.../global-nullable-enabled.ts | 11 +++
.../SimpleGenerator/array-types.expected.js | 2 +-
.../SimpleGenerator/array-types.expected.ts | 2 +-
.../OptionsTests.cs | 10 +++
.../TypeScript.ContractGenerator.Tests.csproj | 12 ++++
.../Types/GlobalNullableRootType.cs | 16 +++++
.../FlowTypeGenerationOptions.cs | 2 +
.../FlowTypeGenerator.cs | 69 +++++++++----------
.../BuildInTypeBuildingContext.cs | 40 ++++-------
.../CustomTypeTypeBuildingContext.cs | 21 ++++--
.../NullableTypeBuildingContext.cs | 32 +++++++++
16 files changed, 190 insertions(+), 72 deletions(-)
create mode 100644 TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.js
create mode 100644 TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.ts
create mode 100644 TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.js
create mode 100644 TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.ts
create mode 100644 TypeScript.ContractGenerator.Tests/Types/GlobalNullableRootType.cs
create mode 100644 TypeScript.ContractGenerator/TypeBuilders/NullableTypeBuildingContext.cs
diff --git a/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.js b/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.js
index 7b498fa..8fe42bb 100644
--- a/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.js
+++ b/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.js
@@ -2,7 +2,7 @@
export type ArrayRootType = {
ints?: null | number[];
nullableInts?: null | Nullable[];
- byteArray?: null | Byte[];
+ byteArray?: null | string;
nullableByteArray?: null | Nullable[];
enums?: null | AnotherEnum[];
nullableEnums?: null | Nullable[];
diff --git a/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.ts b/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.ts
index a850037..f4baf84 100644
--- a/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.ts
+++ b/TypeScript.ContractGenerator.Tests/Files/CustomGenerator/array-types.expected.ts
@@ -2,7 +2,7 @@
export type ArrayRootType = {
ints?: null | number[];
nullableInts?: null | Nullable[];
- byteArray?: null | Byte[];
+ byteArray?: null | string;
nullableByteArray?: null | Nullable[];
enums?: null | AnotherEnum[];
nullableEnums?: null | Nullable[];
diff --git a/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.js b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.js
new file mode 100644
index 0000000..cd63271
--- /dev/null
+++ b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.js
@@ -0,0 +1,15 @@
+
+export type GlobalNullableRootType = {
+ int: number;
+ nullableInt?: null | number;
+ nullableInts?: null | Nullable[];
+ intGeneric?: null | GenericClass;
+ nullableIntGeneric?: null | GenericClass>;
+};
+export type Nullable = {
+ hasValue: boolean;
+ value: T;
+};
+export type GenericClass = {
+ genericType?: T;
+};
diff --git a/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.ts b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.ts
new file mode 100644
index 0000000..cd63271
--- /dev/null
+++ b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-disabled.ts
@@ -0,0 +1,15 @@
+
+export type GlobalNullableRootType = {
+ int: number;
+ nullableInt?: null | number;
+ nullableInts?: null | Nullable[];
+ intGeneric?: null | GenericClass;
+ nullableIntGeneric?: null | GenericClass>;
+};
+export type Nullable = {
+ hasValue: boolean;
+ value: T;
+};
+export type GenericClass = {
+ genericType?: T;
+};
diff --git a/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.js b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.js
new file mode 100644
index 0000000..c2a2154
--- /dev/null
+++ b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.js
@@ -0,0 +1,11 @@
+
+export type GlobalNullableRootType = {
+ int: number;
+ nullableInt?: ?number;
+ nullableInts?: ??number[];
+ intGeneric?: ?GenericClass;
+ nullableIntGeneric?: ?GenericClass;
+};
+export type GenericClass = {
+ genericType?: T;
+};
diff --git a/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.ts b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.ts
new file mode 100644
index 0000000..b379fca
--- /dev/null
+++ b/TypeScript.ContractGenerator.Tests/Files/Options.Expected/global-nullable-enabled.ts
@@ -0,0 +1,11 @@
+
+export type GlobalNullableRootType = {
+ int: number;
+ nullableInt?: Nullable;
+ nullableInts?: Nullable[]>;
+ intGeneric?: Nullable>;
+ nullableIntGeneric?: Nullable>>;
+};
+export type GenericClass = {
+ genericType?: T;
+};
diff --git a/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.js b/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.js
index d22c74c..21823aa 100644
--- a/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.js
+++ b/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.js
@@ -2,7 +2,7 @@
export type ArrayRootType = {
ints?: null | number[];
nullableInts?: null | Nullable[];
- byteArray?: null | Byte[];
+ byteArray?: null | string;
nullableByteArray?: null | Nullable[];
enums?: null | AnotherEnum[];
nullableEnums?: null | Nullable[];
diff --git a/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.ts b/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.ts
index 83a5376..6823a51 100644
--- a/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.ts
+++ b/TypeScript.ContractGenerator.Tests/Files/SimpleGenerator/array-types.expected.ts
@@ -2,7 +2,7 @@
export type ArrayRootType = {
ints?: null | number[];
nullableInts?: null | Nullable[];
- byteArray?: null | Byte[];
+ byteArray?: null | string;
nullableByteArray?: null | Nullable[];
enums?: null | AnotherEnum[];
nullableEnums?: null | Nullable[];
diff --git a/TypeScript.ContractGenerator.Tests/OptionsTests.cs b/TypeScript.ContractGenerator.Tests/OptionsTests.cs
index ec9db3e..d93ed6e 100644
--- a/TypeScript.ContractGenerator.Tests/OptionsTests.cs
+++ b/TypeScript.ContractGenerator.Tests/OptionsTests.cs
@@ -1,3 +1,4 @@
+using System;
using System.Linq;
using FluentAssertions;
@@ -42,5 +43,14 @@ public void ExplicitNullabilityTest(bool explicitNullabilityEnabled, string expe
var expectedCode = GetExpectedCode($"Options.Expected/{expectedFileName}");
generatedCode.Should().Be(expectedCode);
}
+
+ [TestCase(true, "global-nullable-enabled")]
+ [TestCase(false, "global-nullable-disabled")]
+ public void GlobalNullableTest(bool useGlobalNullable, string expectedFileName)
+ {
+ var generatedCode = GenerateCode(new FlowTypeGenerationOptions {UseGlobalNullable = useGlobalNullable}, CustomTypeGenerator.Null, typeof(GlobalNullableRootType)).Single().Replace("\r\n", "\n");
+ var expectedCode = GetExpectedCode($"Options.Expected/{expectedFileName}");
+ generatedCode.Should().Be(expectedCode);
+ }
}
}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator.Tests/TypeScript.ContractGenerator.Tests.csproj b/TypeScript.ContractGenerator.Tests/TypeScript.ContractGenerator.Tests.csproj
index d267cc5..4aa5a57 100644
--- a/TypeScript.ContractGenerator.Tests/TypeScript.ContractGenerator.Tests.csproj
+++ b/TypeScript.ContractGenerator.Tests/TypeScript.ContractGenerator.Tests.csproj
@@ -218,6 +218,18 @@
Always
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
diff --git a/TypeScript.ContractGenerator.Tests/Types/GlobalNullableRootType.cs b/TypeScript.ContractGenerator.Tests/Types/GlobalNullableRootType.cs
new file mode 100644
index 0000000..33d0c09
--- /dev/null
+++ b/TypeScript.ContractGenerator.Tests/Types/GlobalNullableRootType.cs
@@ -0,0 +1,16 @@
+namespace SkbKontur.TypeScript.ContractGenerator.Tests.Types
+{
+ public class GlobalNullableRootType
+ {
+ public int Int { get; set; }
+ public int? NullableInt { get; set; }
+ public int?[] NullableInts { get; set; }
+ public GenericClass IntGeneric { get; set; }
+ public GenericClass NullableIntGeneric { get; set; }
+ }
+
+ public class GenericClass
+ {
+ public T GenericType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/FlowTypeGenerationOptions.cs b/TypeScript.ContractGenerator/FlowTypeGenerationOptions.cs
index 2b3b846..c6f333f 100644
--- a/TypeScript.ContractGenerator/FlowTypeGenerationOptions.cs
+++ b/TypeScript.ContractGenerator/FlowTypeGenerationOptions.cs
@@ -11,6 +11,8 @@ public class FlowTypeGenerationOptions
public bool EnableExplicitNullability { get; set; } = true;
+ public bool UseGlobalNullable { get; set; }
+
[NotNull]
public static FlowTypeGenerationOptions Default { get; } = new FlowTypeGenerationOptions();
}
diff --git a/TypeScript.ContractGenerator/FlowTypeGenerator.cs b/TypeScript.ContractGenerator/FlowTypeGenerator.cs
index 063a970..4a9a70c 100644
--- a/TypeScript.ContractGenerator/FlowTypeGenerator.cs
+++ b/TypeScript.ContractGenerator/FlowTypeGenerator.cs
@@ -76,48 +76,43 @@ public ITypeBuildingContext ResolveType(Type type)
return flowTypeDeclarations[type];
}
var typeLocation = customTypeGenerator.GetTypeLocation(type);
- var typeBuildingContext = customTypeGenerator.ResolveType(typeLocation, type, flowTypeUnitFactory);
- if (typeBuildingContext == null)
- {
- if (BuildInTypeBuildingContext.Accept(type))
- {
- typeBuildingContext = new BuildInTypeBuildingContext(type);
- }
- if (type.IsArray)
- {
- typeBuildingContext = new ArrayTypeBuildingContext(type.GetElementType());
- }
- if (type.IsEnum)
- {
- var targetUnit = flowTypeUnitFactory.GetOrCreateTypeUnit(typeLocation);
- typeBuildingContext = options.EnumGenerationMode == EnumGenerationMode.FixedStringsAndDictionary
- ? (ITypeBuildingContext)new FixedStringsAndDictionaryTypeBuildingContext(targetUnit, type)
- : new TypeScriptEnumTypeBuildingContext(targetUnit, type);
- }
- if (type.IsGenericType && !type.IsGenericTypeDefinition)
- {
- typeBuildingContext = new GenericTypeTypeBuildingContext(type);
- }
- if (type.IsGenericParameter)
- {
- typeBuildingContext = new GenericParameterTypeBuildingContext(type);
- }
- if (type.IsGenericTypeDefinition)
- {
- var targetUnit = flowTypeUnitFactory.GetOrCreateTypeUnit(typeLocation);
- typeBuildingContext = new CustomTypeTypeBuildingContext(targetUnit, type, options);
- }
- if (typeBuildingContext == null)
- {
- var targetUnit = flowTypeUnitFactory.GetOrCreateTypeUnit(typeLocation);
- typeBuildingContext = new CustomTypeTypeBuildingContext(targetUnit, type, options);
- }
- }
+ var typeBuildingContext = customTypeGenerator.ResolveType(typeLocation, type, flowTypeUnitFactory) ?? GetTypeBuildingContext(typeLocation, type);
typeBuildingContext.Initialize(this);
flowTypeDeclarations.Add(type, typeBuildingContext);
return typeBuildingContext;
}
+ private ITypeBuildingContext GetTypeBuildingContext(string typeLocation, Type type)
+ {
+ if (BuildInTypeBuildingContext.Accept(type))
+ return new BuildInTypeBuildingContext(type);
+
+ if (type.IsArray)
+ return new ArrayTypeBuildingContext(type.GetElementType());
+
+ if (type.IsEnum)
+ {
+ var targetUnit = flowTypeUnitFactory.GetOrCreateTypeUnit(typeLocation);
+ return options.EnumGenerationMode == EnumGenerationMode.FixedStringsAndDictionary
+ ? (ITypeBuildingContext)new FixedStringsAndDictionaryTypeBuildingContext(targetUnit, type)
+ : new TypeScriptEnumTypeBuildingContext(targetUnit, type);
+ }
+
+ if (options.UseGlobalNullable && type.IsGenericType && !type.IsGenericTypeDefinition && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ return new NullableTypeBuildingContext(type);
+
+ if (type.IsGenericType && !type.IsGenericTypeDefinition)
+ return new GenericTypeTypeBuildingContext(type);
+
+ if (type.IsGenericParameter)
+ return new GenericParameterTypeBuildingContext(type);
+
+ if (type.IsGenericTypeDefinition)
+ return new CustomTypeTypeBuildingContext(flowTypeUnitFactory.GetOrCreateTypeUnit(typeLocation), type, options);
+
+ return new CustomTypeTypeBuildingContext(flowTypeUnitFactory.GetOrCreateTypeUnit(typeLocation), type, options);
+ }
+
public FlowTypeType BuildAndImportType(FlowTypeUnit targetUnit, ICustomAttributeProvider attributeProvider, Type type)
{
var (isNullable, resultType) = FlowTypeGeneratorHelpers.ProcessNullable(attributeProvider, type);
diff --git a/TypeScript.ContractGenerator/TypeBuilders/BuildInTypeBuildingContext.cs b/TypeScript.ContractGenerator/TypeBuilders/BuildInTypeBuildingContext.cs
index 880ea35..ae0dc2f 100644
--- a/TypeScript.ContractGenerator/TypeBuilders/BuildInTypeBuildingContext.cs
+++ b/TypeScript.ContractGenerator/TypeBuilders/BuildInTypeBuildingContext.cs
@@ -1,5 +1,5 @@
using System;
-using System.Linq;
+using System.Collections.Generic;
using SkbKontur.TypeScript.ContractGenerator.CodeDom;
@@ -14,27 +14,13 @@ public BuildInTypeBuildingContext(Type type)
public static bool Accept(Type type)
{
- return buildInTyped.Contains(type);
+ return builtinTypes.ContainsKey(type);
}
public FlowTypeType ReferenceFrom(FlowTypeUnit targetUnit, ITypeGenerator typeGenerator)
{
- if (type == typeof(string))
- return new FlowTypeBuildInType("string");
- if (type == typeof(bool))
- return new FlowTypeBuildInType("boolean");
- if (type == typeof(int))
- return new FlowTypeBuildInType("number");
- if (type == typeof(decimal))
- return new FlowTypeBuildInType("number");
- if (type == typeof(long))
- return new FlowTypeBuildInType("string");
- if (type == typeof(DateTime))
- return new FlowTypeBuildInType("(Date | string)");
- if (type == typeof(byte[]))
- return new FlowTypeBuildInType("string");
- if (type == typeof(void))
- return new FlowTypeBuildInType("void");
+ if (builtinTypes.ContainsKey(type))
+ return new FlowTypeBuildInType(builtinTypes[type]);
throw new ArgumentOutOfRangeException();
}
@@ -50,16 +36,16 @@ public void BuildDefinition(ITypeGenerator typeGenerator)
private readonly Type type;
- private static readonly Type[] buildInTyped =
+ private static readonly Dictionary builtinTypes = new Dictionary
{
- typeof(string),
- typeof(bool),
- typeof(int),
- typeof(decimal),
- typeof(long),
- typeof(DateTime),
- typeof(void),
- typeof(byte[]),
+ {typeof(string), "string"},
+ {typeof(bool), "boolean"},
+ {typeof(int), "number"},
+ {typeof(decimal), "number"},
+ {typeof(long), "string"},
+ {typeof(DateTime), "(Date | string)"},
+ {typeof(byte[]), "string"},
+ {typeof(void), "void"}
};
}
}
\ No newline at end of file
diff --git a/TypeScript.ContractGenerator/TypeBuilders/CustomTypeTypeBuildingContext.cs b/TypeScript.ContractGenerator/TypeBuilders/CustomTypeTypeBuildingContext.cs
index 2b56138..079619e 100644
--- a/TypeScript.ContractGenerator/TypeBuilders/CustomTypeTypeBuildingContext.cs
+++ b/TypeScript.ContractGenerator/TypeBuilders/CustomTypeTypeBuildingContext.cs
@@ -55,19 +55,32 @@ protected virtual FlowTypeTypeDefintion CreateComplexFlowTypeDefinition(ITypeGen
{
var (isNullable, type) = FlowTypeGeneratorHelpers.ProcessNullable(property, property.PropertyType);
- var propertyType = typeGenerator.BuildAndImportType(Unit, null, type);
result.Members.Add(new FlowTypeTypeMemberDeclaration
{
Name = BuildPropertyName(property.Name),
Optional = isNullable && options.EnableOptionalProperties,
- Type = property.PropertyType.IsGenericParameter
- ? new FlowTypeTypeReference(property.PropertyType.Name)
- : isNullable && options.EnableExplicitNullability ? OrNull(propertyType) : propertyType,
+ Type = GetMaybeNullableComplexType(typeGenerator, type, property, isNullable),
});
}
return result;
}
+ private FlowTypeType GetMaybeNullableComplexType(ITypeGenerator typeGenerator, Type type, PropertyInfo property, bool isNullable)
+ {
+ var propertyType = typeGenerator.BuildAndImportType(Unit, null, type);
+
+ if (property.PropertyType.IsGenericParameter)
+ return new FlowTypeTypeReference(property.PropertyType.Name);
+
+ if (isNullable && options.EnableExplicitNullability && !options.UseGlobalNullable)
+ return OrNull(propertyType);
+
+ if (isNullable && options.EnableExplicitNullability && options.UseGlobalNullable)
+ return new FlowTypeNullableType(propertyType);
+
+ return propertyType;
+ }
+
private static FlowTypeUnionType OrNull(FlowTypeType buildAndImportType)
{
return new FlowTypeUnionType(
diff --git a/TypeScript.ContractGenerator/TypeBuilders/NullableTypeBuildingContext.cs b/TypeScript.ContractGenerator/TypeBuilders/NullableTypeBuildingContext.cs
new file mode 100644
index 0000000..0eb00ae
--- /dev/null
+++ b/TypeScript.ContractGenerator/TypeBuilders/NullableTypeBuildingContext.cs
@@ -0,0 +1,32 @@
+using System;
+
+using SkbKontur.TypeScript.ContractGenerator.CodeDom;
+
+namespace SkbKontur.TypeScript.ContractGenerator.TypeBuilders
+{
+ public class NullableTypeBuildingContext : ITypeBuildingContext
+ {
+ public NullableTypeBuildingContext(Type nullableType)
+ {
+ itemType = nullableType.GetGenericArguments()[0];
+ }
+
+ public bool IsDefinitionBuilt => true;
+
+ public void Initialize(ITypeGenerator typeGenerator)
+ {
+ }
+
+ public void BuildDefinition(ITypeGenerator typeGenerator)
+ {
+ }
+
+ public FlowTypeType ReferenceFrom(FlowTypeUnit targetUnit, ITypeGenerator typeGenerator)
+ {
+ var itemFlowType = typeGenerator.ResolveType(itemType).ReferenceFrom(targetUnit, typeGenerator);
+ return new FlowTypeNullableType(itemFlowType);
+ }
+
+ private readonly Type itemType;
+ }
+}
\ No newline at end of file